VMM/*: Made all modules names lowercase, adjusted imports. v0.6.x
authorPascal Volk <neverseen@users.sourceforge.net>
Wed, 28 Jul 2010 02:08:03 +0000
branchv0.6.x
changeset 320 011066435e6f
parent 319 f4956b4ceba1
child 321 883d5cd66498
VMM/*: Made all modules names lowercase, adjusted imports.
VirtualMailManager/Account.py
VirtualMailManager/Alias.py
VirtualMailManager/AliasDomain.py
VirtualMailManager/Config.py
VirtualMailManager/Domain.py
VirtualMailManager/EmailAddress.py
VirtualMailManager/Handler.py
VirtualMailManager/Relocated.py
VirtualMailManager/Transport.py
VirtualMailManager/__init__.py
VirtualMailManager/account.py
VirtualMailManager/alias.py
VirtualMailManager/aliasdomain.py
VirtualMailManager/cli/Config.py
VirtualMailManager/cli/Handler.py
VirtualMailManager/cli/__init__.py
VirtualMailManager/cli/config.py
VirtualMailManager/cli/handler.py
VirtualMailManager/common.py
VirtualMailManager/config.py
VirtualMailManager/domain.py
VirtualMailManager/emailaddress.py
VirtualMailManager/errors.py
VirtualMailManager/ext/Postconf.py
VirtualMailManager/ext/postconf.py
VirtualMailManager/handler.py
VirtualMailManager/mailbox.py
VirtualMailManager/maillocation.py
VirtualMailManager/password.py
VirtualMailManager/relocated.py
VirtualMailManager/transport.py
--- a/VirtualMailManager/Account.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,421 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Account
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Virtual Mail Manager's Account class to manage e-mail accounts.
-"""
-
-from VirtualMailManager.Domain import Domain
-from VirtualMailManager.EmailAddress import EmailAddress
-from VirtualMailManager.Transport import Transport
-from VirtualMailManager.common import version_str
-from VirtualMailManager.constants import \
-     ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
-     INVALID_ARGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, \
-     NO_SUCH_DOMAIN, UNKNOWN_SERVICE
-from VirtualMailManager.errors import AccountError as AErr
-from VirtualMailManager.maillocation import MailLocation
-from VirtualMailManager.password import pwhash
-
-
-_ = lambda msg: msg
-cfg_dget = lambda option: None
-
-
-class Account(object):
-    """Class to manage e-mail accounts."""
-    __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
-                 '_transport', '_uid')
-
-    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` : pyPgSQL.PgSQL.Connection
-          A database connection for the database access.
-        `address` : VirtualMailManager.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:
-            raise AErr(_(u"The domain '%s' doesn't exist.") %
-                       self._addr.domainname, NO_SUCH_DOMAIN)
-        self._uid = 0
-        self._mail = None
-        self._transport = self._domain.transport
-        self._passwd = None
-        self._new = True
-        self._load()
-
-    def __nonzero__(self):
-        """Returns `True` if the Account is known, `False` if it's new."""
-        return not self._new
-
-    def _load(self):
-        """Load 'uid', 'mid' and 'tid' from the database and set _new to
-        `False` - if the user could be found. """
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT uid, mid, tid FROM users WHERE gid = %s AND '
-                    'local_part = %s', self._domain.gid, self._addr.localpart)
-        result = dbc.fetchone()
-        dbc.close()
-        if result:
-            self._uid, _mid, _tid = result
-            if _tid != self._transport.tid:
-                self._transport = Transport(self._dbh, tid=_tid)
-            self._mail = MailLocation(self._dbh, mid=_mid)
-            self._new = False
-
-    def _set_uid(self):
-        """Set the unique ID for the new Account."""
-        assert self._uid == 0
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('users_uid')")
-        self._uid = dbc.fetchone()[0]
-        dbc.close()
-
-    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(_(u"The mailbox format '%(mbfmt)s' requires Dovecot "
-                         u">= v%(version)s") % {'mbfmt': maillocation.mbformat,
-                       'version': version_str(maillocation.dovecot_version)},
-                       INVALID_MAIL_LOCATION)
-        if not maillocation.postfix and \
-          self._transport.transport.lower() in ('virtual:', 'virtual'):
-            raise AErr(_(u"Invalid transport '%(transport)s' for mailbox "
-                         u"format '%(mbfmt)s'") %
-                       {'transport': self._transport,
-                        'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
-        self._mail = maillocation
-        self._set_uid()
-
-    def _switch_state(self, state, service):
-        """Switch the state of the Account's services on or off. See
-        Account.enable()/Account.disable() for more information."""
-        self._chk_state()
-        if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
-            raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE)
-        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
-            sieve_col = 'sieve'
-        else:
-            sieve_col = 'managesieve'
-        if service in ('smtp', 'pop3', 'imap'):
-            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
-                                                               self._uid)
-        elif service == 'sieve':
-            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col,
-                                                               state,
-                                                               self._uid)
-        else:
-            sql = 'UPDATE users SET smtp = %(s)s, pop3 = %(s)s, imap = %(s)s,\
- %(col)s = %(s)s WHERE uid = %(uid)d' % \
-                {'s': state, 'col': sieve_col, 'uid': self._uid}
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        dbc.close()
-
-    def _count_aliases(self):
-        """Count all alias addresses where the destination address is the
-        address of the Account."""
-        dbc = self._dbh.cursor()
-        sql = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
-                % self._addr
-        dbc.execute(sql)
-        a_count = dbc.fetchone()[0]
-        dbc.close()
-        return a_count
-
-    def _chk_state(self):
-        """Raise an AccountError if the Account is new - not yet saved in the
-        database."""
-        if self._new:
-            raise AErr(_(u"The account '%s' doesn't exist.") % self._addr,
-                       NO_SUCH_ACCOUNT)
-
-    @property
-    def address(self):
-        """The Account's EmailAddress instance."""
-        return self._addr
-
-    @property
-    def domain_directory(self):
-        """The directory of the domain the Account belongs to."""
-        if self._domain:
-            return self._domain.directory
-        return None
-
-    @property
-    def gid(self):
-        """The Account's group ID."""
-        if self._domain:
-            return self._domain.gid
-        return None
-
-    @property
-    def home(self):
-        """The Account's home directory."""
-        if not self._new:
-            return '%s/%s' % (self._domain.directory, self._uid)
-        return None
-
-    @property
-    def mail_location(self):
-        """The Account's MailLocation."""
-        return self._mail
-
-    @property
-    def uid(self):
-        """The Account's unique ID."""
-        return self._uid
-
-    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 isinstance(password, basestring) or not password:
-            raise AErr(_(u"Couldn't accept password: '%s'") % password,
-                       ACCOUNT_MISSING_PASSWORD)
-        self._passwd = password
-
-    def set_transport(self, transport):
-        """Set the transport for the new Account.
-
-        If you want to update the transport of an existing Account use
-        Account.modify().
-
-        Argument:
-
-        `transport` : basestring
-          The string representation of the transport, e.g.: 'dovecot:'
-        """
-        self._transport = Transport(self._dbh, transport=transport)
-
-    def enable(self, service=None):
-        """Enable a/all service/s for the Account.
-
-        Possible values for the *service* are: 'imap', 'pop3', 'sieve' and
-        'smtp'. When all services should be enabled, use 'all' or the
-        default value `None`.
-
-        Arguments:
-
-        `service` : basestring
-          The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all'
-          or `None`.
-        """
-        self._switch_state(True, service)
-
-    def disable(self, service=None):
-        """Disable a/all service/s for the Account.
-
-        For more information see: Account.enable()."""
-        self._switch_state(False, service)
-
-    def save(self):
-        """Save the new Account in the database."""
-        if not self._new:
-            raise AErr(_(u"The account '%s' already exists.") % self._addr,
-                       ACCOUNT_EXISTS)
-        if not self._passwd:
-            raise AErr(_(u"No password set for '%s'.") % self._addr,
-                       ACCOUNT_MISSING_PASSWORD)
-        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
-            sieve_col = 'sieve'
-        else:
-            sieve_col = 'managesieve'
-        self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
-                                   directory=cfg_dget('mailbox.root')))
-        sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
- smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
-            sieve_col, self._addr.localpart, pwhash(self._passwd,
-                                                    user=self._addr),
-            self._uid, self._domain.gid, self._mail.mid, self._transport.tid,
-            cfg_dget('account.smtp'), cfg_dget('account.pop3'),
-            cfg_dget('account.imap'), cfg_dget('account.sieve'))
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        self._dbh.commit()
-        dbc.close()
-        self._new = False
-
-    def modify(self, field, value):
-        """Update the Account's *field* to the new *value*.
-
-        Possible values for *field* are: 'name', 'password' and
-        'transport'.  *value* is the *field*'s new value.
-
-        Arguments:
-
-        `field` : basestring
-          The attribute name: 'name', 'password' or 'transport'
-        `value` : basestring
-          The new value of the attribute.
-        """
-        if field not in ('name', 'password', 'transport'):
-            raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        if field == 'password':
-            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
-                        pwhash(value, user=self._addr), self._uid)
-        elif field == 'transport':
-            if value != self._transport.transport:
-                self._transport = Transport(self._dbh, transport=value)
-                dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
-                            self._transport.tid, self._uid)
-        else:
-            dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
-                        value, self._uid)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        dbc.close()
-
-    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' and
-        'uid'.
-        """
-        self._chk_state()
-        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
-            sieve_col = 'sieve'
-        else:
-            sieve_col = 'managesieve'
-        sql = 'SELECT name, smtp, pop3, imap, %s FROM users WHERE uid = %d' % \
-            (sieve_col, self._uid)
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        info = dbc.fetchone()
-        dbc.close()
-        if info:
-            keys = ('name', 'smtp', 'pop3', 'imap', sieve_col)
-            info = dict(zip(keys, info))
-            for service in keys[1:]:
-                if info[service]:
-                    # TP: A service (pop3/imap) is enabled/usable for a user
-                    info[service] = _('enabled')
-                else:
-                    # TP: A service (pop3/imap) isn't enabled/usable for a user
-                    info[service] = _('disabled')
-            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
-            info['transport'] = self._transport.transport
-            info['uid'] = self._uid
-            return info
-        # nearly impossible‽
-        raise AErr(_(u"Couldn't fetch information for account: '%s'") %
-                   self._addr, NO_SUCH_ACCOUNT)
-
-    def get_aliases(self):
-        """Return a list with all alias e-mail addresses, whose destination
-        is the address of the Account."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT address ||'@'|| domainname FROM alias, "
-                    "domain_name WHERE destination = %s AND domain_name.gid = "
-                    "alias.gid AND domain_name.is_primary ORDER BY address",
-                    str(self._addr))
-        addresses = dbc.fetchall()
-        dbc.close()
-        aliases = []
-        if addresses:
-            aliases = [alias[0] for alias in addresses]
-        return aliases
-
-    def delete(self, delalias=False):
-        """Delete the Account from the database.
-
-        Argument:
-
-        `delalias` : bool
-          if *delalias* is `True`, all aliases, which points to the Account,
-          will be also deleted.  If there are aliases and *delalias* is
-          `False`, an AccountError will be raised.
-        """
-        assert isinstance(delalias, bool)
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        if delalias:
-            dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
-            # delete also all aliases where the destination address is the same
-            # as for this account.
-            dbc.execute("DELETE FROM alias WHERE destination = %s",
-                        str(self._addr))
-            self._dbh.commit()
-        else:  # check first for aliases
-            a_count = self._count_aliases()
-            if a_count > 0:
-                dbc.close()
-                raise AErr(_(u"There are %(count)d aliases with the "
-                             u"destination address '%(address)s'.") %
-                           {'count': a_count, 'address': self._addr},
-                           ALIAS_PRESENT)
-            dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
-            self._dbh.commit()
-        dbc.close()
-        self._new = True
-        self._uid = 0
-        self._addr = self._dbh = self._domain = self._passwd = None
-        self._mail = self._transport = None
-
-
-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` : long
-      The Account unique ID.
-    `dbh` : pyPgSQL.PgSQL.Connection
-      a database connection for the database access.
-    """
-    try:
-        uid = long(uid)
-    except ValueError:
-        raise AErr(_(u'UID must be an int/long.'), INVALID_ARGUMENT)
-    if uid < 1:
-        raise AErr(_(u'UID must be greater than 0.'), INVALID_ARGUMENT)
-    dbc = dbh.cursor()
-    dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address, "
-                "uid, users.gid FROM users LEFT JOIN domain_name ON "
-                "(domain_name.gid = users.gid AND is_primary) WHERE uid = %s",
-                uid)
-    info = dbc.fetchone()
-    dbc.close()
-    if not info:
-        raise AErr(_(u"There is no account with the UID '%d'.") % uid,
-                   NO_SUCH_ACCOUNT)
-    info = dict(zip(('address', 'uid', 'gid'), info))
-    return info
-
-del _, cfg_dget
--- a/VirtualMailManager/Alias.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Alias
-
-    Virtual Mail Manager's Alias class to manage e-mail aliases.
-"""
-
-from VirtualMailManager.Domain import get_gid
-from VirtualMailManager.EmailAddress import EmailAddress
-from VirtualMailManager.errors import AliasError as AErr
-from VirtualMailManager.ext.Postconf import Postconf
-from VirtualMailManager.pycompat import all
-from VirtualMailManager.constants import \
-     ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
-
-
-_ = lambda msg: msg
-cfg_dget = lambda option: None
-
-
-class Alias(object):
-    """Class to manage e-mail aliases."""
-    __slots__ = ('_addr', '_dests', '_gid', '_dbh')
-
-    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(_(u"The domain '%s' doesn't exist.") %
-                       self._addr.domainname, NO_SUCH_DOMAIN)
-        self._dests = []
-
-        self.__load_dests()
-
-    def __load_dests(self):
-        """Loads all known destination addresses into the _dests list."""
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT destination FROM alias WHERE gid = %s AND '
-                    'address = %s', self._gid, self._addr.localpart)
-        dests = dbc.fetchall()
-        if dbc.rowcount > 0:
-            self._dests.extend(EmailAddress(dest[0]) for dest in dests)
-        dbc.close()
-
-    def __check_expansion(self, count_new):
-        """Checks the current expansion limit of the alias."""
-        postconf = Postconf(cfg_dget('bin.postconf'))
-        limit = long(postconf.read('virtual_alias_expansion_limit'))
-        dcount = len(self._dests)
-        failed = False
-        if dcount == limit or dcount + count_new > limit:
-            failed = True
-            errmsg = _(
-u"""Can't add %(count_new)i new destination(s) to alias '%(address)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 = _(
-u"""Can't add %(count_new)i new destination(s) to alias '%(address)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 % {'address': self._addr, 'count': dcount,
-                                 'limit': limit, 'count_new': count_new},
-                       ALIAS_EXCEEDS_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
-        its destination addresses will be deleted.
-
-        """
-        dbc = self._dbh.cursor()
-        if not destination:
-            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)
-
-    @property
-    def address(self):
-        """The Alias' EmailAddress instance."""
-        return self._addr
-
-    def add_destinations(self, destinations, warnings=None):
-        """Adds the `EmailAddress`es from *destinations* list to the
-        destinations of the alias.
-
-        Destinations, that are already assigned to the alias, will be
-        removed from *destinations*.  When done, this method will return
-        a set with all destinations, that were saved in the database.
-        """
-        destinations = set(destinations)
-        assert destinations and \
-                all(isinstance(dest, EmailAddress) for dest in destinations)
-        if not warnings is None:
-            assert isinstance(warnings, list)
-        if self._addr in destinations:
-            destinations.remove(self._addr)
-            if not warnings is None:
-                warnings.append(self._addr)
-        duplicates = destinations.intersection(set(self._dests))
-        if duplicates:
-            destinations.difference_update(set(self._dests))
-            if not warnings is None:
-                warnings.extend(duplicates)
-        if not destinations:
-            return destinations
-        self.__check_expansion(len(destinations))
-        dbc = self._dbh.cursor()
-        dbc.executemany("INSERT INTO alias VALUES (%d, '%s', %%s)" %
-                        (self._gid, self._addr.localpart),
-                        (str(destination) for destination in destinations))
-        self._dbh.commit()
-        dbc.close()
-        self._dests.extend(destinations)
-        return destinations
-
-    def del_destination(self, destination):
-        """Deletes the specified ``destination`` address from the alias."""
-        assert isinstance(destination, EmailAddress)
-        if not self._dests:
-            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
-                       NO_SUCH_ALIAS)
-        if not destination in self._dests:
-            raise AErr(_(u"The address '%(addr)s' isn't a destination of "
-                         u"the alias '%(alias)s'.") % {'addr': self._addr,
-                       'alias': destination}, NO_SUCH_ALIAS)
-        self.__delete(destination)
-        self._dests.remove(destination)
-
-    def get_destinations(self):
-        """Returns an iterator for all destinations of the alias."""
-        if not self._dests:
-            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
-                       NO_SUCH_ALIAS)
-        return iter(self._dests)
-
-    def delete(self):
-        """Deletes the alias with all its destinations."""
-        if not self._dests:
-            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
-                       NO_SUCH_ALIAS)
-        self.__delete()
-        del self._dests[:]
-
-
-del _, cfg_dget
--- a/VirtualMailManager/AliasDomain.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.AliasDomain
-
-    Virtual Mail Manager's AliasDomain class to manage alias domains.
-"""
-
-from VirtualMailManager.Domain import Domain, check_domainname
-from VirtualMailManager.constants import \
-     ALIASDOMAIN_EXISTS, ALIASDOMAIN_ISDOMAIN, ALIASDOMAIN_NO_DOMDEST, \
-     NO_SUCH_ALIASDOMAIN, NO_SUCH_DOMAIN
-from VirtualMailManager.errors import AliasDomainError as ADErr
-
-
-_ = lambda msg: msg
-
-
-class AliasDomain(object):
-    """Class to manage e-mail alias domains."""
-    __slots__ = ('_gid', '_name', '_domain', '_dbh')
-
-    def __init__(self, dbh, domainname):
-        """Creates a new AliasDomain instance.
-
-        Arguments:
-
-        `dbh` : pyPgSQL.PgSQL.Connection
-          a database connection for the database access
-        `domainname` : basestring
-          the name of the AliasDomain"""
-        self._dbh = dbh
-        self._name = check_domainname(domainname)
-        self._gid = 0
-        self._domain = None
-        self._load()
-
-    def _load(self):
-        """Loads the AliasDomain's GID from the database and checks if the
-        domain name is marked as primary."""
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT gid, is_primary FROM domain_name WHERE '
-                    'domainname = %s', self._name)
-        result = dbc.fetchone()
-        dbc.close()
-        if result:
-            if result[1]:
-                raise ADErr(_(u"The domain '%s' is a primary domain.") %
-                            self._name, ALIASDOMAIN_ISDOMAIN)
-            self._gid = result[0]
-
-    def set_destination(self, dest_domain):
-        """Set the destination of a new AliasDomain or updates the
-        destination of an existing AliasDomain.
-
-        Argument:
-
-        `dest_domain` : VirtualMailManager.Domain.Domain
-          the AliasDomain's destination domain
-        """
-        assert isinstance(dest_domain, Domain)
-        self._domain = dest_domain
-
-    def save(self):
-        """Stores information about the new AliasDomain in the database."""
-        if self._gid > 0:
-            raise ADErr(_(u"The alias domain '%s' already exists.") %
-                        self._name, ALIASDOMAIN_EXISTS)
-        if not self._domain:
-            raise ADErr(_(u'No destination domain set for the alias domain.'),
-                        ALIASDOMAIN_NO_DOMDEST)
-        if self._domain.gid < 1:
-            raise ADErr(_(u"The target domain '%s' doesn't exist.") %
-                        self._domain.name, NO_SUCH_DOMAIN)
-        dbc = self._dbh.cursor()
-        dbc.execute('INSERT INTO domain_name VALUES (%s, %s, FALSE)',
-                    self._name, self._domain.gid)
-        self._dbh.commit()
-        dbc.close()
-        self._gid = self._domain.gid
-
-    def info(self):
-        """Returns a dict (keys: "alias" and "domain") with the names of the
-        AliasDomain and its primary domain."""
-        if self._gid < 1:
-            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
-                        self._name, NO_SUCH_ALIASDOMAIN)
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
-                    'is_primary', self._gid)
-        domain = dbc.fetchone()
-        dbc.close()
-        if domain:
-            return {'alias': self._name, 'domain': domain[0]}
-        else:  # an almost unlikely case, isn't it?
-            raise ADErr(_(u'There is no primary domain for the alias domain '
-                          u"'%s'.") % self._name, NO_SUCH_DOMAIN)
-
-    def switch(self):
-        """Switch the destination of the AliasDomain to the new destination,
-        set with the method `set_destination()`.
-        """
-        if not self._domain:
-            raise ADErr(_(u'No destination domain set for the alias domain.'),
-                        ALIASDOMAIN_NO_DOMDEST)
-        if self._domain.gid < 1:
-            raise ADErr(_(u"The target domain '%s' doesn't exist.") %
-                        self._domain.name, NO_SUCH_DOMAIN)
-        if self._gid < 1:
-            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
-                        self._name, NO_SUCH_ALIASDOMAIN)
-        if self._gid == self._domain.gid:
-            raise ADErr(_(u"The alias domain '%(alias)s' is already assigned "
-                          u"to the domain '%(domain)s'.") %
-                        {'alias': self._name, 'domain': self._domain.name},
-                        ALIASDOMAIN_EXISTS)
-        dbc = self._dbh.cursor()
-        dbc.execute('UPDATE domain_name SET gid = %s WHERE gid = %s AND '
-                    'domainname = %s AND NOT is_primary', self._domain.gid,
-                    self._gid, self._name)
-        self._dbh.commit()
-        dbc.close()
-        self._gid = self._domain.gid
-
-    def delete(self):
-        """Delete the AliasDomain's record form the database.
-
-        Raises an AliasDomainError if the AliasDomain doesn't exist.
-        """
-        if self._gid < 1:
-            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
-                        self._name, NO_SUCH_ALIASDOMAIN)
-        dbc = self._dbh.cursor()
-        dbc.execute('DELETE FROM domain_name WHERE domainname = %s AND NOT '
-                    'is_primary', self._name)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-            self._gid = 0
-        dbc.close()
-
-
-del _
--- a/VirtualMailManager/Config.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,452 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Config
-    ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    VMM's configuration module for simplified configuration access.
-"""
-
-import re
-
-from ConfigParser import \
-     Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
-     ParsingError, RawConfigParser
-from cStringIO import StringIO# TODO: move interactive stff to cli
-
-from VirtualMailManager.common import exec_ok, get_unicode, is_dir, version_hex
-from VirtualMailManager.constants import CONF_ERROR
-from VirtualMailManager.errors import ConfigError, VMMError
-from VirtualMailManager.maillocation import known_format
-from VirtualMailManager.password import verify_scheme as _verify_scheme
-
-
-_ = lambda msg: msg
-
-
-class BadOptionError(Error):
-    """Raised when a option isn't in the format 'section.option'."""
-    pass
-
-
-class ConfigValueError(Error):
-    """Raised when creating or validating of new values fails."""
-    pass
-
-
-class NoDefaultError(Error):
-    """Raised when the requested option has no default value."""
-
-    def __init__(self, section, option):
-        Error.__init__(self, 'Option %r in section %r has no default value' %
-                             (option, section))
-
-
-class LazyConfig(RawConfigParser):
-    """The **lazy** derivate of the `RawConfigParser`.
-
-    There are two additional getters:
-
-    `pget()`
-      The polymorphic getter, which returns a option's value with the
-      appropriate type.
-    `dget()`
-      Like `LazyConfig.pget()`, but returns the option's default, from
-      `LazyConfig._cfg['sectionname']['optionname'].default`, if the
-      option is not configured in a ini-like configuration file.
-
-    `set()` differs from `RawConfigParser`'s `set()` method. `set()`
-    takes the `section` and `option` arguments combined to a single
-    string in the form "section.option".
-    """
-
-    def __init__(self):
-        RawConfigParser.__init__(self)
-        self._modified = False
-        # sample _cfg dict.  Create your own in your derived class.
-        self._cfg = {
-            'sectionname': {
-                'optionname': LazyConfigOption(int, 1, self.getint),
-            }
-        }
-
-    def bool_new(self, value):
-        """Converts the string `value` into a `bool` and returns it.
-
-        | '1', 'on', 'yes' and 'true' will become `True`
-        | '0', 'off', 'no' and 'false' will become `False`
-
-        Throws a `ConfigValueError` for all other values, except bools.
-        """
-        if isinstance(value, bool):
-            return value
-        if value.lower() in self._boolean_states:
-            return self._boolean_states[value.lower()]
-        else:
-            raise ConfigValueError(_(u"Not a boolean: '%s'") %
-                                   get_unicode(value))
-
-    def getboolean(self, section, option):
-        """Returns the boolean value of the option, in the given
-        section.
-
-        For a boolean True, the value must be set to '1', 'on', 'yes',
-        'true' or True. For a boolean False, the value must set to '0',
-        'off', 'no', 'false' or False.
-        If the option has another value assigned this method will raise
-        a ValueError.
-        """
-        # if the setting was modified it may be still a boolean value lets see
-        tmp = self.get(section, option)
-        if isinstance(tmp, bool):
-            return tmp
-        if not tmp.lower() in self._boolean_states:
-            raise ValueError('Not a boolean: %s' % tmp)
-        return self._boolean_states[tmp.lower()]
-
-    def _get_section_option(self, section_option):
-        """splits ``section_option`` (section.option) in two parts and
-        returns them as list ``[section, option]``, if:
-
-          * it likes the format of ``section_option``
-          * the ``section`` is known
-          * the ``option`` is known
-
-        Else one of the following exceptions will be thrown:
-
-          * `BadOptionError`
-          * `NoSectionError`
-          * `NoOptionError`
-        """
-        sect_opt = section_option.lower().split('.')
-        # TODO: cache it
-        if len(sect_opt) != 2:  # do we need a regexp to check the format?
-            raise BadOptionError(_(u"Bad format: '%s' - expected: "
-                                   u"section.option") %
-                                 get_unicode(section_option))
-        if not sect_opt[0] in self._cfg:
-            raise NoSectionError(sect_opt[0])
-        if not sect_opt[1] in self._cfg[sect_opt[0]]:
-            raise NoOptionError(sect_opt[1], sect_opt[0])
-        return sect_opt
-
-    def items(self, section):
-        """returns an iterable that returns key, value ``tuples`` from
-        the given ``section``.
-        """
-        if section in self._sections:  # check if the section was parsed
-            sect = self._sections[section]
-        elif not section in self._cfg:
-            raise NoSectionError(section)
-        else:
-            return ((k, self._cfg[section][k].default) \
-                    for k in self._cfg[section].iterkeys())
-        # still here? Get defaults and merge defaults with configured setting
-        defaults = dict((k, self._cfg[section][k].default) \
-                        for k in self._cfg[section].iterkeys())
-        defaults.update(sect)
-        if '__name__' in defaults:
-            del defaults['__name__']
-        return defaults.iteritems()
-
-    def dget(self, option):
-        """Returns the value of the `option`.
-
-        If the option could not be found in the configuration file, the
-        configured default value, from ``LazyConfig._cfg`` will be
-        returned.
-
-        Arguments:
-
-        `option` : string
-            the configuration option in the form "section.option"
-
-        Throws a `NoDefaultError`, if no default value was passed to
-        `LazyConfigOption.__init__()` for the `option`.
-        """
-        section, option = self._get_section_option(option)
-        try:
-            return self._cfg[section][option].getter(section, option)
-        except (NoSectionError, NoOptionError):
-            if not self._cfg[section][option].default is None:  # may be False
-                return self._cfg[section][option].default
-            else:
-                raise NoDefaultError(section, option)
-
-    def pget(self, option):
-        """Returns the value of the `option`."""
-        section, option = self._get_section_option(option)
-        return self._cfg[section][option].getter(section, option)
-
-    def set(self, option, value):
-        """Set the `value` of the `option`.
-
-        Throws a `ValueError` if `value` couldn't be converted using
-        `LazyConfigOption.cls`.
-        """
-        # pylint: disable=W0221
-        # @pylint: _L A Z Y_
-        section, option = self._get_section_option(option)
-        val = self._cfg[section][option].cls(value)
-        if self._cfg[section][option].validate:
-            val = self._cfg[section][option].validate(val)
-        if not RawConfigParser.has_section(self, section):
-            self.add_section(section)
-        RawConfigParser.set(self, section, option, val)
-        self._modified = True
-
-    def has_section(self, section):
-        """Checks if `section` is a known configuration section."""
-        return section.lower() in self._cfg
-
-    def has_option(self, option):
-        """Checks if the option (section.option) is a known
-        configuration option.
-        """
-        # pylint: disable=W0221
-        # @pylint: _L A Z Y_
-        try:
-            self._get_section_option(option)
-            return True
-        except(BadOptionError, NoSectionError, NoOptionError):
-            return False
-
-    def sections(self):
-        """Returns an iterator object for all configuration sections."""
-        return self._cfg.iterkeys()
-
-
-class LazyConfigOption(object):
-    """A simple container class for configuration settings.
-
-    `LazyConfigOption` instances are required by `LazyConfig` instances,
-    and instances of classes derived from `LazyConfig`, like the
-    `Config` class.
-    """
-    __slots__ = ('__cls', '__default', '__getter', '__validate')
-
-    def __init__(self, cls, default, getter, validate=None):
-        """Creates a new `LazyConfigOption` instance.
-
-        Arguments:
-
-        `cls` : type
-          The class/type of the option's value
-        `default`
-          Default value of the option. Use ``None`` if the option should
-          not have a default value.
-        `getter` : callable
-          A method's name of `RawConfigParser` and derived classes, to
-          get a option's value, e.g. `self.getint`.
-        `validate` : NoneType or a callable
-          None or any method, that takes one argument, in order to
-          check the value, when `LazyConfig.set()` is called.
-        """
-        self.__cls = cls
-        if not default is None:  # enforce the type of the default value
-            self.__default = self.__cls(default)
-        else:
-            self.__default = default
-        if not callable(getter):
-            raise TypeError('getter has to be a callable, got a %r' %
-                            getter.__class__.__name__)
-        self.__getter = getter
-        if validate and not callable(validate):
-            raise TypeError('validate has to be callable or None, got a %r' %
-                            validate.__class__.__name__)
-        self.__validate = validate
-
-    @property
-    def cls(self):
-        """The class of the option's value e.g. `str`, `unicode` or `bool`."""
-        return self.__cls
-
-    @property
-    def default(self):
-        """The option's default value, may be `None`"""
-        return self.__default
-
-    @property
-    def getter(self):
-        """The getter method or function to get the option's value"""
-        return self.__getter
-
-    @property
-    def validate(self):
-        """A method or function to validate the value"""
-        return self.__validate
-
-
-class Config(LazyConfig):
-    """This class is for reading vmm's configuration file."""
-
-    def __init__(self, filename):
-        """Creates a new Config instance
-
-        Arguments:
-
-        `filename` : str
-          path to the configuration file
-        """
-        LazyConfig.__init__(self)
-        self._cfg_filename = filename
-        self._cfg_file = None
-        self.__missing = {}
-
-        LCO = LazyConfigOption
-        bool_t = self.bool_new
-        self._cfg = {
-            'account': {
-                'delete_directory': LCO(bool_t, False, self.getboolean),
-                'directory_mode': LCO(int, 448, self.getint),
-                'disk_usage': LCO(bool_t, False, self.getboolean),
-                'password_length': LCO(int, 8, self.getint),
-                'random_password': LCO(bool_t, False, self.getboolean),
-                'imap': LCO(bool_t, True, self.getboolean),
-                'pop3': LCO(bool_t, True, self.getboolean),
-                'sieve': LCO(bool_t, True, self.getboolean),
-                'smtp': LCO(bool_t, True, self.getboolean),
-            },
-            'bin': {
-                'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
-                                 exec_ok),
-                'du': LCO(str, '/usr/bin/du', self.get, exec_ok),
-                'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
-            },
-            'database': {
-                'host': LCO(str, 'localhost', self.get),
-                'name': LCO(str, 'mailsys', self.get),
-                'pass': LCO(str, None, self.get),
-                'user': LCO(str, None, self.get),
-            },
-            'domain': {
-                'auto_postmaster': LCO(bool_t, True, self.getboolean),
-                'delete_directory': LCO(bool_t, False, self.getboolean),
-                'directory_mode': LCO(int, 504, self.getint),
-                'force_deletion': LCO(bool_t, False, self.getboolean),
-            },
-            'mailbox': {
-                'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
-                               self.unicode),
-                'format': LCO(str, 'maildir', self.get, check_mailbox_format),
-                'root': LCO(str, 'Maildir', self.unicode),
-                'subscribe': LCO(bool_t, True, self.getboolean),
-            },
-            'misc': {
-                'base_directory': LCO(str, '/srv/mail', self.get, is_dir),
-                'crypt_blowfish_rounds': LCO(int, 5, self.getint),
-                'crypt_sha256_rounds': LCO(int, 5000, self.getint),
-                'crypt_sha512_rounds': LCO(int, 5000, self.getint),
-                'dovecot_version': LCO(str, None, self.hexversion,
-                                       check_version_format),
-                'password_scheme': LCO(str, 'CRAM-MD5', self.get,
-                                       verify_scheme),
-                'transport': LCO(str, 'dovecot:', self.get),
-            },
-        }
-
-    def load(self):
-        """Loads the configuration, read only.
-
-        Raises a ConfigError if the configuration syntax is
-        invalid.
-        """
-        try:
-            self._cfg_file = open(self._cfg_filename, 'r')
-            self.readfp(self._cfg_file)
-        except (MissingSectionHeaderError, ParsingError), err:
-            raise ConfigError(str(err), CONF_ERROR)
-        finally:
-            if self._cfg_file and not self._cfg_file.closed:
-                self._cfg_file.close()
-
-    def check(self):
-        """Performs a configuration check.
-
-        Raises a ConfigError if settings w/o a default value are missed.
-        Or a ConfigValueError if 'misc.dovecot_version' has the wrong
-        format.
-        """
-        # TODO: There are only two settings w/o defaults.
-        #       So there is no need for cStringIO
-        if not self.__chk_cfg():
-            errmsg = StringIO()
-            errmsg.write(_(u'Missing options, which have no default value.\n'))
-            errmsg.write(_(u'Using configuration file: %s\n') %
-                         self._cfg_filename)
-            for section, options in self.__missing.iteritems():
-                errmsg.write(_(u'* Section: %s\n') % section)
-                for option in options:
-                    errmsg.write((u'    %s\n') % option)
-            raise ConfigError(errmsg.getvalue(), CONF_ERROR)
-        check_version_format(self.get('misc', 'dovecot_version'))
-
-    def hexversion(self, section, option):
-        """Converts the version number (e.g.: 1.2.3) from the *option*'s
-        value to an int."""
-        return version_hex(self.get(section, option))
-
-    def unicode(self, section, option):
-        """Returns the value of the `option` from `section`, converted
-        to Unicode."""
-        return get_unicode(self.get(section, option))
-
-    def __chk_cfg(self):
-        """Checks all section's options for settings w/o a default
-        value.
-
-        Returns `True` if everything is fine, else `False`.
-        """
-        errors = False
-        for section in self._cfg.iterkeys():
-            missing = []
-            for option, value in self._cfg[section].iteritems():
-                if (value.default is None and
-                    not RawConfigParser.has_option(self, section, option)):
-                    missing.append(option)
-                    errors = True
-            if missing:
-                self.__missing[section] = missing
-        return not errors
-
-
-def check_mailbox_format(format):
-    """
-    Check if the mailbox format *format* is supported.  When the *format*
-    is supported it will be returned, otherwise a `ConfigValueError` will
-    be raised.
-    """
-    format = format.lower()
-    if known_format(format):
-        return format
-    raise ConfigValueError(_(u"Unsupported mailbox format: '%s'") %
-                           get_unicode(format))
-
-
-def check_version_format(version_string):
-    """Check if the *version_string* has the proper format, e.g.: '1.2.3'.
-    Returns the validated version string if it has the expected format.
-    Otherwise a `ConfigValueError` will be raised.
-    """
-    version_re = r'^\d+\.\d+\.(?:\d+|(?:alpha|beta|rc)\d+)$'
-    if not re.match(version_re, version_string):
-        raise ConfigValueError(_(u"Not a valid Dovecot version: '%s'") %
-                               get_unicode(version_string))
-    return version_string
-
-
-def verify_scheme(scheme):
-    """Checks if the password scheme *scheme* can be accepted and returns
-    the verified scheme.
-    """
-    try:
-        scheme, encoding = _verify_scheme(scheme)
-    except VMMError, err:  # 'cast' it
-        raise ConfigValueError(err.msg)
-    if not encoding:
-        return scheme
-    return '%s.%s' % (scheme, encoding)
-
-del _
--- a/VirtualMailManager/Domain.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,409 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Domain
-
-    Virtual Mail Manager's Domain class to manage e-mail domains.
-"""
-
-import os
-import re
-from random import choice
-
-from VirtualMailManager.constants import \
-     ACCOUNT_AND_ALIAS_PRESENT, ACCOUNT_PRESENT, ALIAS_PRESENT, \
-     DOMAIN_ALIAS_EXISTS, DOMAIN_EXISTS, DOMAIN_INVALID, DOMAIN_TOO_LONG, \
-     NO_SUCH_DOMAIN
-from VirtualMailManager.errors import DomainError as DomErr
-from VirtualMailManager.Transport import Transport
-
-
-MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
-RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$")
-_ = lambda msg: msg
-
-
-class Domain(object):
-    """Class to manage e-mail domains."""
-    __slots__ = ('_directory', '_gid', '_name', '_transport', '_dbh', '_new')
-
-    def __init__(self, dbh, domainname):
-        """Creates a new Domain instance.
-
-        Loads all relevant data from the database, if the domain could be
-        found.  To create a new domain call the methods set_directory() and
-        set_transport() before save().
-
-        A DomainError will be thrown when the *domainname* is the name of
-        an alias domain.
-
-        Arguments:
-
-        `dbh` : pyPgSQL.PgSQL.Connection
-          a database connection for the database access
-        `domainname` : basestring
-          The name of the domain
-        """
-        self._name = check_domainname(domainname)
-        self._dbh = dbh
-        self._gid = 0
-        self._transport = None
-        self._directory = None
-        self._new = True
-        self._load()
-
-    def _load(self):
-        """Load information from the database and checks if the domain name
-        is the primary one.
-
-        Raises a DomainError if Domain._name isn't the primary name of the
-        domain.
-        """
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT dd.gid, tid, domaindir, is_primary FROM '
-                    'domain_data dd, domain_name dn WHERE domainname = %s AND '
-                    'dn.gid = dd.gid', self._name)
-        result = dbc.fetchone()
-        dbc.close()
-        if result:
-            if not result[3]:
-                raise DomErr(_(u"The domain '%s' is an alias domain.") %
-                             self._name, DOMAIN_ALIAS_EXISTS)
-            self._gid, self._directory = result[0], result[2]
-            self._transport = Transport(self._dbh, tid=result[1])
-            self._new = False
-
-    def _set_gid(self):
-        """Sets the ID of the domain - if not set yet."""
-        assert self._gid == 0
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('domain_gid')")
-        self._gid = dbc.fetchone()[0]
-        dbc.close()
-
-    def _has(self, what):
-        """Checks if aliases or accounts are assigned to the domain.
-
-        If there are assigned accounts or aliases True will be returned,
-        otherwise False will be returned.
-
-        Argument:
-
-        `what` : basestring
-            "alias" or "users"
-        """
-        assert what in ('alias', 'users')
-        dbc = self._dbh.cursor()
-        if what == 'users':
-            dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._gid)
-        else:
-            dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._gid)
-        count = dbc.fetchone()
-        dbc.close()
-        return count[0] > 0
-
-    def _chk_delete(self, deluser, delalias):
-        """Checks dependencies for deletion.
-
-        Arguments:
-        deluser -- ignore available accounts (bool)
-        delalias -- ignore available aliases (bool)
-        """
-        if not deluser:
-            hasuser = self._has('users')
-        else:
-            hasuser = False
-        if not delalias:
-            hasalias = self._has('alias')
-        else:
-            hasalias = False
-        if hasuser and hasalias:
-            raise DomErr(_(u'There are accounts and aliases.'),
-                         ACCOUNT_AND_ALIAS_PRESENT)
-        elif hasuser:
-            raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT)
-        elif hasalias:
-            raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT)
-
-    def _chk_state(self):
-        """Throws a DomainError if the Domain is new - not saved in the
-        database."""
-        if self._new:
-            raise DomErr(_(u"The domain '%s' doesn't exist.") % self._name,
-                         NO_SUCH_DOMAIN)
-
-    @property
-    def gid(self):
-        """The GID of the Domain."""
-        return self._gid
-
-    @property
-    def name(self):
-        """The Domain's name."""
-        return self._name
-
-    @property
-    def directory(self):
-        """The Domain's directory."""
-        return self._directory
-
-    def set_directory(self, basedir):
-        """Set the path value of the Domain's directory, inside *basedir*.
-
-        Argument:
-
-        `basedir` : basestring
-          The base directory of all domains
-        """
-        assert self._new and self._directory is None
-        self._set_gid()
-        self._directory = os.path.join(basedir, choice(MAILDIR_CHARS),
-                                       str(self._gid))
-
-    @property
-    def transport(self):
-        """The Domain's transport."""
-        return self._transport
-
-    def set_transport(self, transport):
-        """Set the transport for the new Domain.
-
-        Argument:
-
-        `transport` : VirtualMailManager.Transport
-          The transport of the new Domain
-        """
-        assert self._new and isinstance(transport, Transport)
-        self._transport = transport
-
-    def save(self):
-        """Stores the new domain in the database."""
-        if not self._new:
-            raise DomErr(_(u"The domain '%s' already exists.") % self._name,
-                         DOMAIN_EXISTS)
-        assert self._directory is not None and self._transport is not None
-        dbc = self._dbh.cursor()
-        dbc.execute("INSERT INTO domain_data VALUES (%s, %s, %s)", self._gid,
-                    self._transport.tid, self._directory)
-        dbc.execute("INSERT INTO domain_name VALUES (%s, %s, %s)", self._name,
-                    self._gid, True)
-        self._dbh.commit()
-        dbc.close()
-        self._new = False
-
-    def delete(self, deluser=False, delalias=False):
-        """Deletes the domain.
-
-        Arguments:
-
-        `deluser` : bool
-          force deletion of all available accounts, default `False`
-        `delalias` : bool
-          force deletion of all available aliases, default `False`
-        """
-        self._chk_state()
-        self._chk_delete(deluser, delalias)
-        dbc = self._dbh.cursor()
-        for tbl in ('alias', 'users', 'relocated', 'domain_name',
-                    'domain_data'):
-            dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._gid))
-        self._dbh.commit()
-        dbc.close()
-        self._gid = 0
-        self._directory = self._transport = None
-        self._new = True
-
-    def update_transport(self, transport, force=False):
-        """Sets a new transport for the Domain.
-
-        If *force* is `True` the new *transport* will be assigned to all
-        existing accounts.  Otherwise the *transport* will be only used for
-        accounts created from now on.
-
-        Arguments:
-
-        `transport` : VirtualMailManager.Transport
-          the new transport
-        `force` : bool
-          enforce new transport setting for all accounts, default `False`
-        """
-        self._chk_state()
-        assert isinstance(transport, Transport)
-        if transport == self._transport:
-            return
-        dbc = self._dbh.cursor()
-        dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s",
-                    transport.tid, self._gid)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        if force:
-            dbc.execute("UPDATE users SET tid = %s WHERE gid = %s",
-                        transport.tid, self._gid)
-            if dbc.rowcount > 0:
-                self._dbh.commit()
-        dbc.close()
-        self._transport = transport
-
-    def get_info(self):
-        """Returns a dictionary with information about the domain."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT gid, domainname, transport, domaindir, '
-                    'aliasdomains accounts, aliases, relocated FROM '
-                    'vmm_domain_info WHERE gid = %s', self._gid)
-        info = dbc.fetchone()
-        dbc.close()
-        keys = ('gid', 'domainname', 'transport', 'domaindir', 'aliasdomains',
-                'accounts', 'aliases', 'relocated')
-        return dict(zip(keys, info))
-
-    def get_accounts(self):
-        """Returns a list with all accounts of the domain."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT local_part from users where gid = %s ORDER BY '
-                    'local_part', self._gid)
-        users = dbc.fetchall()
-        dbc.close()
-        accounts = []
-        if users:
-            addr = u'@'.join
-            _dom = self._name
-            accounts = [addr((account[0], _dom)) for account in users]
-        return accounts
-
-    def get_aliases(self):
-        """Returns a list with all aliases e-mail addresses of the domain."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT DISTINCT address FROM alias WHERE gid = %s ORDER '
-                    'BY address', self._gid)
-        addresses = dbc.fetchall()
-        dbc.close()
-        aliases = []
-        if addresses:
-            addr = u'@'.join
-            _dom = self._name
-            aliases = [addr((alias[0], _dom)) for alias in addresses]
-        return aliases
-
-    def get_relocated(self):
-        """Returns a list with all addresses of relocated users."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT address FROM relocated WHERE gid = %s ORDER BY '
-                    'address', self._gid)
-        addresses = dbc.fetchall()
-        dbc.close()
-        relocated = []
-        if addresses:
-            addr = u'@'.join
-            _dom = self._name
-            relocated = [addr((address[0], _dom)) for address in addresses]
-        return relocated
-
-    def get_aliase_names(self):
-        """Returns a list with all alias domain names of the domain."""
-        self._chk_state()
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
-                    'NOT is_primary ORDER BY domainname', self._gid)
-        anames = dbc.fetchall()
-        dbc.close()
-        aliasdomains = []
-        if anames:
-            aliasdomains = [aname[0] for aname in anames]
-        return aliasdomains
-
-
-def check_domainname(domainname):
-    """Returns the validated domain name `domainname`.
-
-    Throws an `DomainError`, if the domain name is too long or doesn't
-    look like a valid domain name (label.label.label).
-
-    """
-    if not RE_DOMAIN.match(domainname):
-        domainname = domainname.encode('idna')
-    if len(domainname) > 255:
-        raise DomErr(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
-    if not RE_DOMAIN.match(domainname):
-        raise DomErr(_(u"The domain name '%s' is invalid") % domainname,
-                     DOMAIN_INVALID)
-    return domainname
-
-
-def get_gid(dbh, domainname):
-    """Returns the group id of the domain *domainname*.
-
-    If the domain couldn't be found in the database 0 will be returned.
-    """
-    domainname = check_domainname(domainname)
-    dbc = dbh.cursor()
-    dbc.execute('SELECT gid FROM domain_name WHERE domainname=%s', domainname)
-    gid = dbc.fetchone()
-    dbc.close()
-    if gid:
-        return gid[0]
-    return 0
-
-
-def search(dbh, pattern=None, like=False):
-    """'Search' for domains by *pattern* in the database.
-
-    *pattern* may be a domain name or a partial domain name - starting
-    and/or ending with a '%' sign.  When the *pattern* starts or ends with
-    a '%' sign *like* has to be `True` to perform a wildcard search.
-    To retrieve all available domains use the arguments' default values.
-
-    This function returns a tuple with a list and a dict: (order, domains).
-    The order list contains the domains' gid, alphabetical sorted by the
-    primary domain name.  The domains dict's keys are the gids of the
-    domains. The value of item is a list.  The first list element contains
-    the primary domain name or `None`.  The elements [1:] contains the
-    names of alias domains.
-
-    Arguments:
-
-    `pattern` : basestring
-      a (partial) domain name (starting and/or ending with a "%" sign)
-    `like` : bool
-      should be `True` when *pattern* starts/ends with a "%" sign
-    """
-    if pattern and not like:
-        pattern = check_domainname(pattern)
-    sql = 'SELECT gid, domainname, is_primary FROM domain_name'
-    if pattern:
-        if like:
-            sql += " WHERE domainname LIKE '%s'" % pattern
-        else:
-            sql += " WHERE domainname = '%s'" % pattern
-    sql += ' ORDER BY is_primary DESC, domainname'
-    dbc = dbh.cursor()
-    dbc.execute(sql)
-    result = dbc.fetchall()
-    dbc.close()
-
-    gids = [domain[0] for domain in result if domain[2]]
-    domains = {}
-    for gid, domain, is_primary in result:
-        if is_primary:
-            if not gid in domains:
-                domains[gid] = [domain]
-            else:
-                domains[gid].insert(0, domain)
-        else:
-            if gid in gids:
-                if gid in domains:
-                    domains[gid].append(domain)
-                else:
-                    domains[gid] = [domain]
-            else:
-                gids.append(gid)
-                domains[gid] = [None, domain]
-    return gids, domains
-
-
-del _
--- a/VirtualMailManager/EmailAddress.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.EmailAddress
-
-    Virtual Mail Manager's EmailAddress class to handle e-mail addresses.
-"""
-import re
-
-from VirtualMailManager.Domain import check_domainname
-from VirtualMailManager.constants import \
-     DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG
-from VirtualMailManager.errors import EmailAddressError as EAErr
-
-
-RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
-_ = lambda msg: msg
-
-
-class EmailAddress(object):
-    """Simple class for validated e-mail addresses."""
-    __slots__ = ('_localpart', '_domainname')
-
-    def __init__(self, address):
-        """Creates a new instance from the string/unicode ``address``."""
-        assert isinstance(address, basestring)
-        self._localpart = None
-        self._domainname = None
-        self._chk_address(address)
-
-    @property
-    def localpart(self):
-        """The local-part of the address *local-part@domain*"""
-        return self._localpart
-
-    @property
-    def domainname(self):
-        """The domain part of the address *local-part@domain*"""
-        return self._domainname
-
-    def __eq__(self, other):
-        if isinstance(other, self.__class__):
-            return self._localpart == other.localpart and \
-                    self._domainname == other.domainname
-        return NotImplemented
-
-    def __ne__(self, other):
-        if isinstance(other, self.__class__):
-            return self._localpart != other.localpart or \
-                    self._domainname != other.domainname
-        return NotImplemented
-
-    def __hash__(self):
-        return hash((self._localpart.lower(), self._domainname.lower()))
-
-    def __repr__(self):
-        return "EmailAddress('%s@%s')" % (self._localpart, self._domainname)
-
-    def __str__(self):
-        return '%s@%s' % (self._localpart, self._domainname)
-
-    def _chk_address(self, address):
-        """Checks if the string ``address`` could be used for an e-mail
-        address.  If so, it will assign the corresponding values to the
-        attributes `_localpart` and `_domainname`."""
-        parts = address.split('@')
-        p_len = len(parts)
-        if p_len < 2:
-            raise EAErr(_(u"Missing the '@' sign in address %r") % address,
-                        INVALID_ADDRESS)
-        elif p_len > 2:
-            raise EAErr(_(u"Too many '@' signs in address %r") % address,
-                        INVALID_ADDRESS)
-        if not parts[0]:
-            raise EAErr(_(u'Missing local-part in address %r') % address,
-                        LOCALPART_INVALID)
-        if not parts[1]:
-            raise EAErr(_(u'Missing domain name in address %r') % address,
-                        DOMAIN_NO_NAME)
-        self._localpart = check_localpart(parts[0])
-        self._domainname = check_domainname(parts[1])
-
-
-def check_localpart(localpart):
-    """Returns the validated local-part `localpart`.
-
-    Throws a `EmailAddressError` if the local-part is too long or contains
-    invalid characters.
-    """
-    if len(localpart) > 64:
-        raise EAErr(_(u"The local-part '%s' is too long") % localpart,
-                    LOCALPART_TOO_LONG)
-    invalid_chars = set(RE_LOCALPART.findall(localpart))
-    if invalid_chars:
-        i_chars = u''.join((u'"%s" ' % c for c in invalid_chars))
-        raise EAErr(_(u"The local-part '%(l_part)s' contains invalid "
-                      u"characters: %(i_chars)s") % {'l_part': localpart,
-                    'i_chars': i_chars}, LOCALPART_INVALID)
-    return localpart
-
-
-del _
--- a/VirtualMailManager/Handler.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,657 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-   VirtualMailManager.Handler
-
-   A wrapper class. It wraps round all other classes and does some
-   dependencies checks.
-
-   Additionally it communicates with the PostgreSQL database, creates
-   or deletes directories of domains or users.
-"""
-
-import os
-import re
-
-from shutil import rmtree
-from subprocess import Popen, PIPE
-
-from pyPgSQL import PgSQL  # python-pgsql - http://pypgsql.sourceforge.net
-
-from VirtualMailManager.Account import Account
-from VirtualMailManager.Alias import Alias
-from VirtualMailManager.AliasDomain import AliasDomain
-from VirtualMailManager.common import exec_ok
-from VirtualMailManager.Config import Config as Cfg
-from VirtualMailManager.constants import \
-     ACCOUNT_EXISTS, ALIAS_EXISTS, CONF_NOFILE, CONF_NOPERM, CONF_WRONGPERM, \
-     DATABASE_ERROR, DOMAINDIR_GROUP_MISMATCH, DOMAIN_INVALID, \
-     FOUND_DOTS_IN_PATH, INVALID_ARGUMENT, MAILDIR_PERM_MISMATCH, \
-     NOT_EXECUTABLE, NO_SUCH_ACCOUNT, NO_SUCH_ALIAS, NO_SUCH_BINARY, \
-     NO_SUCH_DIRECTORY, NO_SUCH_RELOCATED, RELOCATED_EXISTS
-from VirtualMailManager.Domain import Domain, get_gid
-from VirtualMailManager.EmailAddress import EmailAddress
-from VirtualMailManager.errors import \
-     DomainError, NotRootError, PermissionError, VMMError
-from VirtualMailManager.mailbox import new as new_mailbox
-from VirtualMailManager.pycompat import any
-from VirtualMailManager.Relocated import Relocated
-from VirtualMailManager.Transport import Transport
-
-
-_ = lambda msg: msg
-
-CFG_FILE = 'vmm.cfg'
-CFG_PATH = '/root:/usr/local/etc:/etc'
-RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
-TYPE_ACCOUNT = 0x1
-TYPE_ALIAS = 0x2
-TYPE_RELOCATED = 0x4
-OTHER_TYPES = {
-    TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
-    TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
-    TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
-}
-
-
-class Handler(object):
-    """Wrapper class to simplify the access on all the stuff from
-    VirtualMailManager"""
-    __slots__ = ('_cfg', '_cfg_fname', '_dbh', '_warnings')
-
-    def __init__(self, skip_some_checks=False):
-        """Creates a new Handler instance.
-
-        ``skip_some_checks`` : bool
-            When a derived class knows how to handle all checks this
-            argument may be ``True``. By default it is ``False`` and
-            all checks will be performed.
-
-        Throws a NotRootError if your uid is greater 0.
-        """
-        self._cfg_fname = ''
-        self._warnings = []
-        self._cfg = None
-        self._dbh = None
-
-        if os.geteuid():
-            raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
-                               CONF_NOPERM)
-        if self._check_cfg_file():
-            self._cfg = Cfg(self._cfg_fname)
-            self._cfg.load()
-        if not skip_some_checks:
-            self._cfg.check()
-            self._chkenv()
-
-    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(_(u"Could not find '%(cfg_file)s' in: "
-                             u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
-                           'cfg_path': CFG_PATH}, CONF_NOFILE)
-
-    def _check_cfg_file(self):
-        """Checks the configuration file, returns bool"""
-        self._find_cfg_file()
-        fstat = os.stat(self._cfg_fname)
-        fmode = int(oct(fstat.st_mode & 0777))
-        if fmode % 100 and fstat.st_uid != fstat.st_gid or \
-           fmode % 10 and fstat.st_uid == fstat.st_gid:
-            raise PermissionError(_(u"wrong permissions for '%(file)s': "
-                                    u"%(perms)s\n`chmod 0600 %(file)s` would "
-                                    u"be great.") % {'file': self._cfg_fname,
-                                  'perms': fmode}, CONF_WRONGPERM)
-        else:
-            return True
-
-    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"""
-        basedir = self._cfg.dget('misc.base_directory')
-        if not os.path.exists(basedir):
-            old_umask = os.umask(0006)
-            os.makedirs(basedir, 0771)
-            os.chown(basedir, 0, 0)
-            os.umask(old_umask)
-        elif not os.path.isdir(basedir):
-            raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
-                             u"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, err:
-                if err.code is NO_SUCH_BINARY:
-                    raise VMMError(_(u"'%(binary)s' doesn't exist.\n"
-                                     u"(%(cfg_file)s: section 'bin', option "
-                                     u"'%(option)s')") % {'binary': val,
-                                   'cfg_file': self._cfg_fname, 'option': opt},
-                                   err.code)
-                elif err.code is NOT_EXECUTABLE:
-                    raise VMMError(_(u"'%(binary)s' is not executable.\n"
-                                     u"(%(cfg_file)s: section 'bin', option "
-                                     u"'%(option)s')") % {'binary': val,
-                                   'cfg_file': self._cfg_fname, 'option': opt},
-                                   err.code)
-                else:
-                    raise
-
-    def _db_connect(self):
-        """Creates a pyPgSQL.PgSQL.connection instance."""
-        if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
-                                  not self._dbh._isOpen):
-            try:
-                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.execute("SET NAMES 'UTF8'")
-                dbc.close()
-            except PgSQL.libpq.DatabaseError, err:
-                raise VMMError(str(err), DATABASE_ERROR)
-
-    def _chk_other_address_types(self, address, exclude):
-        """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
-        `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
-        by *exclude*.  If the *address* is known as one of the `TYPE_*`s
-        the according `TYPE_*` constant will be returned.  Otherwise 0 will
-        be returned."""
-        assert exclude in (TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED) and \
-                isinstance(address, EmailAddress)
-        if exclude is not TYPE_ACCOUNT:
-            account = Account(self._dbh, address)
-            if account:
-                return TYPE_ACCOUNT
-        if exclude is not TYPE_ALIAS:
-            alias = Alias(self._dbh, address)
-            if alias:
-                return TYPE_ALIAS
-        if exclude is not TYPE_RELOCATED:
-            relocated = Relocated(self._dbh, address)
-            if relocated:
-                return TYPE_RELOCATED
-        return 0
-
-    def _is_other_address(self, address, exclude):
-        """Checks if *address* is known for an Account (TYPE_ACCOUNT),
-        Alias (TYPE_ALIAS) or Relocated (TYPE_RELOCATED), except for
-        *exclude*.  Returns `False` if the address is not known for other
-        types.
-
-        Raises a `VMMError` if the address is known.
-        """
-        other = self._chk_other_address_types(address, exclude)
-        if not other:
-            return False
-        msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
-        raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
-                              'address': address}, OTHER_TYPES[other][1])
-
-    def _get_account(self, address):
-        """Return an Account instances for the given address (str)."""
-        address = EmailAddress(address)
-        self._db_connect()
-        return Account(self._dbh, address)
-
-    def _get_alias(self, address):
-        """Return an Alias instances for the given address (str)."""
-        address = EmailAddress(address)
-        self._db_connect()
-        return Alias(self._dbh, address)
-
-    def _get_relocated(self, address):
-        """Return a Relocated instances for the given address (str)."""
-        address = EmailAddress(address)
-        self._db_connect()
-        return Relocated(self._dbh, address)
-
-    def _get_domain(self, domainname):
-        """Return a Domain instances for the given domain name (str)."""
-        self._db_connect()
-        return Domain(self._dbh, domainname)
-
-    def _get_disk_usage(self, directory):
-        """Estimate file space usage for the given directory.
-
-        Keyword arguments:
-        directory -- the directory to summarize recursively disk usage for
-        """
-        if self._isdir(directory):
-            return Popen([self._cfg.dget('bin.du'), "-hs", directory],
-                stdout=PIPE).communicate()[0].split('\t')[0]
-        else:
-            return 0
-
-    def _isdir(self, directory):
-        """Check if `directory` is a directory. Returns bool.
-        When `directory` isn't a directory, a warning will be appended to
-        _warnings."""
-        isdir = os.path.isdir(directory)
-        if not isdir:
-            self._warnings.append(_('No such directory: %s') % directory)
-        return isdir
-
-    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:]
-        os.chdir(self._cfg.dget('misc.base_directory'))
-        if not os.path.isdir(hashdir):
-            os.mkdir(hashdir, 0711)
-            os.chown(hashdir, 0, 0)
-        os.mkdir(os.path.join(hashdir, domdir),
-                 self._cfg.dget('domain.directory_mode'))
-        os.chown(domain.directory, 0, domain.gid)
-        os.chdir(cwd)
-
-    def _make_home(self, account):
-        """Create a home directory for the new Account *account*."""
-        os.umask(0007)
-        os.chdir(account.domain_directory)
-        os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode'))
-        os.chown('%s' % account.uid, account.uid, account.gid)
-
-    def _delete_home(self, domdir, uid, gid):
-        """Delete a user's home directory."""
-        if uid > 0 and gid > 0:
-            userdir = '%s' % uid
-            if userdir.count('..') or domdir.count('..'):
-                raise VMMError(_(u'Found ".." in home directory path.'),
-                               FOUND_DOTS_IN_PATH)
-            if os.path.isdir(domdir):
-                os.chdir(domdir)
-                if os.path.isdir(userdir):
-                    mdstat = os.stat(userdir)
-                    if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
-                        raise VMMError(_(u'Detected owner/group mismatch in '
-                                         u'home directory.'),
-                                       MAILDIR_PERM_MISMATCH)
-                    rmtree(userdir, ignore_errors=True)
-                else:
-                    raise VMMError(_(u"No such directory: %s") %
-                                   os.path.join(domdir, userdir),
-                                   NO_SUCH_DIRECTORY)
-
-    def _delete_domain_dir(self, domdir, gid):
-        """Delete a domain's directory."""
-        if gid > 0:
-            if not self._isdir(domdir):
-                return
-            basedir = self._cfg.dget('misc.base_directory')
-            domdirdirs = domdir.replace(basedir + '/', '').split('/')
-            domdirparent = os.path.join(basedir, domdirdirs[0])
-            if basedir.count('..') or domdir.count('..'):
-                raise VMMError(_(u'Found ".." in domain directory path.'),
-                               FOUND_DOTS_IN_PATH)
-            if os.path.isdir(domdirparent):
-                os.chdir(domdirparent)
-                if os.lstat(domdirdirs[1]).st_gid != gid:
-                    raise VMMError(_(u'Detected group mismatch in domain '
-                                     u'directory.'), DOMAINDIR_GROUP_MISMATCH)
-                rmtree(domdirdirs[1], ignore_errors=True)
-
-    def has_warnings(self):
-        """Checks if warnings are present, returns bool."""
-        return bool(len(self._warnings))
-
-    def get_warnings(self):
-        """Returns a list with all available warnings and resets all
-        warnings.
-        """
-        ret_val = self._warnings[:]
-        del self._warnings[:]
-        return ret_val
-
-    def cfg_dget(self, option):
-        """Get the configured value of the *option* (section.option).
-        When the option was not configured its default value will be
-        returned."""
-        return self._cfg.dget(option)
-
-    def cfg_pget(self, option):
-        """Get the configured value of the *option* (section.option)."""
-        return self._cfg.pget(option)
-
-    def cfg_install(self):
-        """Installs the cfg_dget method as ``cfg_dget`` into the built-in
-        namespace."""
-        import __builtin__
-        assert 'cfg_dget' not in __builtin__.__dict__
-        __builtin__.__dict__['cfg_dget'] = self._cfg.dget
-
-    def domain_add(self, domainname, transport=None):
-        """Wrapper around Domain.set_transport() and Domain.save()"""
-        dom = self._get_domain(domainname)
-        if transport is None:
-            dom.set_transport(Transport(self._dbh,
-                              transport=self._cfg.dget('misc.transport')))
-        else:
-            dom.set_transport(Transport(self._dbh, transport=transport))
-        dom.set_directory(self._cfg.dget('misc.base_directory'))
-        dom.save()
-        self._make_domain_dir(dom)
-
-    def domain_transport(self, domainname, transport, force=None):
-        """Wrapper around Domain.update_transport()"""
-        if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
-                              INVALID_ARGUMENT)
-        dom = self._get_domain(domainname)
-        trsp = Transport(self._dbh, transport=transport)
-        if force is None:
-            dom.update_transport(trsp)
-        else:
-            dom.update_transport(trsp, force=True)
-
-    def domain_delete(self, domainname, force=None):
-        """Wrapper around Domain.delete()"""
-        if force and force not in ('deluser', 'delalias', 'delall'):
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
-                              INVALID_ARGUMENT)
-        dom = self._get_domain(domainname)
-        gid = dom.gid
-        domdir = dom.directory
-        if self._cfg.dget('domain.force_deletion') or force == 'delall':
-            dom.delete(True, True)
-        elif force == 'deluser':
-            dom.delete(deluser=True)
-        elif force == 'delalias':
-            dom.delete(delalias=True)
-        else:
-            dom.delete()
-        if self._cfg.dget('domain.delete_directory'):
-            self._delete_domain_dir(domdir, gid)
-
-    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']:
-            raise VMMError(_(u'Invalid argument: “%s”') % details,
-                           INVALID_ARGUMENT)
-        dom = self._get_domain(domainname)
-        dominfo = dom.get_info()
-        if dominfo['domainname'].startswith('xn--'):
-            dominfo['domainname'] += ' (%s)' % \
-                                     dominfo['domainname'].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())
-        else:
-            return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
-                    dom.get_aliases(), dom.get_relocated())
-
-    def aliasdomain_add(self, aliasname, domainname):
-        """Adds an alias domain to the domain.
-
-        Arguments:
-
-        `aliasname` : basestring
-          The name of the alias domain
-        `domainname` : basestring
-          The name of the target domain
-        """
-        dom = self._get_domain(domainname)
-        alias_dom = AliasDomain(self._dbh, aliasname)
-        alias_dom.set_destination(dom)
-        alias_dom.save()
-
-    def aliasdomain_info(self, aliasname):
-        """Returns a dict (keys: "alias" and "domain") with the names of
-        the alias domain and its primary domain."""
-        self._db_connect()
-        alias_dom = AliasDomain(self._dbh, aliasname)
-        return alias_dom.info()
-
-    def aliasdomain_switch(self, aliasname, domainname):
-        """Modifies the target domain of an existing alias domain.
-
-        Arguments:
-
-        `aliasname` : basestring
-          The name of the alias domain
-        `domainname` : basestring
-          The name of the new target domain
-        """
-        dom = self._get_domain(domainname)
-        alias_dom = AliasDomain(self._dbh, aliasname)
-        alias_dom.set_destination(dom)
-        alias_dom.switch()
-
-    def aliasdomain_delete(self, aliasname):
-        """Deletes the given alias domain.
-
-        Argument:
-
-        `aliasname` : basestring
-          The name of the alias domain
-        """
-        self._db_connect()
-        alias_dom = AliasDomain(self._dbh, aliasname)
-        alias_dom.delete()
-
-    def domain_list(self, pattern=None):
-        """Wrapper around function search() from module Domain."""
-        from VirtualMailManager.Domain import search
-        like = False
-        if pattern and (pattern.startswith('%') or pattern.endswith('%')):
-            like = True
-            if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
-                raise VMMError(_(u"The pattern '%s' contains invalid "
-                                 u"characters.") % pattern, DOMAIN_INVALID)
-        self._db_connect()
-        return search(self._dbh, pattern=pattern, like=like)
-
-    def user_add(self, emailaddress, password):
-        """Wrapper around Account.set_password() and Account.save()."""
-        acc = self._get_account(emailaddress)
-        acc.set_password(password)
-        acc.save()
-        oldpwd = os.getcwd()
-        self._make_home(acc)
-        mailbox = new_mailbox(acc)
-        mailbox.create()
-        folders = self._cfg.dget('mailbox.folders').split(':')
-        if any(folders):
-            bad = mailbox.add_boxes(folders,
-                                    self._cfg.dget('mailbox.subscribe'))
-            if bad:
-                self._warnings.append(_(u"Skipped mailbox folders:") +
-                                      '\n\t- ' + '\n\t- '.join(bad))
-        os.chdir(oldpwd)
-
-    def alias_add(self, aliasaddress, *targetaddresses):
-        """Creates a new `Alias` entry for the given *aliasaddress* with
-        the given *targetaddresses*."""
-        alias = self._get_alias(aliasaddress)
-        destinations = [EmailAddress(address) for address in targetaddresses]
-        warnings = []
-        destinations = alias.add_destinations(destinations, warnings)
-        if warnings:
-            self._warnings.append(_('Ignored destination addresses:'))
-            self._warnings.extend(('  * %s' % w for w in warnings))
-        for destination in destinations:
-            if get_gid(self._dbh, destination.domainname) and \
-               not self._chk_other_address_types(destination, TYPE_RELOCATED):
-                self._warnings.append(_(u"The destination account/alias '%s' "
-                                        u"doesn't exist.") % destination)
-
-    def user_delete(self, emailaddress, force=None):
-        """Wrapper around Account.delete(...)"""
-        if force not in (None, 'delalias'):
-            raise VMMError(_(u"Invalid argument: '%s'") % force,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        uid = acc.uid
-        gid = acc.gid
-        dom_dir = acc.domain_directory
-        acc_dir = acc.home
-        acc.delete(bool(force))
-        if self._cfg.dget('account.delete_directory'):
-            try:
-                self._delete_home(dom_dir, uid, gid)
-            except VMMError, err:
-                if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
-                                NO_SUCH_DIRECTORY):
-                    warning = _(u"""\
-The account has been successfully deleted from the database.
-    But an error occurred while deleting the following directory:
-    “%(directory)s”
-    Reason: %(reason)s""") % \
-                                {'directory': acc_dir, 'reason': err.msg}
-                    self._warnings.append(warning)
-                else:
-                    raise
-
-    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(_(u"The alias '%s' doesn't exist.") %
-                           alias.address, NO_SUCH_ALIAS)
-
-    def alias_delete(self, aliasaddress, targetaddress=None):
-        """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._get_alias(aliasaddress)
-        if targetaddress is None:
-            alias.delete()
-        else:
-            alias.del_destination(EmailAddress(targetaddress))
-
-    def user_info(self, emailaddress, details=None):
-        """Wrapper around Account.get_info(...)"""
-        if details not in (None, 'du', 'aliases', 'full'):
-            raise VMMError(_(u"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(_(u"The account '%s' doesn't 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
-
-    def user_by_uid(self, uid):
-        """Search for an Account by its *uid*.
-        Returns a dict (address, uid and gid) if a user could be found."""
-        from VirtualMailManager.Account import get_account_by_uid
-        self._db_connect()
-        return get_account_by_uid(uid, self._dbh)
-
-    def user_password(self, emailaddress, password):
-        """Wrapper for Account.modify('password' ...)."""
-        if not isinstance(password, basestring) or not password:
-            raise VMMError(_(u"Could not accept password: '%s'") % password,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        acc.modify('password', password)
-
-    def user_name(self, emailaddress, name):
-        """Wrapper for Account.modify('name', ...)."""
-        if not isinstance(name, basestring) or not name:
-            raise VMMError(_(u"Could not accept name: '%s'") % name,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        acc.modify('name', name)
-
-    def user_transport(self, emailaddress, transport):
-        """Wrapper for Account.modify('transport', ...)."""
-        if not isinstance(transport, basestring) or not transport:
-            raise VMMError(_(u"Could not accept transport: '%s'") % transport,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        acc.modify('transport', transport)
-
-    def user_disable(self, emailaddress, service=None):
-        """Wrapper for Account.disable(service)"""
-        if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
-            raise VMMError(_(u"Could not accept service: '%s'") % service,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        acc.disable(service)
-
-    def user_enable(self, emailaddress, service=None):
-        """Wrapper for Account.enable(service)"""
-        if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
-            raise VMMError(_(u"Could not accept service: '%s'") % service,
-                           INVALID_ARGUMENT)
-        acc = self._get_account(emailaddress)
-        if not acc:
-            raise VMMError(_(u"The account '%s' doesn't exist.") %
-                           acc.address, NO_SUCH_ACCOUNT)
-        acc.enable(service)
-
-    def relocated_add(self, emailaddress, targetaddress):
-        """Creates a new `Relocated` entry in the database. If there is
-        already a relocated user with the given *emailaddress*, only the
-        *targetaddress* for the relocated user will be updated."""
-        relocated = self._get_relocated(emailaddress)
-        relocated.set_destination(EmailAddress(targetaddress))
-
-    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(_(u"The relocated user '%s' doesn't exist.") %
-                           relocated.address, NO_SUCH_RELOCATED)
-
-    def relocated_delete(self, emailaddress):
-        """Deletes the relocated user with the given *emailaddress* from
-        the database."""
-        relocated = self._get_relocated(emailaddress)
-        relocated.delete()
-
-del _
--- a/VirtualMailManager/Relocated.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Relocated
-
-    Virtual Mail Manager's Relocated class to handle relocated users.
-"""
-
-from VirtualMailManager.Domain import get_gid
-from VirtualMailManager.EmailAddress import EmailAddress
-from VirtualMailManager.errors import RelocatedError as RErr
-from VirtualMailManager.constants import NO_SUCH_DOMAIN, \
-     NO_SUCH_RELOCATED, RELOCATED_ADDR_DEST_IDENTICAL, RELOCATED_EXISTS
-
-
-_ = lambda msg: msg
-
-
-class Relocated(object):
-    """Class to handle e-mail addresses of relocated users."""
-    __slots__ = ('_addr', '_dest', '_gid', '_dbh')
-
-    def __init__(self, dbh, address):
-        """Creates a new *Relocated* instance.  The ``address`` is the
-        old e-mail address of the user.
-
-        Use `setDestination()` to set/update the new address, where the
-        user has moved to.
-
-        """
-        assert isinstance(address, EmailAddress)
-        self._addr = address
-        self._dbh = dbh
-        self._gid = get_gid(self._dbh, self._addr.domainname)
-        if not self._gid:
-            raise RErr(_(u"The domain %r doesn't exist.") %
-                       self._addr.domainname, NO_SUCH_DOMAIN)
-        self._dest = None
-
-        self.__load()
-
-    def __nonzero__(self):
-        """Returns `True` if the Relocated is known, `False` if it's new."""
-        return self._dest is not None
-
-    def __load(self):
-        """Loads the destination address from the database into the
-        `_dest` attribute.
-
-        """
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT destination FROM relocated WHERE gid = %s AND '
-                    'address = %s', self._gid, self._addr.localpart)
-        destination = dbc.fetchone()
-        dbc.close()
-        if destination:
-            self._dest = EmailAddress(destination[0])
-
-    @property
-    def address(self):
-        """The Relocated's EmailAddress instance."""
-        return self._addr
-
-    def set_destination(self, destination):
-        """Sets/updates the new address of the relocated user."""
-        update = False
-        assert isinstance(destination, EmailAddress)
-        if self._addr == destination:
-            raise RErr(_(u'Address and destination are identical.'),
-                       RELOCATED_ADDR_DEST_IDENTICAL)
-        if self._dest:
-            if self._dest == destination:
-                raise RErr(_(u"The relocated user '%s' already exists.") %
-                           self._addr, RELOCATED_EXISTS)
-            else:
-                self._dest = destination
-                update = True
-        else:
-            self._dest = destination
-
-        dbc = self._dbh.cursor()
-        if not update:
-            dbc.execute('INSERT INTO relocated VALUES (%s, %s, %s)',
-                        self._gid, self._addr.localpart, str(self._dest))
-        else:
-            dbc.execute('UPDATE relocated SET destination = %s WHERE gid = %s '
-                        'AND address = %s', str(self._dest), self._gid,
-                        self._addr.localpart)
-        self._dbh.commit()
-        dbc.close()
-
-    def get_info(self):
-        """Returns the address to which mails should be sent."""
-        if not self._dest:
-            raise RErr(_(u"The relocated user '%s' doesn't exist.") %
-                       self._addr, NO_SUCH_RELOCATED)
-        return self._dest
-
-    def delete(self):
-        """Deletes the relocated entry from the database."""
-        if not self._dest:
-            raise RErr(_(u"The relocated user '%s' doesn't exist.") %
-                       self._addr, NO_SUCH_RELOCATED)
-        dbc = self._dbh.cursor()
-        dbc.execute('DELETE FROM relocated WHERE gid = %s AND address = %s',
-                    self._gid, self._addr.localpart)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        dbc.close()
-        self._dest = None
-
-
-del _
--- a/VirtualMailManager/Transport.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.Transport
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Virtual Mail Manager's Transport class to manage the transport for
-    domains and accounts.
-"""
-
-from VirtualMailManager.constants import UNKNOWN_TRANSPORT_ID
-from VirtualMailManager.errors import TransportError
-from VirtualMailManager.pycompat import any
-
-
-class Transport(object):
-    """A wrapper class that provides access to the transport table"""
-    __slots__ = ('_tid', '_transport', '_dbh')
-
-    def __init__(self, dbh, tid=None, transport=None):
-        """Creates a new Transport instance.
-
-        Either tid or transport must be specified. When both arguments
-        are given, tid will be used.
-
-        Keyword arguments:
-        dbh -- a pyPgSQL.PgSQL.connection
-        tid -- the id of a transport (int/long)
-        transport -- the value of the transport (str)
-
-        """
-        self._dbh = dbh
-        assert any((tid, transport))
-        if tid:
-            assert not isinstance(tid, bool) and isinstance(tid, (int, long))
-            self._tid = tid
-            self._loadByID()
-        else:
-            assert isinstance(transport, basestring)
-            self._transport = transport
-            self._loadByName()
-
-    @property
-    def tid(self):
-        """The transport's unique ID."""
-        return self._tid
-
-    @property
-    def transport(self):
-        """The transport's value, ex: 'dovecot:'"""
-        return self._transport
-
-    def __eq__(self, other):
-        if isinstance(other, self.__class__):
-            return self._tid == other.tid
-        return NotImplemented
-
-    def __ne__(self, other):
-        if isinstance(other, self.__class__):
-            return self._tid != other.tid
-        return NotImplemented
-
-    def __str__(self):
-        return self._transport
-
-    def _loadByID(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT transport FROM transport WHERE tid=%s', self._tid)
-        result = dbc.fetchone()
-        dbc.close()
-        if result:
-            self._transport = result[0]
-        else:
-            raise TransportError(_(u'Unknown tid specified.'),
-                                 UNKNOWN_TRANSPORT_ID)
-
-    def _loadByName(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT tid FROM transport WHERE transport = %s',
-                    self._transport)
-        result = dbc.fetchone()
-        dbc.close()
-        if result:
-            self._tid = result[0]
-        else:
-            self._save()
-
-    def _save(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('transport_id')")
-        self._tid = dbc.fetchone()[0]
-        dbc.execute('INSERT INTO transport VALUES (%s, %s)', self._tid,
-                    self._transport)
-        self._dbh.commit()
-        dbc.close()
--- a/VirtualMailManager/__init__.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/__init__.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,9 +1,9 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2007 - 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager
+    ~~~~~~~~~~~~~~~~~~
 
     VirtualMailManager package initialization code
 """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/account.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,420 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.account
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Account class to manage e-mail accounts.
+"""
+
+from VirtualMailManager.domain import Domain
+from VirtualMailManager.emailaddress import EmailAddress
+from VirtualMailManager.transport import Transport
+from VirtualMailManager.common import version_str
+from VirtualMailManager.constants import \
+     ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
+     INVALID_ARGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, \
+     NO_SUCH_DOMAIN, UNKNOWN_SERVICE
+from VirtualMailManager.errors import AccountError as AErr
+from VirtualMailManager.maillocation import MailLocation
+from VirtualMailManager.password import pwhash
+
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class Account(object):
+    """Class to manage e-mail accounts."""
+    __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
+                 '_transport', '_uid')
+
+    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` : pyPgSQL.PgSQL.Connection
+          A database connection for the database access.
+        `address` : VirtualMailManager.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:
+            raise AErr(_(u"The domain '%s' doesn't exist.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._uid = 0
+        self._mail = None
+        self._transport = self._domain.transport
+        self._passwd = None
+        self._new = True
+        self._load()
+
+    def __nonzero__(self):
+        """Returns `True` if the Account is known, `False` if it's new."""
+        return not self._new
+
+    def _load(self):
+        """Load 'uid', 'mid' and 'tid' from the database and set _new to
+        `False` - if the user could be found. """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT uid, mid, tid FROM users WHERE gid = %s AND '
+                    'local_part = %s', self._domain.gid, self._addr.localpart)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._uid, _mid, _tid = result
+            if _tid != self._transport.tid:
+                self._transport = Transport(self._dbh, tid=_tid)
+            self._mail = MailLocation(self._dbh, mid=_mid)
+            self._new = False
+
+    def _set_uid(self):
+        """Set the unique ID for the new Account."""
+        assert self._uid == 0
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('users_uid')")
+        self._uid = dbc.fetchone()[0]
+        dbc.close()
+
+    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(_(u"The mailbox format '%(mbfmt)s' requires Dovecot "
+                         u">= v%(version)s") % {'mbfmt': maillocation.mbformat,
+                       'version': version_str(maillocation.dovecot_version)},
+                       INVALID_MAIL_LOCATION)
+        if not maillocation.postfix and \
+          self._transport.transport.lower() in ('virtual:', 'virtual'):
+            raise AErr(_(u"Invalid transport '%(transport)s' for mailbox "
+                         u"format '%(mbfmt)s'") %
+                       {'transport': self._transport,
+                        'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
+        self._mail = maillocation
+        self._set_uid()
+
+    def _switch_state(self, state, service):
+        """Switch the state of the Account's services on or off. See
+        Account.enable()/Account.disable() for more information."""
+        self._chk_state()
+        if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
+            raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE)
+        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
+            sieve_col = 'sieve'
+        else:
+            sieve_col = 'managesieve'
+        if service in ('smtp', 'pop3', 'imap'):
+            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
+                                                               self._uid)
+        elif service == 'sieve':
+            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col,
+                                                               state,
+                                                               self._uid)
+        else:
+            sql = 'UPDATE users SET smtp = %(s)s, pop3 = %(s)s, imap = %(s)s,\
+ %(col)s = %(s)s WHERE uid = %(uid)d' % \
+                {'s': state, 'col': sieve_col, 'uid': self._uid}
+        dbc = self._dbh.cursor()
+        dbc.execute(sql)
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def _count_aliases(self):
+        """Count all alias addresses where the destination address is the
+        address of the Account."""
+        dbc = self._dbh.cursor()
+        sql = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
+                % self._addr
+        dbc.execute(sql)
+        a_count = dbc.fetchone()[0]
+        dbc.close()
+        return a_count
+
+    def _chk_state(self):
+        """Raise an AccountError if the Account is new - not yet saved in the
+        database."""
+        if self._new:
+            raise AErr(_(u"The account '%s' doesn't exist.") % self._addr,
+                       NO_SUCH_ACCOUNT)
+
+    @property
+    def address(self):
+        """The Account's EmailAddress instance."""
+        return self._addr
+
+    @property
+    def domain_directory(self):
+        """The directory of the domain the Account belongs to."""
+        if self._domain:
+            return self._domain.directory
+        return None
+
+    @property
+    def gid(self):
+        """The Account's group ID."""
+        if self._domain:
+            return self._domain.gid
+        return None
+
+    @property
+    def home(self):
+        """The Account's home directory."""
+        if not self._new:
+            return '%s/%s' % (self._domain.directory, self._uid)
+        return None
+
+    @property
+    def mail_location(self):
+        """The Account's MailLocation."""
+        return self._mail
+
+    @property
+    def uid(self):
+        """The Account's unique ID."""
+        return self._uid
+
+    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 isinstance(password, basestring) or not password:
+            raise AErr(_(u"Couldn't accept password: '%s'") % password,
+                       ACCOUNT_MISSING_PASSWORD)
+        self._passwd = password
+
+    def set_transport(self, transport):
+        """Set the transport for the new Account.
+
+        If you want to update the transport of an existing Account use
+        Account.modify().
+
+        Argument:
+
+        `transport` : basestring
+          The string representation of the transport, e.g.: 'dovecot:'
+        """
+        self._transport = Transport(self._dbh, transport=transport)
+
+    def enable(self, service=None):
+        """Enable a/all service/s for the Account.
+
+        Possible values for the *service* are: 'imap', 'pop3', 'sieve' and
+        'smtp'. When all services should be enabled, use 'all' or the
+        default value `None`.
+
+        Arguments:
+
+        `service` : basestring
+          The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all'
+          or `None`.
+        """
+        self._switch_state(True, service)
+
+    def disable(self, service=None):
+        """Disable a/all service/s for the Account.
+
+        For more information see: Account.enable()."""
+        self._switch_state(False, service)
+
+    def save(self):
+        """Save the new Account in the database."""
+        if not self._new:
+            raise AErr(_(u"The account '%s' already exists.") % self._addr,
+                       ACCOUNT_EXISTS)
+        if not self._passwd:
+            raise AErr(_(u"No password set for '%s'.") % self._addr,
+                       ACCOUNT_MISSING_PASSWORD)
+        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
+            sieve_col = 'sieve'
+        else:
+            sieve_col = 'managesieve'
+        self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
+                                   directory=cfg_dget('mailbox.root')))
+        sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
+ smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
+            sieve_col, self._addr.localpart, pwhash(self._passwd,
+                                                    user=self._addr),
+            self._uid, self._domain.gid, self._mail.mid, self._transport.tid,
+            cfg_dget('account.smtp'), cfg_dget('account.pop3'),
+            cfg_dget('account.imap'), cfg_dget('account.sieve'))
+        dbc = self._dbh.cursor()
+        dbc.execute(sql)
+        self._dbh.commit()
+        dbc.close()
+        self._new = False
+
+    def modify(self, field, value):
+        """Update the Account's *field* to the new *value*.
+
+        Possible values for *field* are: 'name', 'password' and
+        'transport'.  *value* is the *field*'s new value.
+
+        Arguments:
+
+        `field` : basestring
+          The attribute name: 'name', 'password' or 'transport'
+        `value` : basestring
+          The new value of the attribute.
+        """
+        if field not in ('name', 'password', 'transport'):
+            raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        if field == 'password':
+            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
+                        pwhash(value, user=self._addr), self._uid)
+        elif field == 'transport':
+            if value != self._transport.transport:
+                self._transport = Transport(self._dbh, transport=value)
+                dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
+                            self._transport.tid, self._uid)
+        else:
+            dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
+                        value, self._uid)
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    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' and
+        'uid'.
+        """
+        self._chk_state()
+        if cfg_dget('misc.dovecot_version') >= 0x10200b02:
+            sieve_col = 'sieve'
+        else:
+            sieve_col = 'managesieve'
+        sql = 'SELECT name, smtp, pop3, imap, %s FROM users WHERE uid = %d' % \
+            (sieve_col, self._uid)
+        dbc = self._dbh.cursor()
+        dbc.execute(sql)
+        info = dbc.fetchone()
+        dbc.close()
+        if info:
+            keys = ('name', 'smtp', 'pop3', 'imap', sieve_col)
+            info = dict(zip(keys, info))
+            for service in keys[1:]:
+                if info[service]:
+                    # TP: A service (pop3/imap) is enabled/usable for a user
+                    info[service] = _('enabled')
+                else:
+                    # TP: A service (pop3/imap) isn't enabled/usable for a user
+                    info[service] = _('disabled')
+            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
+            info['transport'] = self._transport.transport
+            info['uid'] = self._uid
+            return info
+        # nearly impossible‽
+        raise AErr(_(u"Couldn't fetch information for account: '%s'") %
+                   self._addr, NO_SUCH_ACCOUNT)
+
+    def get_aliases(self):
+        """Return a list with all alias e-mail addresses, whose destination
+        is the address of the Account."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT address ||'@'|| domainname FROM alias, "
+                    "domain_name WHERE destination = %s AND domain_name.gid = "
+                    "alias.gid AND domain_name.is_primary ORDER BY address",
+                    str(self._addr))
+        addresses = dbc.fetchall()
+        dbc.close()
+        aliases = []
+        if addresses:
+            aliases = [alias[0] for alias in addresses]
+        return aliases
+
+    def delete(self, delalias=False):
+        """Delete the Account from the database.
+
+        Argument:
+
+        `delalias` : bool
+          if *delalias* is `True`, all aliases, which points to the Account,
+          will be also deleted.  If there are aliases and *delalias* is
+          `False`, an AccountError will be raised.
+        """
+        assert isinstance(delalias, bool)
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        if delalias:
+            dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
+            # delete also all aliases where the destination address is the same
+            # as for this account.
+            dbc.execute("DELETE FROM alias WHERE destination = %s",
+                        str(self._addr))
+            self._dbh.commit()
+        else:  # check first for aliases
+            a_count = self._count_aliases()
+            if a_count > 0:
+                dbc.close()
+                raise AErr(_(u"There are %(count)d aliases with the "
+                             u"destination address '%(address)s'.") %
+                           {'count': a_count, 'address': self._addr},
+                           ALIAS_PRESENT)
+            dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
+            self._dbh.commit()
+        dbc.close()
+        self._new = True
+        self._uid = 0
+        self._addr = self._dbh = self._domain = self._passwd = None
+        self._mail = self._transport = None
+
+
+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` : long
+      The Account unique ID.
+    `dbh` : pyPgSQL.PgSQL.Connection
+      a database connection for the database access.
+    """
+    try:
+        uid = long(uid)
+    except ValueError:
+        raise AErr(_(u'UID must be an int/long.'), INVALID_ARGUMENT)
+    if uid < 1:
+        raise AErr(_(u'UID must be greater than 0.'), INVALID_ARGUMENT)
+    dbc = dbh.cursor()
+    dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address, "
+                "uid, users.gid FROM users LEFT JOIN domain_name ON "
+                "(domain_name.gid = users.gid AND is_primary) WHERE uid = %s",
+                uid)
+    info = dbc.fetchone()
+    dbc.close()
+    if not info:
+        raise AErr(_(u"There is no account with the UID '%d'.") % uid,
+                   NO_SUCH_ACCOUNT)
+    info = dict(zip(('address', 'uid', 'gid'), info))
+    return info
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/alias.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,164 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.alias
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Alias class to manage e-mail aliases.
+"""
+
+from VirtualMailManager.domain import get_gid
+from VirtualMailManager.emailaddress import EmailAddress
+from VirtualMailManager.errors import AliasError as AErr
+from VirtualMailManager.ext.postconf import Postconf
+from VirtualMailManager.pycompat import all
+from VirtualMailManager.constants import \
+     ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
+
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class Alias(object):
+    """Class to manage e-mail aliases."""
+    __slots__ = ('_addr', '_dests', '_gid', '_dbh')
+
+    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(_(u"The domain '%s' doesn't exist.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._dests = []
+
+        self.__load_dests()
+
+    def __load_dests(self):
+        """Loads all known destination addresses into the _dests list."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT destination FROM alias WHERE gid = %s AND '
+                    'address = %s', self._gid, self._addr.localpart)
+        dests = dbc.fetchall()
+        if dbc.rowcount > 0:
+            self._dests.extend(EmailAddress(dest[0]) for dest in dests)
+        dbc.close()
+
+    def __check_expansion(self, count_new):
+        """Checks the current expansion limit of the alias."""
+        postconf = Postconf(cfg_dget('bin.postconf'))
+        limit = long(postconf.read('virtual_alias_expansion_limit'))
+        dcount = len(self._dests)
+        failed = False
+        if dcount == limit or dcount + count_new > limit:
+            failed = True
+            errmsg = _(
+u"""Can't add %(count_new)i new destination(s) to alias '%(address)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 = _(
+u"""Can't add %(count_new)i new destination(s) to alias '%(address)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 % {'address': self._addr, 'count': dcount,
+                                 'limit': limit, 'count_new': count_new},
+                       ALIAS_EXCEEDS_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
+        its destination addresses will be deleted.
+
+        """
+        dbc = self._dbh.cursor()
+        if not destination:
+            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)
+
+    @property
+    def address(self):
+        """The Alias' EmailAddress instance."""
+        return self._addr
+
+    def add_destinations(self, destinations, warnings=None):
+        """Adds the `EmailAddress`es from *destinations* list to the
+        destinations of the alias.
+
+        Destinations, that are already assigned to the alias, will be
+        removed from *destinations*.  When done, this method will return
+        a set with all destinations, that were saved in the database.
+        """
+        destinations = set(destinations)
+        assert destinations and \
+                all(isinstance(dest, EmailAddress) for dest in destinations)
+        if not warnings is None:
+            assert isinstance(warnings, list)
+        if self._addr in destinations:
+            destinations.remove(self._addr)
+            if not warnings is None:
+                warnings.append(self._addr)
+        duplicates = destinations.intersection(set(self._dests))
+        if duplicates:
+            destinations.difference_update(set(self._dests))
+            if not warnings is None:
+                warnings.extend(duplicates)
+        if not destinations:
+            return destinations
+        self.__check_expansion(len(destinations))
+        dbc = self._dbh.cursor()
+        dbc.executemany("INSERT INTO alias VALUES (%d, '%s', %%s)" %
+                        (self._gid, self._addr.localpart),
+                        (str(destination) for destination in destinations))
+        self._dbh.commit()
+        dbc.close()
+        self._dests.extend(destinations)
+        return destinations
+
+    def del_destination(self, destination):
+        """Deletes the specified ``destination`` address from the alias."""
+        assert isinstance(destination, EmailAddress)
+        if not self._dests:
+            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+                       NO_SUCH_ALIAS)
+        if not destination in self._dests:
+            raise AErr(_(u"The address '%(addr)s' isn't a destination of "
+                         u"the alias '%(alias)s'.") % {'addr': self._addr,
+                       'alias': destination}, NO_SUCH_ALIAS)
+        self.__delete(destination)
+        self._dests.remove(destination)
+
+    def get_destinations(self):
+        """Returns an iterator for all destinations of the alias."""
+        if not self._dests:
+            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+                       NO_SUCH_ALIAS)
+        return iter(self._dests)
+
+    def delete(self):
+        """Deletes the alias with all its destinations."""
+        if not self._dests:
+            raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+                       NO_SUCH_ALIAS)
+        self.__delete()
+        del self._dests[:]
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/aliasdomain.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,143 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.aliasdomain
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's AliasDomain class to manage alias domains.
+"""
+
+from VirtualMailManager.domain import Domain, check_domainname
+from VirtualMailManager.constants import \
+     ALIASDOMAIN_EXISTS, ALIASDOMAIN_ISDOMAIN, ALIASDOMAIN_NO_DOMDEST, \
+     NO_SUCH_ALIASDOMAIN, NO_SUCH_DOMAIN
+from VirtualMailManager.errors import AliasDomainError as ADErr
+
+
+_ = lambda msg: msg
+
+
+class AliasDomain(object):
+    """Class to manage e-mail alias domains."""
+    __slots__ = ('_gid', '_name', '_domain', '_dbh')
+
+    def __init__(self, dbh, domainname):
+        """Creates a new AliasDomain instance.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          a database connection for the database access
+        `domainname` : basestring
+          the name of the AliasDomain"""
+        self._dbh = dbh
+        self._name = check_domainname(domainname)
+        self._gid = 0
+        self._domain = None
+        self._load()
+
+    def _load(self):
+        """Loads the AliasDomain's GID from the database and checks if the
+        domain name is marked as primary."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT gid, is_primary FROM domain_name WHERE '
+                    'domainname = %s', self._name)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            if result[1]:
+                raise ADErr(_(u"The domain '%s' is a primary domain.") %
+                            self._name, ALIASDOMAIN_ISDOMAIN)
+            self._gid = result[0]
+
+    def set_destination(self, dest_domain):
+        """Set the destination of a new AliasDomain or updates the
+        destination of an existing AliasDomain.
+
+        Argument:
+
+        `dest_domain` : VirtualMailManager.Domain.Domain
+          the AliasDomain's destination domain
+        """
+        assert isinstance(dest_domain, Domain)
+        self._domain = dest_domain
+
+    def save(self):
+        """Stores information about the new AliasDomain in the database."""
+        if self._gid > 0:
+            raise ADErr(_(u"The alias domain '%s' already exists.") %
+                        self._name, ALIASDOMAIN_EXISTS)
+        if not self._domain:
+            raise ADErr(_(u'No destination domain set for the alias domain.'),
+                        ALIASDOMAIN_NO_DOMDEST)
+        if self._domain.gid < 1:
+            raise ADErr(_(u"The target domain '%s' doesn't exist.") %
+                        self._domain.name, NO_SUCH_DOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('INSERT INTO domain_name VALUES (%s, %s, FALSE)',
+                    self._name, self._domain.gid)
+        self._dbh.commit()
+        dbc.close()
+        self._gid = self._domain.gid
+
+    def info(self):
+        """Returns a dict (keys: "alias" and "domain") with the names of the
+        AliasDomain and its primary domain."""
+        if self._gid < 1:
+            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+                        self._name, NO_SUCH_ALIASDOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
+                    'is_primary', self._gid)
+        domain = dbc.fetchone()
+        dbc.close()
+        if domain:
+            return {'alias': self._name, 'domain': domain[0]}
+        else:  # an almost unlikely case, isn't it?
+            raise ADErr(_(u'There is no primary domain for the alias domain '
+                          u"'%s'.") % self._name, NO_SUCH_DOMAIN)
+
+    def switch(self):
+        """Switch the destination of the AliasDomain to the new destination,
+        set with the method `set_destination()`.
+        """
+        if not self._domain:
+            raise ADErr(_(u'No destination domain set for the alias domain.'),
+                        ALIASDOMAIN_NO_DOMDEST)
+        if self._domain.gid < 1:
+            raise ADErr(_(u"The target domain '%s' doesn't exist.") %
+                        self._domain.name, NO_SUCH_DOMAIN)
+        if self._gid < 1:
+            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+                        self._name, NO_SUCH_ALIASDOMAIN)
+        if self._gid == self._domain.gid:
+            raise ADErr(_(u"The alias domain '%(alias)s' is already assigned "
+                          u"to the domain '%(domain)s'.") %
+                        {'alias': self._name, 'domain': self._domain.name},
+                        ALIASDOMAIN_EXISTS)
+        dbc = self._dbh.cursor()
+        dbc.execute('UPDATE domain_name SET gid = %s WHERE gid = %s AND '
+                    'domainname = %s AND NOT is_primary', self._domain.gid,
+                    self._gid, self._name)
+        self._dbh.commit()
+        dbc.close()
+        self._gid = self._domain.gid
+
+    def delete(self):
+        """Delete the AliasDomain's record form the database.
+
+        Raises an AliasDomainError if the AliasDomain doesn't exist.
+        """
+        if self._gid < 1:
+            raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+                        self._name, NO_SUCH_ALIASDOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('DELETE FROM domain_name WHERE domainname = %s AND NOT '
+                    'is_primary', self._name)
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+            self._gid = 0
+        dbc.close()
+
+del _
--- a/VirtualMailManager/cli/Config.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.cli.CliConfig
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Adds some interactive stuff to the Config class.
-"""
-
-from ConfigParser import RawConfigParser
-from shutil import copy2
-
-from VirtualMailManager import ENCODING
-from VirtualMailManager.Config import Config, ConfigValueError, LazyConfig
-from VirtualMailManager.errors import ConfigError
-from VirtualMailManager.cli import w_err, w_std
-from VirtualMailManager.constants import VMM_TOO_MANY_FAILURES
-
-_ = lambda msg: msg
-
-
-class CliConfig(Config):
-    """Adds the interactive ``configure`` method to the `Config` class
-    and overwrites `LazyConfig.set(), in order to update a single option
-    in the configuration file with a single command line command.
-    """
-
-    def configure(self, sections):
-        """Interactive method for configuring all options of the given
-        iterable ``sections`` object."""
-        input_fmt = _(u'Enter new value for option %(option)s '
-                      u'[%(current_value)s]: ')
-        failures = 0
-
-        w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
-        for section in sections:
-            w_std(_(u'* Configuration section: %r') % section)
-            for opt, val in self.items(section):
-                failures = 0
-                while True:
-                    newval = raw_input(input_fmt.encode(ENCODING, 'replace') %
-                                       {'option': opt, 'current_value': val})
-                    if newval and newval != val:
-                        try:
-                            LazyConfig.set(self, '%s.%s' % (section, opt),
-                                           newval)
-                            break
-                        except (ValueError, ConfigValueError), err:
-                            w_err(0, _(u'Warning: %s') % err)
-                            failures += 1
-                            if failures > 2:
-                                raise ConfigError(_(u'Too many failures - try '
-                                                    u'again later.'),
-                                                  VMM_TOO_MANY_FAILURES)
-                    else:
-                        break
-            print
-        if self._modified:
-            self.__save_changes()
-
-    def set(self, option, value):
-        """Set the value of an option.
-
-        If the new `value` has been set, the configuration file will be
-        immediately updated.
-
-        Throws a ``ValueError`` if `value` couldn't be converted to
-        ``LazyConfigOption.cls``"""
-        section, option_ = self._get_section_option(option)
-        val = self._cfg[section][option_].cls(value)
-        if self._cfg[section][option_].validate:
-            val = self._cfg[section][option_].validate(val)
-        # Do not write default values also skip identical values
-        if not self._cfg[section][option_].default is None:
-            old_val = self.dget(option)
-        else:
-            old_val = self.pget(option)
-        if val == old_val:
-            return
-        if not RawConfigParser.has_section(self, section):
-            self.add_section(section)
-        RawConfigParser.set(self, section, option_, val)
-        self.__save_changes()
-
-    def __save_changes(self):
-        """Writes changes to the configuration file."""
-        copy2(self._cfg_filename, self._cfg_filename + '.bak')
-        self._cfg_file = open(self._cfg_filename, 'w')
-        self.write(self._cfg_file)
-        self._cfg_file.close()
-
-del _
--- a/VirtualMailManager/cli/Handler.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.cli.Handler
-
-    A derived Handler class with a few changes/additions for cli use.
-"""
-
-import os
-
-from VirtualMailManager.errors import VMMError
-from VirtualMailManager.Handler import Handler
-from VirtualMailManager.cli import read_pass
-from VirtualMailManager.cli.Config import CliConfig as Cfg
-from VirtualMailManager.constants import INVALID_SECTION
-
-_ = lambda msg: msg
-
-
-class CliHandler(Handler):
-    """This class uses a `CliConfig` for configuration stuff, instead of
-    the non-interactive `Config` class.
-
-    It provides the additional methods cfgSet() and configure().
-
-    Additionally it uses `VirtualMailManager.cli.read_pass()` for for the
-    interactive password dialog.
-    """
-
-    __slots__ = ()  # nothing additional, also no __dict__/__weakref__
-
-    def __init__(self):
-        """Creates a new CliHandler instance.
-
-        Throws a NotRootError if your uid is greater 0.
-        """
-        # Overwrite the parent CTor partly, we use the CliConfig class
-        # and add some command line checks.
-        skip_some_checks = os.sys.argv[1] in ('cf', 'configure', 'h', 'help',
-                                              'v', 'version')
-        super(CliHandler, self).__init__(skip_some_checks)
-
-        self._cfg = Cfg(self._cfg_fname)
-        self._cfg.load()
-        if not skip_some_checks:
-            self._cfg.check()
-            self._chkenv()
-
-    def cfg_set(self, option, value):
-        """Set a new value for the given option."""
-        return self._cfg.set(option, value)
-
-    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(_(u'Invalid section: “%s”') % section,
-                           INVALID_SECTION)
-
-    def user_add(self, emailaddress, password=None):
-        """Prefix the parent user_add() with the interactive password
-        dialog."""
-        if password is None:
-            password = read_pass()
-        super(CliHandler, self).user_add(emailaddress, password)
-
-    def user_password(self, emailaddress, password=None):
-        """Prefix the parent user_password() with the interactive password
-        dialog."""
-        if password is None:
-            password = read_pass()
-        super(CliHandler, self).user_password(emailaddress, password)
-
-del _
--- a/VirtualMailManager/cli/__init__.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/cli/__init__.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,7 +1,6 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.cli
     ~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/config.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,93 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.config
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Adds some interactive stuff to the Config class.
+"""
+
+from ConfigParser import RawConfigParser
+from shutil import copy2
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.config import Config, ConfigValueError, LazyConfig
+from VirtualMailManager.errors import ConfigError
+from VirtualMailManager.cli import w_err, w_std
+from VirtualMailManager.constants import VMM_TOO_MANY_FAILURES
+
+_ = lambda msg: msg
+
+
+class CliConfig(Config):
+    """Adds the interactive ``configure`` method to the `Config` class
+    and overwrites `LazyConfig.set(), in order to update a single option
+    in the configuration file with a single command line command.
+    """
+
+    def configure(self, sections):
+        """Interactive method for configuring all options of the given
+        iterable ``sections`` object."""
+        input_fmt = _(u'Enter new value for option %(option)s '
+                      u'[%(current_value)s]: ')
+        failures = 0
+
+        w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
+        for section in sections:
+            w_std(_(u'* Configuration section: %r') % section)
+            for opt, val in self.items(section):
+                failures = 0
+                while True:
+                    newval = raw_input(input_fmt.encode(ENCODING, 'replace') %
+                                       {'option': opt, 'current_value': val})
+                    if newval and newval != val:
+                        try:
+                            LazyConfig.set(self, '%s.%s' % (section, opt),
+                                           newval)
+                            break
+                        except (ValueError, ConfigValueError), err:
+                            w_err(0, _(u'Warning: %s') % err)
+                            failures += 1
+                            if failures > 2:
+                                raise ConfigError(_(u'Too many failures - try '
+                                                    u'again later.'),
+                                                  VMM_TOO_MANY_FAILURES)
+                    else:
+                        break
+            print
+        if self._modified:
+            self.__save_changes()
+
+    def set(self, option, value):
+        """Set the value of an option.
+
+        If the new `value` has been set, the configuration file will be
+        immediately updated.
+
+        Throws a ``ValueError`` if `value` couldn't be converted to
+        ``LazyConfigOption.cls``"""
+        section, option_ = self._get_section_option(option)
+        val = self._cfg[section][option_].cls(value)
+        if self._cfg[section][option_].validate:
+            val = self._cfg[section][option_].validate(val)
+        # Do not write default values also skip identical values
+        if not self._cfg[section][option_].default is None:
+            old_val = self.dget(option)
+        else:
+            old_val = self.pget(option)
+        if val == old_val:
+            return
+        if not RawConfigParser.has_section(self, section):
+            self.add_section(section)
+        RawConfigParser.set(self, section, option_, val)
+        self.__save_changes()
+
+    def __save_changes(self):
+        """Writes changes to the configuration file."""
+        copy2(self._cfg_filename, self._cfg_filename + '.bak')
+        self._cfg_file = open(self._cfg_filename, 'w')
+        self.write(self._cfg_file)
+        self._cfg_file.close()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/handler.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,84 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.handler
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    A derived Handler class with a few changes/additions for cli use.
+"""
+
+import os
+
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.handler import Handler
+from VirtualMailManager.cli import read_pass
+from VirtualMailManager.cli.config import CliConfig as Cfg
+from VirtualMailManager.constants import INVALID_SECTION
+
+_ = lambda msg: msg
+
+
+class CliHandler(Handler):
+    """This class uses a `CliConfig` for configuration stuff, instead of
+    the non-interactive `Config` class.
+
+    It provides the additional methods cfgSet() and configure().
+
+    Additionally it uses `VirtualMailManager.cli.read_pass()` for for the
+    interactive password dialog.
+    """
+
+    __slots__ = ()  # nothing additional, also no __dict__/__weakref__
+
+    def __init__(self):
+        """Creates a new CliHandler instance.
+
+        Throws a NotRootError if your uid is greater 0.
+        """
+        # Overwrite the parent CTor partly, we use the CliConfig class
+        # and add some command line checks.
+        skip_some_checks = os.sys.argv[1] in ('cf', 'configure', 'h', 'help',
+                                              'v', 'version')
+        super(CliHandler, self).__init__(skip_some_checks)
+
+        self._cfg = Cfg(self._cfg_fname)
+        self._cfg.load()
+        if not skip_some_checks:
+            self._cfg.check()
+            self._chkenv()
+
+    def cfg_set(self, option, value):
+        """Set a new value for the given option."""
+        return self._cfg.set(option, value)
+
+    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(_(u'Invalid section: “%s”') % section,
+                           INVALID_SECTION)
+
+    def user_add(self, emailaddress, password=None):
+        """Prefix the parent user_add() with the interactive password
+        dialog."""
+        if password is None:
+            password = read_pass()
+        super(CliHandler, self).user_add(emailaddress, password)
+
+    def user_password(self, emailaddress, password=None):
+        """Prefix the parent user_password() with the interactive password
+        dialog."""
+        if password is None:
+            password = read_pass()
+        super(CliHandler, self).user_password(emailaddress, password)
+
+del _
--- a/VirtualMailManager/common.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/common.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,9 +1,9 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.common
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
 
     Some common functions
 """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/config.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,451 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.config
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VMM's configuration module for simplified configuration access.
+"""
+
+import re
+
+from ConfigParser import \
+     Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
+     ParsingError, RawConfigParser
+from cStringIO import StringIO# TODO: move interactive stff to cli
+
+from VirtualMailManager.common import exec_ok, get_unicode, is_dir, version_hex
+from VirtualMailManager.constants import CONF_ERROR
+from VirtualMailManager.errors import ConfigError, VMMError
+from VirtualMailManager.maillocation import known_format
+from VirtualMailManager.password import verify_scheme as _verify_scheme
+
+
+_ = lambda msg: msg
+
+
+class BadOptionError(Error):
+    """Raised when a option isn't in the format 'section.option'."""
+    pass
+
+
+class ConfigValueError(Error):
+    """Raised when creating or validating of new values fails."""
+    pass
+
+
+class NoDefaultError(Error):
+    """Raised when the requested option has no default value."""
+
+    def __init__(self, section, option):
+        Error.__init__(self, 'Option %r in section %r has no default value' %
+                             (option, section))
+
+
+class LazyConfig(RawConfigParser):
+    """The **lazy** derivate of the `RawConfigParser`.
+
+    There are two additional getters:
+
+    `pget()`
+      The polymorphic getter, which returns a option's value with the
+      appropriate type.
+    `dget()`
+      Like `LazyConfig.pget()`, but returns the option's default, from
+      `LazyConfig._cfg['sectionname']['optionname'].default`, if the
+      option is not configured in a ini-like configuration file.
+
+    `set()` differs from `RawConfigParser`'s `set()` method. `set()`
+    takes the `section` and `option` arguments combined to a single
+    string in the form "section.option".
+    """
+
+    def __init__(self):
+        RawConfigParser.__init__(self)
+        self._modified = False
+        # sample _cfg dict.  Create your own in your derived class.
+        self._cfg = {
+            'sectionname': {
+                'optionname': LazyConfigOption(int, 1, self.getint),
+            }
+        }
+
+    def bool_new(self, value):
+        """Converts the string `value` into a `bool` and returns it.
+
+        | '1', 'on', 'yes' and 'true' will become `True`
+        | '0', 'off', 'no' and 'false' will become `False`
+
+        Throws a `ConfigValueError` for all other values, except bools.
+        """
+        if isinstance(value, bool):
+            return value
+        if value.lower() in self._boolean_states:
+            return self._boolean_states[value.lower()]
+        else:
+            raise ConfigValueError(_(u"Not a boolean: '%s'") %
+                                   get_unicode(value))
+
+    def getboolean(self, section, option):
+        """Returns the boolean value of the option, in the given
+        section.
+
+        For a boolean True, the value must be set to '1', 'on', 'yes',
+        'true' or True. For a boolean False, the value must set to '0',
+        'off', 'no', 'false' or False.
+        If the option has another value assigned this method will raise
+        a ValueError.
+        """
+        # if the setting was modified it may be still a boolean value lets see
+        tmp = self.get(section, option)
+        if isinstance(tmp, bool):
+            return tmp
+        if not tmp.lower() in self._boolean_states:
+            raise ValueError('Not a boolean: %s' % tmp)
+        return self._boolean_states[tmp.lower()]
+
+    def _get_section_option(self, section_option):
+        """splits ``section_option`` (section.option) in two parts and
+        returns them as list ``[section, option]``, if:
+
+          * it likes the format of ``section_option``
+          * the ``section`` is known
+          * the ``option`` is known
+
+        Else one of the following exceptions will be thrown:
+
+          * `BadOptionError`
+          * `NoSectionError`
+          * `NoOptionError`
+        """
+        sect_opt = section_option.lower().split('.')
+        # TODO: cache it
+        if len(sect_opt) != 2:  # do we need a regexp to check the format?
+            raise BadOptionError(_(u"Bad format: '%s' - expected: "
+                                   u"section.option") %
+                                 get_unicode(section_option))
+        if not sect_opt[0] in self._cfg:
+            raise NoSectionError(sect_opt[0])
+        if not sect_opt[1] in self._cfg[sect_opt[0]]:
+            raise NoOptionError(sect_opt[1], sect_opt[0])
+        return sect_opt
+
+    def items(self, section):
+        """returns an iterable that returns key, value ``tuples`` from
+        the given ``section``.
+        """
+        if section in self._sections:  # check if the section was parsed
+            sect = self._sections[section]
+        elif not section in self._cfg:
+            raise NoSectionError(section)
+        else:
+            return ((k, self._cfg[section][k].default) \
+                    for k in self._cfg[section].iterkeys())
+        # still here? Get defaults and merge defaults with configured setting
+        defaults = dict((k, self._cfg[section][k].default) \
+                        for k in self._cfg[section].iterkeys())
+        defaults.update(sect)
+        if '__name__' in defaults:
+            del defaults['__name__']
+        return defaults.iteritems()
+
+    def dget(self, option):
+        """Returns the value of the `option`.
+
+        If the option could not be found in the configuration file, the
+        configured default value, from ``LazyConfig._cfg`` will be
+        returned.
+
+        Arguments:
+
+        `option` : string
+            the configuration option in the form "section.option"
+
+        Throws a `NoDefaultError`, if no default value was passed to
+        `LazyConfigOption.__init__()` for the `option`.
+        """
+        section, option = self._get_section_option(option)
+        try:
+            return self._cfg[section][option].getter(section, option)
+        except (NoSectionError, NoOptionError):
+            if not self._cfg[section][option].default is None:  # may be False
+                return self._cfg[section][option].default
+            else:
+                raise NoDefaultError(section, option)
+
+    def pget(self, option):
+        """Returns the value of the `option`."""
+        section, option = self._get_section_option(option)
+        return self._cfg[section][option].getter(section, option)
+
+    def set(self, option, value):
+        """Set the `value` of the `option`.
+
+        Throws a `ValueError` if `value` couldn't be converted using
+        `LazyConfigOption.cls`.
+        """
+        # pylint: disable=W0221
+        # @pylint: _L A Z Y_
+        section, option = self._get_section_option(option)
+        val = self._cfg[section][option].cls(value)
+        if self._cfg[section][option].validate:
+            val = self._cfg[section][option].validate(val)
+        if not RawConfigParser.has_section(self, section):
+            self.add_section(section)
+        RawConfigParser.set(self, section, option, val)
+        self._modified = True
+
+    def has_section(self, section):
+        """Checks if `section` is a known configuration section."""
+        return section.lower() in self._cfg
+
+    def has_option(self, option):
+        """Checks if the option (section.option) is a known
+        configuration option.
+        """
+        # pylint: disable=W0221
+        # @pylint: _L A Z Y_
+        try:
+            self._get_section_option(option)
+            return True
+        except(BadOptionError, NoSectionError, NoOptionError):
+            return False
+
+    def sections(self):
+        """Returns an iterator object for all configuration sections."""
+        return self._cfg.iterkeys()
+
+
+class LazyConfigOption(object):
+    """A simple container class for configuration settings.
+
+    `LazyConfigOption` instances are required by `LazyConfig` instances,
+    and instances of classes derived from `LazyConfig`, like the
+    `Config` class.
+    """
+    __slots__ = ('__cls', '__default', '__getter', '__validate')
+
+    def __init__(self, cls, default, getter, validate=None):
+        """Creates a new `LazyConfigOption` instance.
+
+        Arguments:
+
+        `cls` : type
+          The class/type of the option's value
+        `default`
+          Default value of the option. Use ``None`` if the option should
+          not have a default value.
+        `getter` : callable
+          A method's name of `RawConfigParser` and derived classes, to
+          get a option's value, e.g. `self.getint`.
+        `validate` : NoneType or a callable
+          None or any method, that takes one argument, in order to
+          check the value, when `LazyConfig.set()` is called.
+        """
+        self.__cls = cls
+        if not default is None:  # enforce the type of the default value
+            self.__default = self.__cls(default)
+        else:
+            self.__default = default
+        if not callable(getter):
+            raise TypeError('getter has to be a callable, got a %r' %
+                            getter.__class__.__name__)
+        self.__getter = getter
+        if validate and not callable(validate):
+            raise TypeError('validate has to be callable or None, got a %r' %
+                            validate.__class__.__name__)
+        self.__validate = validate
+
+    @property
+    def cls(self):
+        """The class of the option's value e.g. `str`, `unicode` or `bool`."""
+        return self.__cls
+
+    @property
+    def default(self):
+        """The option's default value, may be `None`"""
+        return self.__default
+
+    @property
+    def getter(self):
+        """The getter method or function to get the option's value"""
+        return self.__getter
+
+    @property
+    def validate(self):
+        """A method or function to validate the value"""
+        return self.__validate
+
+
+class Config(LazyConfig):
+    """This class is for reading vmm's configuration file."""
+
+    def __init__(self, filename):
+        """Creates a new Config instance
+
+        Arguments:
+
+        `filename` : str
+          path to the configuration file
+        """
+        LazyConfig.__init__(self)
+        self._cfg_filename = filename
+        self._cfg_file = None
+        self.__missing = {}
+
+        LCO = LazyConfigOption
+        bool_t = self.bool_new
+        self._cfg = {
+            'account': {
+                'delete_directory': LCO(bool_t, False, self.getboolean),
+                'directory_mode': LCO(int, 448, self.getint),
+                'disk_usage': LCO(bool_t, False, self.getboolean),
+                'password_length': LCO(int, 8, self.getint),
+                'random_password': LCO(bool_t, False, self.getboolean),
+                'imap': LCO(bool_t, True, self.getboolean),
+                'pop3': LCO(bool_t, True, self.getboolean),
+                'sieve': LCO(bool_t, True, self.getboolean),
+                'smtp': LCO(bool_t, True, self.getboolean),
+            },
+            'bin': {
+                'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
+                                 exec_ok),
+                'du': LCO(str, '/usr/bin/du', self.get, exec_ok),
+                'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
+            },
+            'database': {
+                'host': LCO(str, 'localhost', self.get),
+                'name': LCO(str, 'mailsys', self.get),
+                'pass': LCO(str, None, self.get),
+                'user': LCO(str, None, self.get),
+            },
+            'domain': {
+                'auto_postmaster': LCO(bool_t, True, self.getboolean),
+                'delete_directory': LCO(bool_t, False, self.getboolean),
+                'directory_mode': LCO(int, 504, self.getint),
+                'force_deletion': LCO(bool_t, False, self.getboolean),
+            },
+            'mailbox': {
+                'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
+                               self.unicode),
+                'format': LCO(str, 'maildir', self.get, check_mailbox_format),
+                'root': LCO(str, 'Maildir', self.unicode),
+                'subscribe': LCO(bool_t, True, self.getboolean),
+            },
+            'misc': {
+                'base_directory': LCO(str, '/srv/mail', self.get, is_dir),
+                'crypt_blowfish_rounds': LCO(int, 5, self.getint),
+                'crypt_sha256_rounds': LCO(int, 5000, self.getint),
+                'crypt_sha512_rounds': LCO(int, 5000, self.getint),
+                'dovecot_version': LCO(str, None, self.hexversion,
+                                       check_version_format),
+                'password_scheme': LCO(str, 'CRAM-MD5', self.get,
+                                       verify_scheme),
+                'transport': LCO(str, 'dovecot:', self.get),
+            },
+        }
+
+    def load(self):
+        """Loads the configuration, read only.
+
+        Raises a ConfigError if the configuration syntax is
+        invalid.
+        """
+        try:
+            self._cfg_file = open(self._cfg_filename, 'r')
+            self.readfp(self._cfg_file)
+        except (MissingSectionHeaderError, ParsingError), err:
+            raise ConfigError(str(err), CONF_ERROR)
+        finally:
+            if self._cfg_file and not self._cfg_file.closed:
+                self._cfg_file.close()
+
+    def check(self):
+        """Performs a configuration check.
+
+        Raises a ConfigError if settings w/o a default value are missed.
+        Or a ConfigValueError if 'misc.dovecot_version' has the wrong
+        format.
+        """
+        # TODO: There are only two settings w/o defaults.
+        #       So there is no need for cStringIO
+        if not self.__chk_cfg():
+            errmsg = StringIO()
+            errmsg.write(_(u'Missing options, which have no default value.\n'))
+            errmsg.write(_(u'Using configuration file: %s\n') %
+                         self._cfg_filename)
+            for section, options in self.__missing.iteritems():
+                errmsg.write(_(u'* Section: %s\n') % section)
+                for option in options:
+                    errmsg.write((u'    %s\n') % option)
+            raise ConfigError(errmsg.getvalue(), CONF_ERROR)
+        check_version_format(self.get('misc', 'dovecot_version'))
+
+    def hexversion(self, section, option):
+        """Converts the version number (e.g.: 1.2.3) from the *option*'s
+        value to an int."""
+        return version_hex(self.get(section, option))
+
+    def unicode(self, section, option):
+        """Returns the value of the `option` from `section`, converted
+        to Unicode."""
+        return get_unicode(self.get(section, option))
+
+    def __chk_cfg(self):
+        """Checks all section's options for settings w/o a default
+        value.
+
+        Returns `True` if everything is fine, else `False`.
+        """
+        errors = False
+        for section in self._cfg.iterkeys():
+            missing = []
+            for option, value in self._cfg[section].iteritems():
+                if (value.default is None and
+                    not RawConfigParser.has_option(self, section, option)):
+                    missing.append(option)
+                    errors = True
+            if missing:
+                self.__missing[section] = missing
+        return not errors
+
+
+def check_mailbox_format(format):
+    """
+    Check if the mailbox format *format* is supported.  When the *format*
+    is supported it will be returned, otherwise a `ConfigValueError` will
+    be raised.
+    """
+    format = format.lower()
+    if known_format(format):
+        return format
+    raise ConfigValueError(_(u"Unsupported mailbox format: '%s'") %
+                           get_unicode(format))
+
+
+def check_version_format(version_string):
+    """Check if the *version_string* has the proper format, e.g.: '1.2.3'.
+    Returns the validated version string if it has the expected format.
+    Otherwise a `ConfigValueError` will be raised.
+    """
+    version_re = r'^\d+\.\d+\.(?:\d+|(?:alpha|beta|rc)\d+)$'
+    if not re.match(version_re, version_string):
+        raise ConfigValueError(_(u"Not a valid Dovecot version: '%s'") %
+                               get_unicode(version_string))
+    return version_string
+
+
+def verify_scheme(scheme):
+    """Checks if the password scheme *scheme* can be accepted and returns
+    the verified scheme.
+    """
+    try:
+        scheme, encoding = _verify_scheme(scheme)
+    except VMMError, err:  # 'cast' it
+        raise ConfigValueError(err.msg)
+    if not encoding:
+        return scheme
+    return '%s.%s' % (scheme, encoding)
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/domain.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,408 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.domain
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Domain class to manage e-mail domains.
+"""
+
+import os
+import re
+from random import choice
+
+from VirtualMailManager.constants import \
+     ACCOUNT_AND_ALIAS_PRESENT, ACCOUNT_PRESENT, ALIAS_PRESENT, \
+     DOMAIN_ALIAS_EXISTS, DOMAIN_EXISTS, DOMAIN_INVALID, DOMAIN_TOO_LONG, \
+     NO_SUCH_DOMAIN
+from VirtualMailManager.errors import DomainError as DomErr
+from VirtualMailManager.transport import Transport
+
+
+MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
+RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$")
+_ = lambda msg: msg
+
+
+class Domain(object):
+    """Class to manage e-mail domains."""
+    __slots__ = ('_directory', '_gid', '_name', '_transport', '_dbh', '_new')
+
+    def __init__(self, dbh, domainname):
+        """Creates a new Domain instance.
+
+        Loads all relevant data from the database, if the domain could be
+        found.  To create a new domain call the methods set_directory() and
+        set_transport() before save().
+
+        A DomainError will be thrown when the *domainname* is the name of
+        an alias domain.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          a database connection for the database access
+        `domainname` : basestring
+          The name of the domain
+        """
+        self._name = check_domainname(domainname)
+        self._dbh = dbh
+        self._gid = 0
+        self._transport = None
+        self._directory = None
+        self._new = True
+        self._load()
+
+    def _load(self):
+        """Load information from the database and checks if the domain name
+        is the primary one.
+
+        Raises a DomainError if Domain._name isn't the primary name of the
+        domain.
+        """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT dd.gid, tid, domaindir, is_primary FROM '
+                    'domain_data dd, domain_name dn WHERE domainname = %s AND '
+                    'dn.gid = dd.gid', self._name)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            if not result[3]:
+                raise DomErr(_(u"The domain '%s' is an alias domain.") %
+                             self._name, DOMAIN_ALIAS_EXISTS)
+            self._gid, self._directory = result[0], result[2]
+            self._transport = Transport(self._dbh, tid=result[1])
+            self._new = False
+
+    def _set_gid(self):
+        """Sets the ID of the domain - if not set yet."""
+        assert self._gid == 0
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('domain_gid')")
+        self._gid = dbc.fetchone()[0]
+        dbc.close()
+
+    def _has(self, what):
+        """Checks if aliases or accounts are assigned to the domain.
+
+        If there are assigned accounts or aliases True will be returned,
+        otherwise False will be returned.
+
+        Argument:
+
+        `what` : basestring
+            "alias" or "users"
+        """
+        assert what in ('alias', 'users')
+        dbc = self._dbh.cursor()
+        if what == 'users':
+            dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._gid)
+        else:
+            dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._gid)
+        count = dbc.fetchone()
+        dbc.close()
+        return count[0] > 0
+
+    def _chk_delete(self, deluser, delalias):
+        """Checks dependencies for deletion.
+
+        Arguments:
+        deluser -- ignore available accounts (bool)
+        delalias -- ignore available aliases (bool)
+        """
+        if not deluser:
+            hasuser = self._has('users')
+        else:
+            hasuser = False
+        if not delalias:
+            hasalias = self._has('alias')
+        else:
+            hasalias = False
+        if hasuser and hasalias:
+            raise DomErr(_(u'There are accounts and aliases.'),
+                         ACCOUNT_AND_ALIAS_PRESENT)
+        elif hasuser:
+            raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT)
+        elif hasalias:
+            raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT)
+
+    def _chk_state(self):
+        """Throws a DomainError if the Domain is new - not saved in the
+        database."""
+        if self._new:
+            raise DomErr(_(u"The domain '%s' doesn't exist.") % self._name,
+                         NO_SUCH_DOMAIN)
+
+    @property
+    def gid(self):
+        """The GID of the Domain."""
+        return self._gid
+
+    @property
+    def name(self):
+        """The Domain's name."""
+        return self._name
+
+    @property
+    def directory(self):
+        """The Domain's directory."""
+        return self._directory
+
+    def set_directory(self, basedir):
+        """Set the path value of the Domain's directory, inside *basedir*.
+
+        Argument:
+
+        `basedir` : basestring
+          The base directory of all domains
+        """
+        assert self._new and self._directory is None
+        self._set_gid()
+        self._directory = os.path.join(basedir, choice(MAILDIR_CHARS),
+                                       str(self._gid))
+
+    @property
+    def transport(self):
+        """The Domain's transport."""
+        return self._transport
+
+    def set_transport(self, transport):
+        """Set the transport for the new Domain.
+
+        Argument:
+
+        `transport` : VirtualMailManager.Transport
+          The transport of the new Domain
+        """
+        assert self._new and isinstance(transport, Transport)
+        self._transport = transport
+
+    def save(self):
+        """Stores the new domain in the database."""
+        if not self._new:
+            raise DomErr(_(u"The domain '%s' already exists.") % self._name,
+                         DOMAIN_EXISTS)
+        assert self._directory is not None and self._transport is not None
+        dbc = self._dbh.cursor()
+        dbc.execute("INSERT INTO domain_data VALUES (%s, %s, %s)", self._gid,
+                    self._transport.tid, self._directory)
+        dbc.execute("INSERT INTO domain_name VALUES (%s, %s, %s)", self._name,
+                    self._gid, True)
+        self._dbh.commit()
+        dbc.close()
+        self._new = False
+
+    def delete(self, deluser=False, delalias=False):
+        """Deletes the domain.
+
+        Arguments:
+
+        `deluser` : bool
+          force deletion of all available accounts, default `False`
+        `delalias` : bool
+          force deletion of all available aliases, default `False`
+        """
+        self._chk_state()
+        self._chk_delete(deluser, delalias)
+        dbc = self._dbh.cursor()
+        for tbl in ('alias', 'users', 'relocated', 'domain_name',
+                    'domain_data'):
+            dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._gid))
+        self._dbh.commit()
+        dbc.close()
+        self._gid = 0
+        self._directory = self._transport = None
+        self._new = True
+
+    def update_transport(self, transport, force=False):
+        """Sets a new transport for the Domain.
+
+        If *force* is `True` the new *transport* will be assigned to all
+        existing accounts.  Otherwise the *transport* will be only used for
+        accounts created from now on.
+
+        Arguments:
+
+        `transport` : VirtualMailManager.Transport
+          the new transport
+        `force` : bool
+          enforce new transport setting for all accounts, default `False`
+        """
+        self._chk_state()
+        assert isinstance(transport, Transport)
+        if transport == self._transport:
+            return
+        dbc = self._dbh.cursor()
+        dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s",
+                    transport.tid, self._gid)
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        if force:
+            dbc.execute("UPDATE users SET tid = %s WHERE gid = %s",
+                        transport.tid, self._gid)
+            if dbc.rowcount > 0:
+                self._dbh.commit()
+        dbc.close()
+        self._transport = transport
+
+    def get_info(self):
+        """Returns a dictionary with information about the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT gid, domainname, transport, domaindir, '
+                    'aliasdomains accounts, aliases, relocated FROM '
+                    'vmm_domain_info WHERE gid = %s', self._gid)
+        info = dbc.fetchone()
+        dbc.close()
+        keys = ('gid', 'domainname', 'transport', 'domaindir', 'aliasdomains',
+                'accounts', 'aliases', 'relocated')
+        return dict(zip(keys, info))
+
+    def get_accounts(self):
+        """Returns a list with all accounts of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT local_part from users where gid = %s ORDER BY '
+                    'local_part', self._gid)
+        users = dbc.fetchall()
+        dbc.close()
+        accounts = []
+        if users:
+            addr = u'@'.join
+            _dom = self._name
+            accounts = [addr((account[0], _dom)) for account in users]
+        return accounts
+
+    def get_aliases(self):
+        """Returns a list with all aliases e-mail addresses of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT DISTINCT address FROM alias WHERE gid = %s ORDER '
+                    'BY address', self._gid)
+        addresses = dbc.fetchall()
+        dbc.close()
+        aliases = []
+        if addresses:
+            addr = u'@'.join
+            _dom = self._name
+            aliases = [addr((alias[0], _dom)) for alias in addresses]
+        return aliases
+
+    def get_relocated(self):
+        """Returns a list with all addresses of relocated users."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT address FROM relocated WHERE gid = %s ORDER BY '
+                    'address', self._gid)
+        addresses = dbc.fetchall()
+        dbc.close()
+        relocated = []
+        if addresses:
+            addr = u'@'.join
+            _dom = self._name
+            relocated = [addr((address[0], _dom)) for address in addresses]
+        return relocated
+
+    def get_aliase_names(self):
+        """Returns a list with all alias domain names of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
+                    'NOT is_primary ORDER BY domainname', self._gid)
+        anames = dbc.fetchall()
+        dbc.close()
+        aliasdomains = []
+        if anames:
+            aliasdomains = [aname[0] for aname in anames]
+        return aliasdomains
+
+
+def check_domainname(domainname):
+    """Returns the validated domain name `domainname`.
+
+    Throws an `DomainError`, if the domain name is too long or doesn't
+    look like a valid domain name (label.label.label).
+
+    """
+    if not RE_DOMAIN.match(domainname):
+        domainname = domainname.encode('idna')
+    if len(domainname) > 255:
+        raise DomErr(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
+    if not RE_DOMAIN.match(domainname):
+        raise DomErr(_(u"The domain name '%s' is invalid") % domainname,
+                     DOMAIN_INVALID)
+    return domainname
+
+
+def get_gid(dbh, domainname):
+    """Returns the group id of the domain *domainname*.
+
+    If the domain couldn't be found in the database 0 will be returned.
+    """
+    domainname = check_domainname(domainname)
+    dbc = dbh.cursor()
+    dbc.execute('SELECT gid FROM domain_name WHERE domainname=%s', domainname)
+    gid = dbc.fetchone()
+    dbc.close()
+    if gid:
+        return gid[0]
+    return 0
+
+
+def search(dbh, pattern=None, like=False):
+    """'Search' for domains by *pattern* in the database.
+
+    *pattern* may be a domain name or a partial domain name - starting
+    and/or ending with a '%' sign.  When the *pattern* starts or ends with
+    a '%' sign *like* has to be `True` to perform a wildcard search.
+    To retrieve all available domains use the arguments' default values.
+
+    This function returns a tuple with a list and a dict: (order, domains).
+    The order list contains the domains' gid, alphabetical sorted by the
+    primary domain name.  The domains dict's keys are the gids of the
+    domains. The value of item is a list.  The first list element contains
+    the primary domain name or `None`.  The elements [1:] contains the
+    names of alias domains.
+
+    Arguments:
+
+    `pattern` : basestring
+      a (partial) domain name (starting and/or ending with a "%" sign)
+    `like` : bool
+      should be `True` when *pattern* starts/ends with a "%" sign
+    """
+    if pattern and not like:
+        pattern = check_domainname(pattern)
+    sql = 'SELECT gid, domainname, is_primary FROM domain_name'
+    if pattern:
+        if like:
+            sql += " WHERE domainname LIKE '%s'" % pattern
+        else:
+            sql += " WHERE domainname = '%s'" % pattern
+    sql += ' ORDER BY is_primary DESC, domainname'
+    dbc = dbh.cursor()
+    dbc.execute(sql)
+    result = dbc.fetchall()
+    dbc.close()
+
+    gids = [domain[0] for domain in result if domain[2]]
+    domains = {}
+    for gid, domain, is_primary in result:
+        if is_primary:
+            if not gid in domains:
+                domains[gid] = [domain]
+            else:
+                domains[gid].insert(0, domain)
+        else:
+            if gid in gids:
+                if gid in domains:
+                    domains[gid].append(domain)
+                else:
+                    domains[gid] = [domain]
+            else:
+                gids.append(gid)
+                domains[gid] = [None, domain]
+    return gids, domains
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/emailaddress.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,103 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.emailaddress
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's EmailAddress class to handle e-mail addresses.
+"""
+import re
+
+from VirtualMailManager.domain import check_domainname
+from VirtualMailManager.constants import \
+     DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG
+from VirtualMailManager.errors import EmailAddressError as EAErr
+
+
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+_ = lambda msg: msg
+
+
+class EmailAddress(object):
+    """Simple class for validated e-mail addresses."""
+    __slots__ = ('_localpart', '_domainname')
+
+    def __init__(self, address):
+        """Creates a new instance from the string/unicode ``address``."""
+        assert isinstance(address, basestring)
+        self._localpart = None
+        self._domainname = None
+        self._chk_address(address)
+
+    @property
+    def localpart(self):
+        """The local-part of the address *local-part@domain*"""
+        return self._localpart
+
+    @property
+    def domainname(self):
+        """The domain part of the address *local-part@domain*"""
+        return self._domainname
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart == other.localpart and \
+                    self._domainname == other.domainname
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart != other.localpart or \
+                    self._domainname != other.domainname
+        return NotImplemented
+
+    def __hash__(self):
+        return hash((self._localpart.lower(), self._domainname.lower()))
+
+    def __repr__(self):
+        return "EmailAddress('%s@%s')" % (self._localpart, self._domainname)
+
+    def __str__(self):
+        return '%s@%s' % (self._localpart, self._domainname)
+
+    def _chk_address(self, address):
+        """Checks if the string ``address`` could be used for an e-mail
+        address.  If so, it will assign the corresponding values to the
+        attributes `_localpart` and `_domainname`."""
+        parts = address.split('@')
+        p_len = len(parts)
+        if p_len < 2:
+            raise EAErr(_(u"Missing the '@' sign in address %r") % address,
+                        INVALID_ADDRESS)
+        elif p_len > 2:
+            raise EAErr(_(u"Too many '@' signs in address %r") % address,
+                        INVALID_ADDRESS)
+        if not parts[0]:
+            raise EAErr(_(u'Missing local-part in address %r') % address,
+                        LOCALPART_INVALID)
+        if not parts[1]:
+            raise EAErr(_(u'Missing domain name in address %r') % address,
+                        DOMAIN_NO_NAME)
+        self._localpart = check_localpart(parts[0])
+        self._domainname = check_domainname(parts[1])
+
+
+def check_localpart(localpart):
+    """Returns the validated local-part `localpart`.
+
+    Throws a `EmailAddressError` if the local-part is too long or contains
+    invalid characters.
+    """
+    if len(localpart) > 64:
+        raise EAErr(_(u"The local-part '%s' is too long") % localpart,
+                    LOCALPART_TOO_LONG)
+    invalid_chars = set(RE_LOCALPART.findall(localpart))
+    if invalid_chars:
+        i_chars = u''.join((u'"%s" ' % c for c in invalid_chars))
+        raise EAErr(_(u"The local-part '%(l_part)s' contains invalid "
+                      u"characters: %(i_chars)s") % {'l_part': localpart,
+                    'i_chars': i_chars}, LOCALPART_INVALID)
+    return localpart
+
+del _
--- a/VirtualMailManager/errors.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/errors.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,9 +1,9 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2007 - 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.errors
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
 
     VMM's Exception classes
 """
@@ -20,6 +20,7 @@
     def __repr__(self):
         return '%s(%r, %r)' % (self.__class__.__name__, self.msg, self.code)
 
+
 class ConfigError(VMMError):
     """Exception class for configuration exceptions"""
     pass
--- a/VirtualMailManager/ext/Postconf.py	Wed Jul 28 01:03:56 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-"""
-    VirtualMailManager.ext.Postconf
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Wrapper class for Postfix's postconf.
-    Postconf instances can be used to read actual values of configuration
-    parameters or edit the value of a configuration parameter.
-
-    postconf.read(parameter) -> value
-    postconf.edit(parameter, value)
-"""
-
-import re
-from subprocess import Popen, PIPE
-
-from VirtualMailManager.errors import VMMError
-from VirtualMailManager.constants import VMM_ERROR
-
-_ = lambda msg: msg
-
-
-class Postconf(object):
-    """Wrapper class for Postfix's postconf."""
-    __slots__ = ('_bin', '_val')
-    _parameter_re = re.compile(r'^\w+$')
-    _variables_re = re.compile(r'\$\b\w+\b')
-
-    def __init__(self, postconf_bin):
-        """Creates a new Postconf instance.
-
-        Argument:
-
-        `postconf_bin` : str
-          absolute path to the Postfix postconf binary.
-        """
-        self._bin = postconf_bin
-        self._val = ''
-
-    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)
-        stdout, stderr = Popen((self._bin, '-e', parameter + '=' + str(value)),
-                               stderr=PIPE).communicate()
-        if stderr:
-            raise VMMError(stderr.strip(), VMM_ERROR)
-
-    def read(self, parameter, expand_vars=True):
-        """Returns the parameters value.
-
-        If expand_vars is True (default), all variables in the value will be
-        expanded:
-        e.g. mydestination: mail.example.com, localhost.example.com, localhost
-        Otherwise the value may contain one or more variables.
-        e.g. mydestination: $myhostname, localhost.$mydomain, localhost
-
-        Arguments:
-
-        `parameter` : str
-          the name of a Postfix configuration parameter.
-        `expand_vars` : bool
-          indicates if variables should be expanded or not, default True
-        """
-        self._check_parameter(parameter)
-        self._val = self._read(parameter)
-        if expand_vars:
-            self._expand_vars()
-        return self._val
-
-    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(_(u"The value '%s' doesn't look like a valid "
-                             u"postfix configuration parameter name.") %
-                           parameter, VMM_ERROR)
-
-    def _expand_vars(self):
-        """Expand the $variables in self._val to their values."""
-        while True:
-            pvars = set(self.__class__._variables_re.findall(self._val))
-            if not pvars:
-                break
-            if len(pvars) > 1:
-                self._expand_multi_vars(self._read_multi(pvars))
-                continue
-            pvars = pvars.pop()
-            self._val = self._val.replace(pvars, self._read(pvars[1:]))
-
-    def _expand_multi_vars(self, old_new):
-        """Replace all $vars in self._val with their values."""
-        for old, new in old_new.iteritems():
-            self._val = self._val.replace('$' + old, new)
-
-    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(), VMM_ERROR)
-        return stdout.strip()
-
-    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(), VMM_ERROR)
-        par_val = {}
-        for line in stdout.splitlines():
-            par, val = line.split(' = ')
-            par_val[par] = val
-        return par_val
-
-del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/ext/postconf.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,127 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.ext.postconf
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Wrapper class for Postfix's postconf.
+    Postconf instances can be used to read actual values of configuration
+    parameters or edit the value of a configuration parameter.
+
+    postconf.read(parameter) -> value
+    postconf.edit(parameter, value)
+"""
+
+import re
+from subprocess import Popen, PIPE
+
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.constants import VMM_ERROR
+
+_ = lambda msg: msg
+
+
+class Postconf(object):
+    """Wrapper class for Postfix's postconf."""
+    __slots__ = ('_bin', '_val')
+    _parameter_re = re.compile(r'^\w+$')
+    _variables_re = re.compile(r'\$\b\w+\b')
+
+    def __init__(self, postconf_bin):
+        """Creates a new Postconf instance.
+
+        Argument:
+
+        `postconf_bin` : str
+          absolute path to the Postfix postconf binary.
+        """
+        self._bin = postconf_bin
+        self._val = ''
+
+    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)
+        stdout, stderr = Popen((self._bin, '-e', parameter + '=' + str(value)),
+                               stderr=PIPE).communicate()
+        if stderr:
+            raise VMMError(stderr.strip(), VMM_ERROR)
+
+    def read(self, parameter, expand_vars=True):
+        """Returns the parameters value.
+
+        If expand_vars is True (default), all variables in the value will be
+        expanded:
+        e.g. mydestination: mail.example.com, localhost.example.com, localhost
+        Otherwise the value may contain one or more variables.
+        e.g. mydestination: $myhostname, localhost.$mydomain, localhost
+
+        Arguments:
+
+        `parameter` : str
+          the name of a Postfix configuration parameter.
+        `expand_vars` : bool
+          indicates if variables should be expanded or not, default True
+        """
+        self._check_parameter(parameter)
+        self._val = self._read(parameter)
+        if expand_vars:
+            self._expand_vars()
+        return self._val
+
+    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(_(u"The value '%s' doesn't look like a valid "
+                             u"postfix configuration parameter name.") %
+                           parameter, VMM_ERROR)
+
+    def _expand_vars(self):
+        """Expand the $variables in self._val to their values."""
+        while True:
+            pvars = set(self.__class__._variables_re.findall(self._val))
+            if not pvars:
+                break
+            if len(pvars) > 1:
+                self._expand_multi_vars(self._read_multi(pvars))
+                continue
+            pvars = pvars.pop()
+            self._val = self._val.replace(pvars, self._read(pvars[1:]))
+
+    def _expand_multi_vars(self, old_new):
+        """Replace all $vars in self._val with their values."""
+        for old, new in old_new.iteritems():
+            self._val = self._val.replace('$' + old, new)
+
+    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(), VMM_ERROR)
+        return stdout.strip()
+
+    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(), VMM_ERROR)
+        par_val = {}
+        for line in stdout.splitlines():
+            par, val = line.split(' = ')
+            par_val[par] = val
+        return par_val
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/handler.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,657 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+   VirtualMailManager.handler
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+   A wrapper class. It wraps round all other classes and does some
+   dependencies checks.
+
+   Additionally it communicates with the PostgreSQL database, creates
+   or deletes directories of domains or users.
+"""
+
+import os
+import re
+
+from shutil import rmtree
+from subprocess import Popen, PIPE
+
+from pyPgSQL import PgSQL  # python-pgsql - http://pypgsql.sourceforge.net
+
+from VirtualMailManager.account import Account
+from VirtualMailManager.alias import Alias
+from VirtualMailManager.aliasdomain import AliasDomain
+from VirtualMailManager.common import exec_ok
+from VirtualMailManager.config import Config as Cfg
+from VirtualMailManager.constants import \
+     ACCOUNT_EXISTS, ALIAS_EXISTS, CONF_NOFILE, CONF_NOPERM, CONF_WRONGPERM, \
+     DATABASE_ERROR, DOMAINDIR_GROUP_MISMATCH, DOMAIN_INVALID, \
+     FOUND_DOTS_IN_PATH, INVALID_ARGUMENT, MAILDIR_PERM_MISMATCH, \
+     NOT_EXECUTABLE, NO_SUCH_ACCOUNT, NO_SUCH_ALIAS, NO_SUCH_BINARY, \
+     NO_SUCH_DIRECTORY, NO_SUCH_RELOCATED, RELOCATED_EXISTS
+from VirtualMailManager.domain import Domain, get_gid
+from VirtualMailManager.emailaddress import EmailAddress
+from VirtualMailManager.errors import \
+     DomainError, NotRootError, PermissionError, VMMError
+from VirtualMailManager.mailbox import new as new_mailbox
+from VirtualMailManager.pycompat import any
+from VirtualMailManager.relocated import Relocated
+from VirtualMailManager.transport import Transport
+
+
+_ = lambda msg: msg
+
+CFG_FILE = 'vmm.cfg'
+CFG_PATH = '/root:/usr/local/etc:/etc'
+RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
+TYPE_ACCOUNT = 0x1
+TYPE_ALIAS = 0x2
+TYPE_RELOCATED = 0x4
+OTHER_TYPES = {
+    TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
+    TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
+    TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
+}
+
+
+class Handler(object):
+    """Wrapper class to simplify the access on all the stuff from
+    VirtualMailManager"""
+    __slots__ = ('_cfg', '_cfg_fname', '_dbh', '_warnings')
+
+    def __init__(self, skip_some_checks=False):
+        """Creates a new Handler instance.
+
+        ``skip_some_checks`` : bool
+            When a derived class knows how to handle all checks this
+            argument may be ``True``. By default it is ``False`` and
+            all checks will be performed.
+
+        Throws a NotRootError if your uid is greater 0.
+        """
+        self._cfg_fname = ''
+        self._warnings = []
+        self._cfg = None
+        self._dbh = None
+
+        if os.geteuid():
+            raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
+                               CONF_NOPERM)
+        if self._check_cfg_file():
+            self._cfg = Cfg(self._cfg_fname)
+            self._cfg.load()
+        if not skip_some_checks:
+            self._cfg.check()
+            self._chkenv()
+
+    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(_(u"Could not find '%(cfg_file)s' in: "
+                             u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
+                           'cfg_path': CFG_PATH}, CONF_NOFILE)
+
+    def _check_cfg_file(self):
+        """Checks the configuration file, returns bool"""
+        self._find_cfg_file()
+        fstat = os.stat(self._cfg_fname)
+        fmode = int(oct(fstat.st_mode & 0777))
+        if fmode % 100 and fstat.st_uid != fstat.st_gid or \
+           fmode % 10 and fstat.st_uid == fstat.st_gid:
+            raise PermissionError(_(u"wrong permissions for '%(file)s': "
+                                    u"%(perms)s\n`chmod 0600 %(file)s` would "
+                                    u"be great.") % {'file': self._cfg_fname,
+                                  'perms': fmode}, CONF_WRONGPERM)
+        else:
+            return True
+
+    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"""
+        basedir = self._cfg.dget('misc.base_directory')
+        if not os.path.exists(basedir):
+            old_umask = os.umask(0006)
+            os.makedirs(basedir, 0771)
+            os.chown(basedir, 0, 0)
+            os.umask(old_umask)
+        elif not os.path.isdir(basedir):
+            raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
+                             u"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, err:
+                if err.code is NO_SUCH_BINARY:
+                    raise VMMError(_(u"'%(binary)s' doesn't exist.\n"
+                                     u"(%(cfg_file)s: section 'bin', option "
+                                     u"'%(option)s')") % {'binary': val,
+                                   'cfg_file': self._cfg_fname, 'option': opt},
+                                   err.code)
+                elif err.code is NOT_EXECUTABLE:
+                    raise VMMError(_(u"'%(binary)s' is not executable.\n"
+                                     u"(%(cfg_file)s: section 'bin', option "
+                                     u"'%(option)s')") % {'binary': val,
+                                   'cfg_file': self._cfg_fname, 'option': opt},
+                                   err.code)
+                else:
+                    raise
+
+    def _db_connect(self):
+        """Creates a pyPgSQL.PgSQL.connection instance."""
+        if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
+                                  not self._dbh._isOpen):
+            try:
+                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.execute("SET NAMES 'UTF8'")
+                dbc.close()
+            except PgSQL.libpq.DatabaseError, err:
+                raise VMMError(str(err), DATABASE_ERROR)
+
+    def _chk_other_address_types(self, address, exclude):
+        """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
+        `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
+        by *exclude*.  If the *address* is known as one of the `TYPE_*`s
+        the according `TYPE_*` constant will be returned.  Otherwise 0 will
+        be returned."""
+        assert exclude in (TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED) and \
+                isinstance(address, EmailAddress)
+        if exclude is not TYPE_ACCOUNT:
+            account = Account(self._dbh, address)
+            if account:
+                return TYPE_ACCOUNT
+        if exclude is not TYPE_ALIAS:
+            alias = Alias(self._dbh, address)
+            if alias:
+                return TYPE_ALIAS
+        if exclude is not TYPE_RELOCATED:
+            relocated = Relocated(self._dbh, address)
+            if relocated:
+                return TYPE_RELOCATED
+        return 0
+
+    def _is_other_address(self, address, exclude):
+        """Checks if *address* is known for an Account (TYPE_ACCOUNT),
+        Alias (TYPE_ALIAS) or Relocated (TYPE_RELOCATED), except for
+        *exclude*.  Returns `False` if the address is not known for other
+        types.
+
+        Raises a `VMMError` if the address is known.
+        """
+        other = self._chk_other_address_types(address, exclude)
+        if not other:
+            return False
+        msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
+        raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
+                              'address': address}, OTHER_TYPES[other][1])
+
+    def _get_account(self, address):
+        """Return an Account instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Account(self._dbh, address)
+
+    def _get_alias(self, address):
+        """Return an Alias instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Alias(self._dbh, address)
+
+    def _get_relocated(self, address):
+        """Return a Relocated instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Relocated(self._dbh, address)
+
+    def _get_domain(self, domainname):
+        """Return a Domain instances for the given domain name (str)."""
+        self._db_connect()
+        return Domain(self._dbh, domainname)
+
+    def _get_disk_usage(self, directory):
+        """Estimate file space usage for the given directory.
+
+        Keyword arguments:
+        directory -- the directory to summarize recursively disk usage for
+        """
+        if self._isdir(directory):
+            return Popen([self._cfg.dget('bin.du'), "-hs", directory],
+                stdout=PIPE).communicate()[0].split('\t')[0]
+        else:
+            return 0
+
+    def _isdir(self, directory):
+        """Check if `directory` is a directory. Returns bool.
+        When `directory` isn't a directory, a warning will be appended to
+        _warnings."""
+        isdir = os.path.isdir(directory)
+        if not isdir:
+            self._warnings.append(_('No such directory: %s') % directory)
+        return isdir
+
+    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:]
+        os.chdir(self._cfg.dget('misc.base_directory'))
+        if not os.path.isdir(hashdir):
+            os.mkdir(hashdir, 0711)
+            os.chown(hashdir, 0, 0)
+        os.mkdir(os.path.join(hashdir, domdir),
+                 self._cfg.dget('domain.directory_mode'))
+        os.chown(domain.directory, 0, domain.gid)
+        os.chdir(cwd)
+
+    def _make_home(self, account):
+        """Create a home directory for the new Account *account*."""
+        os.umask(0007)
+        os.chdir(account.domain_directory)
+        os.mkdir('%s' % account.uid, self._cfg.dget('account.directory_mode'))
+        os.chown('%s' % account.uid, account.uid, account.gid)
+
+    def _delete_home(self, domdir, uid, gid):
+        """Delete a user's home directory."""
+        if uid > 0 and gid > 0:
+            userdir = '%s' % uid
+            if userdir.count('..') or domdir.count('..'):
+                raise VMMError(_(u'Found ".." in home directory path.'),
+                               FOUND_DOTS_IN_PATH)
+            if os.path.isdir(domdir):
+                os.chdir(domdir)
+                if os.path.isdir(userdir):
+                    mdstat = os.stat(userdir)
+                    if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
+                        raise VMMError(_(u'Detected owner/group mismatch in '
+                                         u'home directory.'),
+                                       MAILDIR_PERM_MISMATCH)
+                    rmtree(userdir, ignore_errors=True)
+                else:
+                    raise VMMError(_(u"No such directory: %s") %
+                                   os.path.join(domdir, userdir),
+                                   NO_SUCH_DIRECTORY)
+
+    def _delete_domain_dir(self, domdir, gid):
+        """Delete a domain's directory."""
+        if gid > 0:
+            if not self._isdir(domdir):
+                return
+            basedir = self._cfg.dget('misc.base_directory')
+            domdirdirs = domdir.replace(basedir + '/', '').split('/')
+            domdirparent = os.path.join(basedir, domdirdirs[0])
+            if basedir.count('..') or domdir.count('..'):
+                raise VMMError(_(u'Found ".." in domain directory path.'),
+                               FOUND_DOTS_IN_PATH)
+            if os.path.isdir(domdirparent):
+                os.chdir(domdirparent)
+                if os.lstat(domdirdirs[1]).st_gid != gid:
+                    raise VMMError(_(u'Detected group mismatch in domain '
+                                     u'directory.'), DOMAINDIR_GROUP_MISMATCH)
+                rmtree(domdirdirs[1], ignore_errors=True)
+
+    def has_warnings(self):
+        """Checks if warnings are present, returns bool."""
+        return bool(len(self._warnings))
+
+    def get_warnings(self):
+        """Returns a list with all available warnings and resets all
+        warnings.
+        """
+        ret_val = self._warnings[:]
+        del self._warnings[:]
+        return ret_val
+
+    def cfg_dget(self, option):
+        """Get the configured value of the *option* (section.option).
+        When the option was not configured its default value will be
+        returned."""
+        return self._cfg.dget(option)
+
+    def cfg_pget(self, option):
+        """Get the configured value of the *option* (section.option)."""
+        return self._cfg.pget(option)
+
+    def cfg_install(self):
+        """Installs the cfg_dget method as ``cfg_dget`` into the built-in
+        namespace."""
+        import __builtin__
+        assert 'cfg_dget' not in __builtin__.__dict__
+        __builtin__.__dict__['cfg_dget'] = self._cfg.dget
+
+    def domain_add(self, domainname, transport=None):
+        """Wrapper around Domain.set_transport() and Domain.save()"""
+        dom = self._get_domain(domainname)
+        if transport is None:
+            dom.set_transport(Transport(self._dbh,
+                              transport=self._cfg.dget('misc.transport')))
+        else:
+            dom.set_transport(Transport(self._dbh, transport=transport))
+        dom.set_directory(self._cfg.dget('misc.base_directory'))
+        dom.save()
+        self._make_domain_dir(dom)
+
+    def domain_transport(self, domainname, transport, force=None):
+        """Wrapper around Domain.update_transport()"""
+        if force is not None and force != 'force':
+            raise DomainError(_(u"Invalid argument: '%s'") % force,
+                              INVALID_ARGUMENT)
+        dom = self._get_domain(domainname)
+        trsp = Transport(self._dbh, transport=transport)
+        if force is None:
+            dom.update_transport(trsp)
+        else:
+            dom.update_transport(trsp, force=True)
+
+    def domain_delete(self, domainname, force=None):
+        """Wrapper around Domain.delete()"""
+        if force and force not in ('deluser', 'delalias', 'delall'):
+            raise DomainError(_(u"Invalid argument: '%s'") % force,
+                              INVALID_ARGUMENT)
+        dom = self._get_domain(domainname)
+        gid = dom.gid
+        domdir = dom.directory
+        if self._cfg.dget('domain.force_deletion') or force == 'delall':
+            dom.delete(True, True)
+        elif force == 'deluser':
+            dom.delete(deluser=True)
+        elif force == 'delalias':
+            dom.delete(delalias=True)
+        else:
+            dom.delete()
+        if self._cfg.dget('domain.delete_directory'):
+            self._delete_domain_dir(domdir, gid)
+
+    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']:
+            raise VMMError(_(u'Invalid argument: “%s”') % details,
+                           INVALID_ARGUMENT)
+        dom = self._get_domain(domainname)
+        dominfo = dom.get_info()
+        if dominfo['domainname'].startswith('xn--'):
+            dominfo['domainname'] += ' (%s)' % \
+                                     dominfo['domainname'].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())
+        else:
+            return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
+                    dom.get_aliases(), dom.get_relocated())
+
+    def aliasdomain_add(self, aliasname, domainname):
+        """Adds an alias domain to the domain.
+
+        Arguments:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        `domainname` : basestring
+          The name of the target domain
+        """
+        dom = self._get_domain(domainname)
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.set_destination(dom)
+        alias_dom.save()
+
+    def aliasdomain_info(self, aliasname):
+        """Returns a dict (keys: "alias" and "domain") with the names of
+        the alias domain and its primary domain."""
+        self._db_connect()
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        return alias_dom.info()
+
+    def aliasdomain_switch(self, aliasname, domainname):
+        """Modifies the target domain of an existing alias domain.
+
+        Arguments:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        `domainname` : basestring
+          The name of the new target domain
+        """
+        dom = self._get_domain(domainname)
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.set_destination(dom)
+        alias_dom.switch()
+
+    def aliasdomain_delete(self, aliasname):
+        """Deletes the given alias domain.
+
+        Argument:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        """
+        self._db_connect()
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.delete()
+
+    def domain_list(self, pattern=None):
+        """Wrapper around function search() from module Domain."""
+        from VirtualMailManager.domain import search
+        like = False
+        if pattern and (pattern.startswith('%') or pattern.endswith('%')):
+            like = True
+            if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
+                raise VMMError(_(u"The pattern '%s' contains invalid "
+                                 u"characters.") % pattern, DOMAIN_INVALID)
+        self._db_connect()
+        return search(self._dbh, pattern=pattern, like=like)
+
+    def user_add(self, emailaddress, password):
+        """Wrapper around Account.set_password() and Account.save()."""
+        acc = self._get_account(emailaddress)
+        acc.set_password(password)
+        acc.save()
+        oldpwd = os.getcwd()
+        self._make_home(acc)
+        mailbox = new_mailbox(acc)
+        mailbox.create()
+        folders = self._cfg.dget('mailbox.folders').split(':')
+        if any(folders):
+            bad = mailbox.add_boxes(folders,
+                                    self._cfg.dget('mailbox.subscribe'))
+            if bad:
+                self._warnings.append(_(u"Skipped mailbox folders:") +
+                                      '\n\t- ' + '\n\t- '.join(bad))
+        os.chdir(oldpwd)
+
+    def alias_add(self, aliasaddress, *targetaddresses):
+        """Creates a new `Alias` entry for the given *aliasaddress* with
+        the given *targetaddresses*."""
+        alias = self._get_alias(aliasaddress)
+        destinations = [EmailAddress(address) for address in targetaddresses]
+        warnings = []
+        destinations = alias.add_destinations(destinations, warnings)
+        if warnings:
+            self._warnings.append(_('Ignored destination addresses:'))
+            self._warnings.extend(('  * %s' % w for w in warnings))
+        for destination in destinations:
+            if get_gid(self._dbh, destination.domainname) and \
+               not self._chk_other_address_types(destination, TYPE_RELOCATED):
+                self._warnings.append(_(u"The destination account/alias '%s' "
+                                        u"doesn't exist.") % destination)
+
+    def user_delete(self, emailaddress, force=None):
+        """Wrapper around Account.delete(...)"""
+        if force not in (None, 'delalias'):
+            raise VMMError(_(u"Invalid argument: '%s'") % force,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        uid = acc.uid
+        gid = acc.gid
+        dom_dir = acc.domain_directory
+        acc_dir = acc.home
+        acc.delete(bool(force))
+        if self._cfg.dget('account.delete_directory'):
+            try:
+                self._delete_home(dom_dir, uid, gid)
+            except VMMError, err:
+                if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
+                                NO_SUCH_DIRECTORY):
+                    warning = _(u"""\
+The account has been successfully deleted from the database.
+    But an error occurred while deleting the following directory:
+    “%(directory)s”
+    Reason: %(reason)s""") % \
+                                {'directory': acc_dir, 'reason': err.msg}
+                    self._warnings.append(warning)
+                else:
+                    raise
+
+    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(_(u"The alias '%s' doesn't exist.") %
+                           alias.address, NO_SUCH_ALIAS)
+
+    def alias_delete(self, aliasaddress, targetaddress=None):
+        """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._get_alias(aliasaddress)
+        if targetaddress is None:
+            alias.delete()
+        else:
+            alias.del_destination(EmailAddress(targetaddress))
+
+    def user_info(self, emailaddress, details=None):
+        """Wrapper around Account.get_info(...)"""
+        if details not in (None, 'du', 'aliases', 'full'):
+            raise VMMError(_(u"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(_(u"The account '%s' doesn't 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
+
+    def user_by_uid(self, uid):
+        """Search for an Account by its *uid*.
+        Returns a dict (address, uid and gid) if a user could be found."""
+        from VirtualMailManager.account import get_account_by_uid
+        self._db_connect()
+        return get_account_by_uid(uid, self._dbh)
+
+    def user_password(self, emailaddress, password):
+        """Wrapper for Account.modify('password' ...)."""
+        if not isinstance(password, basestring) or not password:
+            raise VMMError(_(u"Could not accept password: '%s'") % password,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('password', password)
+
+    def user_name(self, emailaddress, name):
+        """Wrapper for Account.modify('name', ...)."""
+        if not isinstance(name, basestring) or not name:
+            raise VMMError(_(u"Could not accept name: '%s'") % name,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('name', name)
+
+    def user_transport(self, emailaddress, transport):
+        """Wrapper for Account.modify('transport', ...)."""
+        if not isinstance(transport, basestring) or not transport:
+            raise VMMError(_(u"Could not accept transport: '%s'") % transport,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('transport', transport)
+
+    def user_disable(self, emailaddress, service=None):
+        """Wrapper for Account.disable(service)"""
+        if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
+            raise VMMError(_(u"Could not accept service: '%s'") % service,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.disable(service)
+
+    def user_enable(self, emailaddress, service=None):
+        """Wrapper for Account.enable(service)"""
+        if service not in (None, 'all', 'imap', 'pop3', 'smtp', 'sieve'):
+            raise VMMError(_(u"Could not accept service: '%s'") % service,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' doesn't exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.enable(service)
+
+    def relocated_add(self, emailaddress, targetaddress):
+        """Creates a new `Relocated` entry in the database. If there is
+        already a relocated user with the given *emailaddress*, only the
+        *targetaddress* for the relocated user will be updated."""
+        relocated = self._get_relocated(emailaddress)
+        relocated.set_destination(EmailAddress(targetaddress))
+
+    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(_(u"The relocated user '%s' doesn't exist.") %
+                           relocated.address, NO_SUCH_RELOCATED)
+
+    def relocated_delete(self, emailaddress):
+        """Deletes the relocated user with the given *emailaddress* from
+        the database."""
+        relocated = self._get_relocated(emailaddress)
+        relocated.delete()
+
+del _
--- a/VirtualMailManager/mailbox.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/mailbox.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,7 +1,6 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.mailbox
     ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -15,7 +14,7 @@
 from binascii import a2b_base64, b2a_base64
 from subprocess import Popen, PIPE
 
-from VirtualMailManager.Account import Account
+from VirtualMailManager.account import Account
 from VirtualMailManager.common import is_dir
 from VirtualMailManager.errors import VMMError
 from VirtualMailManager.constants import VMM_ERROR
--- a/VirtualMailManager/maillocation.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/maillocation.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,7 +1,6 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2008 - 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.maillocation
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/VirtualMailManager/password.py	Wed Jul 28 01:03:56 2010 +0000
+++ b/VirtualMailManager/password.py	Wed Jul 28 02:08:03 2010 +0000
@@ -1,9 +1,9 @@
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2010, Pascal Volk
 # See COPYING for distribution information.
-
 """
     VirtualMailManager.password
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     VirtualMailManager's password module to generate password hashes from
     passwords or random passwords. This module provides following
@@ -24,7 +24,7 @@
     from VirtualMailManager.pycompat import hashlib
 
 from VirtualMailManager import ENCODING
-from VirtualMailManager.EmailAddress import EmailAddress
+from VirtualMailManager.emailaddress import EmailAddress
 from VirtualMailManager.common import get_unicode, version_str
 from VirtualMailManager.constants import VMM_ERROR
 from VirtualMailManager.errors import VMMError
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/relocated.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,114 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.relocated
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Relocated class to handle relocated users.
+"""
+
+from VirtualMailManager.domain import get_gid
+from VirtualMailManager.emailaddress import EmailAddress
+from VirtualMailManager.errors import RelocatedError as RErr
+from VirtualMailManager.constants import NO_SUCH_DOMAIN, \
+     NO_SUCH_RELOCATED, RELOCATED_ADDR_DEST_IDENTICAL, RELOCATED_EXISTS
+
+
+_ = lambda msg: msg
+
+
+class Relocated(object):
+    """Class to handle e-mail addresses of relocated users."""
+    __slots__ = ('_addr', '_dest', '_gid', '_dbh')
+
+    def __init__(self, dbh, address):
+        """Creates a new *Relocated* instance.  The ``address`` is the
+        old e-mail address of the user.
+
+        Use `setDestination()` to set/update the new address, where the
+        user has moved to.
+
+        """
+        assert isinstance(address, EmailAddress)
+        self._addr = address
+        self._dbh = dbh
+        self._gid = get_gid(self._dbh, self._addr.domainname)
+        if not self._gid:
+            raise RErr(_(u"The domain %r doesn't exist.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._dest = None
+
+        self.__load()
+
+    def __nonzero__(self):
+        """Returns `True` if the Relocated is known, `False` if it's new."""
+        return self._dest is not None
+
+    def __load(self):
+        """Loads the destination address from the database into the
+        `_dest` attribute.
+
+        """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT destination FROM relocated WHERE gid = %s AND '
+                    'address = %s', self._gid, self._addr.localpart)
+        destination = dbc.fetchone()
+        dbc.close()
+        if destination:
+            self._dest = EmailAddress(destination[0])
+
+    @property
+    def address(self):
+        """The Relocated's EmailAddress instance."""
+        return self._addr
+
+    def set_destination(self, destination):
+        """Sets/updates the new address of the relocated user."""
+        update = False
+        assert isinstance(destination, EmailAddress)
+        if self._addr == destination:
+            raise RErr(_(u'Address and destination are identical.'),
+                       RELOCATED_ADDR_DEST_IDENTICAL)
+        if self._dest:
+            if self._dest == destination:
+                raise RErr(_(u"The relocated user '%s' already exists.") %
+                           self._addr, RELOCATED_EXISTS)
+            else:
+                self._dest = destination
+                update = True
+        else:
+            self._dest = destination
+
+        dbc = self._dbh.cursor()
+        if not update:
+            dbc.execute('INSERT INTO relocated VALUES (%s, %s, %s)',
+                        self._gid, self._addr.localpart, str(self._dest))
+        else:
+            dbc.execute('UPDATE relocated SET destination = %s WHERE gid = %s '
+                        'AND address = %s', str(self._dest), self._gid,
+                        self._addr.localpart)
+        self._dbh.commit()
+        dbc.close()
+
+    def get_info(self):
+        """Returns the address to which mails should be sent."""
+        if not self._dest:
+            raise RErr(_(u"The relocated user '%s' doesn't exist.") %
+                       self._addr, NO_SUCH_RELOCATED)
+        return self._dest
+
+    def delete(self):
+        """Deletes the relocated entry from the database."""
+        if not self._dest:
+            raise RErr(_(u"The relocated user '%s' doesn't exist.") %
+                       self._addr, NO_SUCH_RELOCATED)
+        dbc = self._dbh.cursor()
+        dbc.execute('DELETE FROM relocated WHERE gid = %s AND address = %s',
+                    self._gid, self._addr.localpart)
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+        self._dest = None
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/transport.py	Wed Jul 28 02:08:03 2010 +0000
@@ -0,0 +1,96 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.transport
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Transport class to manage the transport for
+    domains and accounts.
+"""
+
+from VirtualMailManager.constants import UNKNOWN_TRANSPORT_ID
+from VirtualMailManager.errors import TransportError
+from VirtualMailManager.pycompat import any
+
+
+class Transport(object):
+    """A wrapper class that provides access to the transport table"""
+    __slots__ = ('_tid', '_transport', '_dbh')
+
+    def __init__(self, dbh, tid=None, transport=None):
+        """Creates a new Transport instance.
+
+        Either tid or transport must be specified. When both arguments
+        are given, tid will be used.
+
+        Keyword arguments:
+        dbh -- a pyPgSQL.PgSQL.connection
+        tid -- the id of a transport (int/long)
+        transport -- the value of the transport (str)
+
+        """
+        self._dbh = dbh
+        assert any((tid, transport))
+        if tid:
+            assert not isinstance(tid, bool) and isinstance(tid, (int, long))
+            self._tid = tid
+            self._loadByID()
+        else:
+            assert isinstance(transport, basestring)
+            self._transport = transport
+            self._loadByName()
+
+    @property
+    def tid(self):
+        """The transport's unique ID."""
+        return self._tid
+
+    @property
+    def transport(self):
+        """The transport's value, ex: 'dovecot:'"""
+        return self._transport
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._tid == other.tid
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._tid != other.tid
+        return NotImplemented
+
+    def __str__(self):
+        return self._transport
+
+    def _loadByID(self):
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT transport FROM transport WHERE tid=%s', self._tid)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._transport = result[0]
+        else:
+            raise TransportError(_(u'Unknown tid specified.'),
+                                 UNKNOWN_TRANSPORT_ID)
+
+    def _loadByName(self):
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT tid FROM transport WHERE transport = %s',
+                    self._transport)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._tid = result[0]
+        else:
+            self._save()
+
+    def _save(self):
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('transport_id')")
+        self._tid = dbc.fetchone()[0]
+        dbc.execute('INSERT INTO transport VALUES (%s, %s)', self._tid,
+                    self._transport)
+        self._dbh.commit()
+        dbc.close()