merged changes from default(b65c3abf9ca8). v0.7.x
authorPascal Volk <user@localhost.localdomain.org>
Sun, 09 Mar 2014 18:52:27 +0000
branchv0.7.x
changeset 740 aa346db76ee2
parent 738 935b4901d652 (diff)
parent 739 b65c3abf9ca8 (current diff)
child 743 51d834ac7923
merged changes from default(b65c3abf9ca8).
--- a/INSTALL	Sun Mar 09 18:42:58 2014 +0000
+++ b/INSTALL	Sun Mar 09 18:52:27 2014 +0000
@@ -1,26 +1,12 @@
 Installation Prerequisites
-You should already have installed and configured Postfix, Dovecot and
-PostgreSQL.
+You should already have installed and configured Postfix, Dovecot ≥ 1.2.0
+and PostgreSQL.
 
 The Virtual Mail Manager depends on:
-    - Python (>= 2.4.0)
-    - Psycopg 2¹ or pyPgSQL²
+    - Python (≥ 3.2)
+    - Psycopg¹ (≥ 2.0)
 
-If you are using Python <= 2.5.0:
-    - if you want to store your users' passwords as PLAIN-MD4 digest in
-      the database, vmm will try to use Crypto.Hash.MD4 from PyCrypto³.
-    - if you are using Dovecot >= v1.1.0 and you want to store your users'
-      passwords as SHA256 or SSHA256 hashes, vmm will try to use
-      Crypto.Hash.SHA256 from PyCrypto². For SHA256/SSHA256 you should have
-      at least use PyCrypto in version 2.1.0alpha1.
-
-    When the Crypto.Hash module couldn't be imported, vmm will use
-    dovecotpw/doveadm, if the misc.password_scheme setting in the vmm.cfg
-    is set to PLAIN-MD4, SHA256 or SSHA256
-
-[1] Psycopg: <http://initd.org/psycopg/> (Debian: python-psycopg2)
-[2] pyPgSQL: <http://pypgsql.sourceforge.net/> (Debian: python-pgsql)
-[3] PyCrypto: <http://www.pycrypto.org/> (Debian: python-crypto)
+[1] Psycopg: <http://initd.org/psycopg/> (Debian: python3-psycopg2)
 
 
 Create additionally a user and groups for improved security
@@ -32,7 +18,7 @@
 	doveauth
 
   This will create the doveauth user and group.
-  For Dovecot >= 2.0 we create also the group `dovemail'. Dovecot will assign
+  For Dovecot ≥ 2.0 we create also the group `dovemail'. Dovecot will assign
   this group to all Dovecot processes.
   On a Debian GNU/Linux bases system run:
 
@@ -43,7 +29,7 @@
 (for more details see:
     http://vmm.localdomain.org/installation/postgresql_configuration.html)
 
-* /etc/postgresql/8.4/main/pg_hba.conf
+* /etc/postgresql/9.1/main/pg_hba.conf
   [ if you prefer to connect via TCP/IP ]
     # IPv4 local connections:
     host    mailsys     +mailsys    127.0.0.1/32          md5
@@ -52,7 +38,7 @@
     local   mailsys     +mailsys                          md5
 
     # reload configuration
-    /etc/init.d/postgresql-8.4 force-reload
+    /etc/init.d/postgresql force-reload
 
 * Create a database superuser if necessary:
     # as root run: su - postgres
@@ -74,9 +60,7 @@
 
     # connect to the new database
     psql mailsys vmm -W -h 127.0.0.1
-    # either import the database structure for Dovecot v1.0.x/v1.1.x
-    \i vmm-y.x.z/pgsql/create_tables.pgsql
-    # or import the database structure for Dovecot v1.2.x/v2.x
+    # import the database structure for Dovecot ≥ 1.2.0
     \i vmm-x.y.z/pgsql/create_tables-dovecot-1.2.x.pgsql
     # leave psql
     \q
@@ -93,8 +77,8 @@
   chmod 751 /srv/mail/*
 
 
-For Dovecot >= 2.0 read the file Configure.Dovecot_2
-Configuring Dovecot v1.x
+For Dovecot ≥ 2.0 read the file Configure.Dovecot_2
+Configuring Dovecot v1.2.x
 
 * /etc/dovecot/dovecot.conf
     # all your other settings
@@ -137,8 +121,8 @@
 
 Provide a root SETUID copy of Dovecot's deliver agent for Postfix
 
-/!\ Only required with Dovecot v.1.x.
-    With Dovecot >= v2.0 use Dovecot's lmtp!
+/!\ Only required with Dovecot v.1.2.x.
+    With Dovecot ≥ v2.0 use Dovecot's lmtp!
 
     mkdir -p /usr/local/lib/dovecot
     chmod 700 /usr/local/lib/dovecot
@@ -154,7 +138,7 @@
 
 Configuring Postfix's master.cf
     
-/!\ Only required with Dovecot v.1.x.
+/!\ Only required with Dovecot v.1.2.x.
     # Add Dovecot's deliver agent
     dovecot   unix  -       n       n       -       -       pipe
       flags=DORhu user=nobody argv=/usr/local/lib/dovecot/deliver -f ${sender}
@@ -181,7 +165,7 @@
     virtual_mailbox_base = /
     virtual_mailbox_maps = ${proxysql}pgsql-virtual_mailbox_maps.cf
 
-    # dovecot LDA (only recommended with Dovecot v1.x)
+    # dovecot LDA (only recommended with Dovecot v1.2.x)
     #dovecot_destination_recipient_limit = 1
     #virtual_transport = dovecot:
 
@@ -219,7 +203,7 @@
     # configure the Virtual Mail Manager
     # vmm.cfg(5) - configuration file for vmm
     #
-    # For Dovecot v1.x use 'dovecot:' as domain.transport
+    # For Dovecot v1.2.x use 'dovecot:' as domain.transport
     # When using Dovecot v2.x use 'lmtp:unix:private/dovecot-lmtp' as
     # domain.transport
     vmm configure
--- a/README	Sun Mar 09 18:42:58 2014 +0000
+++ b/README	Sun Mar 09 18:52:27 2014 +0000
@@ -18,7 +18,7 @@
 • General features
 
   ‣ Unicode/UTF-8 capable (input/storage/output)
-  ‣ supports IDN_ (also ccTLDs/ccIDNs and 'new' gTLDs)
+  ‣ supports IDN_ (also `IDN ccTLDs`_/ccIDNs and 'new' gTLDs)
   ‣ supports the mailbox format Maildir_ and Dovecot's own high-performance
     mailbox formats single- and multi-\ dbox_
   ‣ configurable basic mailbox structure, including sub-mailboxes
@@ -76,8 +76,8 @@
 
 Installation Prerequisites
 ==========================
-You already should have installed and configured Postfix and Dovecot with
-PostgreSQL support. You also need access to a local or remote PostgreSQL
+You already should have installed and configured Postfix and Dovecot (≥ 2.0.0)
+with PostgreSQL support. You also need access to a local or remote PostgreSQL
 server.
 
 To verify that your Dovecot and Postfix installation has support for
@@ -90,22 +90,9 @@
         hostname ~ # dovecot --build-options | grep postgresql
         SQL drivers: mysql postgresql sqlite
 
-vmm depends on Python (≥ 2.4.0) and Psycopg_ (≥ 2.0) or pyPgSQL_ (≥ 2.5.1).
-Psycopg and pyPgSQL are depending on parts of the *eGenix.com mx Base
-Distribution* (mxDateTime_ and mxTools_).
-
-If you are using Python ≤ 2.5.0:
-
-  • if you want to store your users' passwords as ``PLAIN-MD4`` digest in the
-    database, vmm will try to use ``Crypto.Hash.MD4`` from PyCrypto_
-  • if you are using Dovecot ≥ v1.1.0 and you want to store your users'
-    passwords as ``SHA256`` or ``SSHA256`` hashes, vmm will try to use
-    ``Crypto.Hash.SHA256`` from PyCrypto. For ``SHA256``/``SSHA256`` you
-    should have installed PyCrypto, at least in version 2.1.0alpha1.
-
-  When the Crypto.Hash module couldn't be imported, vmm will use
-  dovecotpw/doveadm, if  the *misc.password_scheme* setting in your *vmm.cfg*
-  is set to ``PLAIN-MD4``, ``SHA256`` or ``SSHA256``.
+Dependences
+-----------
+vmm (≥ 0.7.0) depends on Python (≥ 3.2) and Psycopg_ (≥ 2.0).
 
 Source code
 ===========
@@ -138,18 +125,16 @@
 details see the `COPYING` file.
 
 .. External references
+.. _IDN ccTLDs: \
+ http://en.wikipedia.org/wiki/Internationalized_country_code_top-level_domain
 .. _dbox: http://wiki2.dovecot.org/MailboxFormat/dbox
 .. _Dovecot: http://dovecot.org/
 .. _IDN: http://en.wikipedia.org/wiki/Internationalized_domain_name
 .. _Maildir: http://wiki2.dovecot.org/MailboxFormat/Maildir
 .. _Mercurial: http://mercurial.selenic.com/
-.. _mxDateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/
-.. _mxTools: http://www.egenix.com/products/python/mxBase/mxTools/
 .. _Postfix: http://www.postfix.org/
 .. _PostgreSQL: http://www.postgresql.org/
 .. _Psycopg: http://initd.org/psycopg/
-.. _PyCrypto: http://www.pycrypto.org/
-.. _pyPgSQL: http://pypgsql.sourceforge.net/
 .. _Python: http://www.python.org/
 .. _relocated: http://www.postfix.org/relocated.5.html
 .. _transport: http://www.postfix.org/transport.5.html
--- a/VirtualMailManager/__init__.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/__init__.py	Sun Mar 09 18:52:27 2014 +0000
@@ -32,4 +32,4 @@
     locale.setlocale(locale.LC_ALL, 'C')
 ENCODING = locale.nl_langinfo(locale.CODESET)
 
-gettext.install('vmm', '/usr/local/share/locale', unicode=1)
+gettext.install('vmm', '/usr/local/share/locale')
--- a/VirtualMailManager/account.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/account.py	Sun Mar 09 18:52:27 2014 +0000
@@ -43,7 +43,7 @@
 
         Arguments:
 
-        `dbh` : pyPgSQL.PgSQL.Connection
+        `dbh` : psycopg2._psycopg.connection
           A database connection for the database access.
         `address` : VirtualMailManager.EmailAddress.EmailAddress
           The e-mail address of the (new) Account.
@@ -57,7 +57,7 @@
             # TP: Hm, what “quotation marks” should be used?
             # If you are unsure have a look at:
             # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
-            raise AErr(_(u"The domain '%s' does not exist.") %
+            raise AErr(_("The domain '%s' does not exist.") %
                        self._addr.domainname, NO_SUCH_DOMAIN)
         self._uid = 0
         self._mail = None
@@ -69,7 +69,7 @@
         self._new = True
         self._load()
 
-    def __nonzero__(self):
+    def __bool__(self):
         """Returns `True` if the Account is known, `False` if it's new."""
         return not self._new
 
@@ -86,11 +86,7 @@
             self._uid, _mid, _qid, _ssid, _tid, _note = result
 
             def load_helper(ctor, own, field, dbresult):
-                #  Py25: cur = None if own is None else getattr(own, field)
-                if own is None:
-                    cur = None
-                else:
-                    cur = getattr(own, field)
+                cur = None if own is None else getattr(own, field)
                 if cur != dbresult:
                     kwargs = {field: dbresult}
                     if dbresult is None:
@@ -120,8 +116,8 @@
         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.") % {
+            raise AErr(_("The mailbox format '%(mbfmt)s' requires Dovecot "
+                         ">= v%(version)s.") % {
                        'mbfmt': maillocation.mbformat,
                        'version': version_str(maillocation.dovecot_version)},
                        INVALID_MAIL_LOCATION)
@@ -137,7 +133,7 @@
 
         `column` : basestring
           Name of the table column. Currently: qid, ssid and tid
-        `value` : long
+        `value` : int
           The referenced key
         """
         if column not in ('qid', 'ssid', 'tid'):
@@ -163,7 +159,7 @@
         """Raise an AccountError if the Account is new - not yet saved in the
         database."""
         if self._new:
-            raise AErr(_(u"The account '%s' does not exist.") % self._addr,
+            raise AErr(_("The account '%s' does not exist.") % self._addr,
                        NO_SUCH_ACCOUNT)
 
     @property
@@ -219,10 +215,10 @@
           The password for the new Account.
         """
         if not self._new:
-            raise AErr(_(u"The account '%s' already exists.") % self._addr,
+            raise AErr(_("The account '%s' already exists.") % self._addr,
                        ACCOUNT_EXISTS)
-        if not isinstance(password, basestring) or not password:
-            raise AErr(_(u"Could not accept password: '%s'") % password,
+        if not isinstance(password, str) or not password:
+            raise AErr(_("Could not accept password: '%s'") % password,
                        ACCOUNT_MISSING_PASSWORD)
         self._passwd = password
 
@@ -234,36 +230,29 @@
         `note` : basestring or None
           The note, or None to remove
         """
-        assert note is None or isinstance(note, basestring)
+        assert note is None or isinstance(note, str)
         self._note = note
 
     def save(self):
         """Save the new Account in the database."""
         if not self._new:
-            raise AErr(_(u"The account '%s' already exists.") % self._addr,
+            raise AErr(_("The account '%s' already exists.") % self._addr,
                        ACCOUNT_EXISTS)
         if not self._passwd:
-            raise AErr(_(u"No password set for account: '%s'") % self._addr,
+            raise AErr(_("No password set for account: '%s'") % self._addr,
                        ACCOUNT_MISSING_PASSWORD)
         self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
                                    directory=cfg_dget('mailbox.root')))
         dbc = self._dbh.cursor()
-        qid = ssid = tid = None
-        if self._qlimit:
-            qid = self._qlimit.qid
-        if self._services:
-            ssid = self._services.ssid
-        if self._transport:
-            tid = self._transport.tid
         dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
                     'qid, ssid, tid, note) '
                     'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)',
                     (self._addr.localpart,
                      pwhash(self._passwd, user=self._addr), self._uid,
-                     self._domain.gid, self._mail.mid, qid, ssid, tid,
-#                     self._qlimit.qid if self._qlimit else None,
-#                     self._services.ssid if self._services else None,
-#                     self._transport.tid if self._transport else None,
+                     self._domain.gid, self._mail.mid,
+                     self._qlimit.qid if self._qlimit else None,
+                     self._services.ssid if self._services else None,
+                     self._transport.tid if self._transport else None,
                      self._note))
         self._dbh.commit()
         dbc.close()
@@ -272,25 +261,45 @@
     def modify(self, field, value):
         """Update the Account's *field* to the new *value*.
 
-        Possible values for *field* are: 'name', 'password', 'note'.
+        Possible values for *field* are: 'name', 'note' and 'pwhash'.
 
         Arguments:
 
-        `field` : basestring
-          The attribute name: 'name', 'password' or 'note'
-        `value` : basestring
+        `field` : str
+          The attribute name: 'name', 'note' or 'pwhash'
+        `value` : str
           The new value of the attribute.
         """
-        if field not in ('name', 'password', 'note'):
-            raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
+        if field not in ('name', 'note', 'pwhash'):
+            raise AErr(_("Unknown field: '%s'") % field, INVALID_ARGUMENT)
+        if field == 'pwhash':
+            field = 'passwd'
         self._chk_state()
         dbc = self._dbh.cursor()
-        if field == 'password':
-            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
-                        (pwhash(value, user=self._addr), self._uid))
-        else:
-            dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % field,
-                        (value, self._uid))
+        dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % field,
+                    (value, self._uid))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def update_password(self, password, scheme=None):
+        """Update the Account's password.
+
+        The given *password* will be hashed using password.pwhash.
+        When no *scheme* is specified, the configured scheme
+        (misc.password_scheme) will be used.
+
+        Arguments:
+
+        `password' : str
+          The Account's new plain text password
+        `scheme' : str
+          The password scheme used for password hashing; default None
+        """
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
+                    (pwhash(password, scheme, self._addr), self.uid))
         if dbc.rowcount > 0:
             self._dbh.commit()
         dbc.close()
@@ -303,9 +312,6 @@
         `quotalimit` : VirtualMailManager.quotalimit.QuotaLimit
           the new quota limit of the domain.
         """
-        if cfg_dget('misc.dovecot_version') < 0x10102f00:
-            raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
-                             u'Dovecot >= v1.1.2.'), VMM_ERROR)
         self._chk_state()
         if quotalimit == self._qlimit:
             return
@@ -364,7 +370,7 @@
             fmt = format_domain_default
 
         ret = {}
-        for service, state in services.iteritems():
+        for service, state in services.items():
             # TP: A service (e.g. pop3 or imap) may be enabled/usable or
             # disabled/unusable for a user.
             ret[service] = fmt((_('disabled'), _('enabled'))[state])
@@ -387,7 +393,7 @@
         info = dbc.fetchone()
         dbc.close()
         if info:
-            info = dict(zip(('name', 'uq_bytes', 'uq_messages'), info))
+            info = dict(list(zip(('name', 'uq_bytes', 'uq_messages'), info)))
             info.update(self._get_info_serviceset())
             info['address'] = self._addr
             info['gid'] = self._domain.gid
@@ -406,7 +412,7 @@
             info['uid'] = self._uid
             return info
         # nearly impossible‽
-        raise AErr(_(u"Could not fetch information for account: '%s'") %
+        raise AErr(_("Could not fetch information for account: '%s'") %
                    self._addr, NO_SUCH_ACCOUNT)
 
     def get_aliases(self):
@@ -450,8 +456,8 @@
             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'.") %
+                raise AErr(_("There are %(count)d aliases with the "
+                             "destination address '%(address)s'.") %
                            {'count': a_count, 'address': self._addr},
                            ALIAS_PRESENT)
             dbc.execute('DELETE FROM users WHERE uid = %s', (self._uid,))
@@ -471,17 +477,17 @@
 
     Argument:
 
-    `uid` : long
+    `uid` : int
       The Account unique ID.
-    `dbh` : pyPgSQL.PgSQL.Connection
+    `dbh` : psycopg2._psycopg.connection
       a database connection for the database access.
     """
     try:
-        uid = long(uid)
+        uid = int(uid)
     except ValueError:
-        raise AErr(_(u'UID must be an int/long.'), INVALID_ARGUMENT)
+        raise AErr(_('UID must be an integer.'), INVALID_ARGUMENT)
     if uid < 1:
-        raise AErr(_(u'UID must be greater than 0.'), INVALID_ARGUMENT)
+        raise AErr(_('UID must be greater than 0.'), INVALID_ARGUMENT)
     dbc = dbh.cursor()
     dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address, "
                 "uid, users.gid, note FROM users LEFT JOIN domain_name ON "
@@ -490,9 +496,9 @@
     info = dbc.fetchone()
     dbc.close()
     if not info:
-        raise AErr(_(u"There is no account with the UID: '%d'") % uid,
+        raise AErr(_("There is no account with the UID: '%d'") % uid,
                    NO_SUCH_ACCOUNT)
-    info = dict(zip(('address', 'uid', 'gid', 'note'), info))
+    info = dict(list(zip(('address', 'uid', 'gid', 'note'), info)))
     return info
 
 del _, cfg_dget
--- a/VirtualMailManager/alias.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/alias.py	Sun Mar 09 18:52:27 2014 +0000
@@ -13,7 +13,6 @@
      EmailAddress, DestinationEmailAddress as DestAddr
 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
 
@@ -32,7 +31,7 @@
         self._dbh = dbh
         self._gid = get_gid(self._dbh, self._addr.domainname)
         if not self._gid:
-            raise AErr(_(u"The domain '%s' does not exist.") %
+            raise AErr(_("The domain '%s' does not exist.") %
                        self._addr.domainname, NO_SUCH_DOMAIN)
         self._dests = []
 
@@ -52,20 +51,20 @@
     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'))
+        limit = int(postconf.read('virtual_alias_expansion_limit'))
         dcount = len(self._dests)
         failed = False
         if dcount == limit or dcount + count_new > limit:
             failed = True
             errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
+"""Cannot 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"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
+"""Cannot 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.""")
@@ -152,7 +151,7 @@
             if not warnings is None:
                 warnings.append(self._addr)
         if not self._dests:
-            raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+            raise AErr(_("The alias '%s' does not exist.") % self._addr,
                        NO_SUCH_ALIAS)
         unknown = destinations.difference(set(self._dests))
         if unknown:
@@ -160,8 +159,8 @@
             if not warnings is None:
                 warnings.extend(unknown)
         if not destinations:
-            raise AErr(_(u"No suitable destinations left to remove from alias "
-                         u"'%s'.") % self._addr, NO_SUCH_ALIAS)
+            raise AErr(_("No suitable destinations left to remove from alias "
+                         "'%s'.") % self._addr, NO_SUCH_ALIAS)
         self._delete(destinations)
         for destination in destinations:
             self._dests.remove(destination)
@@ -169,14 +168,14 @@
     def get_destinations(self):
         """Returns an iterator for all destinations of the alias."""
         if not self._dests:
-            raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+            raise AErr(_("The alias '%s' does not 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' does not exist.") % self._addr,
+            raise AErr(_("The alias '%s' does not exist.") % self._addr,
                        NO_SUCH_ALIAS)
         self._delete()
         del self._dests[:]
--- a/VirtualMailManager/aliasdomain.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/aliasdomain.py	Sun Mar 09 18:52:27 2014 +0000
@@ -27,7 +27,7 @@
 
         Arguments:
 
-        `dbh` : pyPgSQL.PgSQL.Connection
+        `dbh` : psycopg2._psycopg.connection
           a database connection for the database access
         `domainname` : basestring
           the name of the AliasDomain"""
@@ -47,7 +47,7 @@
         dbc.close()
         if result:
             if result[1]:
-                raise ADErr(_(u"The domain '%s' is a primary domain.") %
+                raise ADErr(_("The domain '%s' is a primary domain.") %
                             self._name, ALIASDOMAIN_ISDOMAIN)
             self._gid = result[0]
 
@@ -66,13 +66,13 @@
     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.") %
+            raise ADErr(_("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.'),
+            raise ADErr(_('No destination domain set for the alias domain.'),
                         ALIASDOMAIN_NO_DOMDEST)
         if self._domain.gid < 1:
-            raise ADErr(_(u"The target domain '%s' does not exist.") %
+            raise ADErr(_("The target domain '%s' does not exist.") %
                         self._domain.name, NO_SUCH_DOMAIN)
         dbc = self._dbh.cursor()
         dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary) '
@@ -85,7 +85,7 @@
         """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' does not exist.") %
+            raise ADErr(_("The alias domain '%s' does not exist.") %
                         self._name, NO_SUCH_ALIASDOMAIN)
         dbc = self._dbh.cursor()
         dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
@@ -95,25 +95,25 @@
         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)
+            raise ADErr(_('There is no primary domain for the alias domain '
+                          "'%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.'),
+            raise ADErr(_('No destination domain set for the alias domain.'),
                         ALIASDOMAIN_NO_DOMDEST)
         if self._domain.gid < 1:
-            raise ADErr(_(u"The target domain '%s' does not exist.") %
+            raise ADErr(_("The target domain '%s' does not exist.") %
                         self._domain.name, NO_SUCH_DOMAIN)
         if self._gid < 1:
-            raise ADErr(_(u"The alias domain '%s' does not exist.") %
+            raise ADErr(_("The alias domain '%s' does not 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'.") %
+            raise ADErr(_("The alias domain '%(alias)s' is already assigned "
+                          "to the domain '%(domain)s'.") %
                         {'alias': self._name, 'domain': self._domain.name},
                         ALIASDOMAIN_EXISTS)
         dbc = self._dbh.cursor()
@@ -130,7 +130,7 @@
         Raises an AliasDomainError if the AliasDomain doesn't exist.
         """
         if self._gid < 1:
-            raise ADErr(_(u"The alias domain '%s' does not exist.") %
+            raise ADErr(_("The alias domain '%s' does not exist.") %
                         self._name, NO_SUCH_ALIASDOMAIN)
         dbc = self._dbh.cursor()
         dbc.execute('DELETE FROM domain_name WHERE domainname = %s AND NOT '
--- a/VirtualMailManager/catchall.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/catchall.py	Sun Mar 09 18:52:27 2014 +0000
@@ -23,7 +23,6 @@
      EmailAddress, DestinationEmailAddress as DestAddr
 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
 
@@ -41,7 +40,7 @@
         self._dbh = dbh
         self._gid = get_gid(self._dbh, self.domain)
         if not self._gid:
-            raise AErr(_(u"The domain '%s' does not exist.") %
+            raise AErr(_("The domain '%s' does not exist.") %
                        self.domain, NO_SUCH_DOMAIN)
         self._dests = []
 
@@ -60,13 +59,13 @@
     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'))
+        limit = int(postconf.read('virtual_alias_expansion_limit'))
         dcount = len(self._dests)
         failed = False
         if dcount == limit or dcount + count_new > limit:
             failed = True
             errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to catch-all alias for
+"""Cannot add %(count_new)i new destination(s) to catch-all alias for
 domain '%(domain)s'. Currently this alias expands into %(count)i/%(limit)i
 recipients. %(count_new)i additional destination(s) will render this alias
 unusable.
@@ -74,7 +73,7 @@
         elif dcount > limit:
             failed = True
             errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to catch-all alias for domain
+"""Cannot add %(count_new)i new destination(s) to catch-all alias for domain
 '%(domain)s'. This alias already exceeds its expansion limit \
 (%(count)i/%(limit)i).
 So its unusable, all messages addressed to this alias will be bounced.
@@ -148,16 +147,16 @@
         if not warnings is None:
             assert isinstance(warnings, list)
         if not self._dests:
-            raise AErr(_(u"There are no catch-all aliases defined for "
-                         u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+            raise AErr(_("There are no catch-all aliases defined for "
+                         "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
         unknown = destinations.difference(set(self._dests))
         if unknown:
             destinations.intersection_update(set(self._dests))
             if not warnings is None:
                 warnings.extend(unknown)
         if not destinations:
-            raise AErr(_(u"No suitable destinations left to remove from the "
-                         u"catch-all alias of domain '%s'.") % self._domain,
+            raise AErr(_("No suitable destinations left to remove from the "
+                         "catch-all alias of domain '%s'.") % self._domain,
                        NO_SUCH_ALIAS)
         self._delete(destinations)
         for destination in destinations:
@@ -166,15 +165,15 @@
     def get_destinations(self):
         """Returns an iterator for all destinations of the catchall alias."""
         if not self._dests:
-            raise AErr(_(u"There are no catch-all aliases defined for "
-                         u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+            raise AErr(_("There are no catch-all aliases defined for "
+                         "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
         return iter(self._dests)
 
     def delete(self):
         """Deletes all catchall destinations for the domain."""
         if not self._dests:
-            raise AErr(_(u"There are no catch-all aliases defined for "
-                         u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+            raise AErr(_("There are no catch-all aliases defined for "
+                         "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
         self._delete()
         del self._dests[:]
 
--- a/VirtualMailManager/cli/__init__.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/cli/__init__.py	Sun Mar 09 18:52:27 2014 +0000
@@ -19,19 +19,20 @@
 from VirtualMailManager.errors import VMMError
 
 
-__all__ = ('prog', 'get_winsize', 'read_pass', 'w_err', 'w_std')
+__all__ = ('get_winsize', 'read_pass', 'w_err', 'w_std')
 
 _ = lambda msg: msg
 _std_write = os.sys.stdout.write
 _err_write = os.sys.stderr.write
-prog = os.path.basename(os.sys.argv[0])
 
 
 def w_std(*args):
     """Writes a line for each arg of *args*, encoded in the current
     ENCODING, to stdout.
     """
-    _std_write('\n'.join(a.encode(ENCODING, 'replace') for a in args) + '\n')
+    _std_write('\n'.join(arg.encode(ENCODING, 'replace').decode(ENCODING,
+                                                                'replace')
+               for arg in args) + '\n')
 
 
 def w_err(code, *args):
@@ -40,7 +41,9 @@
     This function optionally interrupts the program execution if *code*
     does not equal to 0. *code* will be used as the system exit status.
     """
-    _err_write('\n'.join(a.encode(ENCODING, 'replace') for a in args) + '\n')
+    _err_write('\n'.join(arg.encode(ENCODING, 'replace').decode(ENCODING,
+                                                                'replace')
+               for arg in args) + '\n')
     if code:
         os.sys.exit(code)
 
@@ -75,24 +78,24 @@
     Throws a VMMError after the third failure.
     """
     # TP: Please preserve the trailing space.
-    readp_msg0 = _(u'Enter new password: ').encode(ENCODING, 'replace')
+    readp_msg0 = _('Enter new password: ')
     # TP: Please preserve the trailing space.
-    readp_msg1 = _(u'Retype new password: ').encode(ENCODING, 'replace')
+    readp_msg1 = _('Retype new password: ')
     mismatched = True
     failures = 0
     while mismatched:
         if failures > 2:
-            raise VMMError(_(u'Too many failures - try again later.'),
+            raise VMMError(_('Too many failures - try again later.'),
                            VMM_TOO_MANY_FAILURES)
         clear0 = getpass(prompt=readp_msg0)
         clear1 = getpass(prompt=readp_msg1)
         if clear0 != clear1:
             failures += 1
-            w_err(0, _(u'Sorry, passwords do not match.'))
+            w_err(0, _('Sorry, passwords do not match.'))
             continue
         if not clear0:
             failures += 1
-            w_err(0, _(u'Sorry, empty passwords are not permitted.'))
+            w_err(0, _('Sorry, empty passwords are not permitted.'))
             continue
         mismatched = False
     return clear0
--- a/VirtualMailManager/cli/clihelp.py	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2012 - 2014, Pascal Volk
-# See COPYING for distribution information.
-"""
-    VirtualMailManager.cli.vmmhelp
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Virtual Mail Manager's command line help.
-"""
-
-_ = lambda msg: msg
-
-help_msgs = {
-# TP: There are some words enclosed within angle brackets '<'word'>'. They
-# are used to indicate replaceable arguments. Please do not translate them.
-#
-# The descriptions of subcommands may contain the both keywords 'domain'
-# and 'force', enclosed within single quotes. Please keep them as they are.
-#
-    # TP: description of subcommand configget
-    'configget': (_(u"""This subcommand is used to display the actual value
-of the given configuration <option>."""),),
-    # TP: description of subcommand configset
-    'configset': (_(u"""Use this subcommand to set or update a single
-configuration option's value. <option> is the configuration option, <value>
-is the <option>'s new value."""),
-_(u"""Note: This subcommand will create a new vmm.cfg without any comments.
-Your current configuration file will be backed as vmm.cfg.bak."""),),
-    # TP: description of subcommand configure
-    'configure': (_(u"""Starts the interactive configuration for all
-configuration sections."""),
-_(u"""In this process the currently set value of each option will be displayed
-in square brackets. If no value is configured, the default value of each
-option will be displayed in square brackets. Press the return key, to accept
-the displayed value."""),
-_(u"""If the optional argument <section> is given, only the configuration
-options from the given section will be displayed and will be configurable.
-The following sections are available:
-"""),
-"""    account, bin, database, domain, mailbox, misc""",
-_(u"""All configuration options are described in vmm.cfg(5)."""),
-_(u"""Note: This subcommand will create a new vmm.cfg without any comments.
-Your current configuration file will be backed as vmm.cfg.bak."""),),
-    # TP: description of subcommand getuser
-    'getuser': (_(u"""If only the <uid> is available, for example from process
-list, the subcommand getuser will show the user's address."""),),
-    # TP: description of subcommand listaddresses
-    'listaddresses': (_(u"""This command lists all defined addresses.
-Addresses belonging to alias-domains are prefixed with a '-', addresses of
-regular domains with a '+'. Additionally, the letters 'u', 'a', and 'r'
-indicate the type of each address: user, alias and relocated respectively.
-The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
-    # TP: description of subcommand listaliases
-    'listaliases': (_(u"""This command lists all defined aliases. Aliases
-belonging to alias-domains are prefixed with a '-', addresses of regular
-domains with a '+'. The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
-    # TP: description of subcommand listdomains
-    'listdomains': (_(u"""This subcommand lists all available domains. All
-domain names will be prefixed either with `[+]', if the domain is a primary
-domain, or with `[-]', if it is an alias domain name. The output can be
-limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
-    # TP: description of subcommand listpwschemes
-    'listpwschemes': (_(u"""This subcommand lists all password schemes which
-could be used in the vmm.cfg as value of the misc.password_scheme option.
-The output varies, depending on the used Dovecot version and the system's
-libc."""),
-_(u"""When your Dovecot installation isn't too old, you will see additionally
-a few usable encoding suffixes. One of them can be appended to the password
-scheme."""),),
-    # TP: description of subcommand listrelocated
-    'listrelocated': (_(u"""This command lists all defined relocated addresses.
-Relocated entries belonging to alias-domains are prefixed with a '-', addresses
-of regular domains with a '+'. The output can be limited with an optional
-<pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
-    # TP: description of subcommand listusers
-    'listusers': (_(u"""This command lists all user accounts. User accounts
-belonging to alias-domains are prefixed with a '-', addresses of regular
-domains with a '+'. The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the pattern."""),),
-    # TP: description of subcommand version
-    'version': (_(u"""Prints vmm's version and copyright information to stdout.
-After this vmm exits."""),),
-    # TP: description of subcommand domainadd
-    'domainadd': (_(u"""Adds the new domain into the database and creates the
-domain directory."""),
-_(u"""If the optional argument <transport> is given, it will override the
-default transport (domain.transport) from vmm.cfg. The specified <transport>
-will be the default transport for all new accounts in this domain."""),
-_(u"""Configuration-related behavior:"""),
-u""" * domain.auto_postmaster""",
-_(u"""When that option is set to true (default) vmm will automatically create
-the postmaster account for the new domain and prompt for postmaster@<fqdn>'s
-password."""),
-u""" * account.random_password""",
-_(u"""When the value of that option is also set to true, vmm will automatically
-create the postmaster account for the new domain and print the generated
-postmaster password to stdout."""),),
-    # TP: description of subcommand domaindelete
-    'domaindelete': (_(u"""This subcommand deletes the domain specified by
-<fqdn>."""),
-_(u"""If there are accounts, aliases and/or relocated users assigned to the
-given domain, vmm will abort the requested operation and show an error
-message. If you know, what you are doing, you can specify the optional keyword
-'force'."""),
-_(u"""If you really always know what you are doing, edit your vmm.cfg and set
-the option domain.force_deletion to true."""),),
-    # TP: description of subcommand domaininfo
-    'domaininfo': (_(u"""This subcommand shows some information about the
-given domain."""),
-_(u"""For a more detailed information about the domain the optional argument
-<details> can be specified. A possible <details> value can be one of the
-following six keywords:"""),
-"""    accounts, aliasdomains, aliases, catchall, relocated, full""",),
-    # TP: description of subcommand domainquota
-    'domainquota': (_(u"""This subcommand is used to configure a new quota
-limit for the accounts of the domain - not for the domain itself."""),
-_(u"""The default quota limit for accounts is defined in the vmm.cfg
-(domain.quota_bytes and domain.quota_messages)."""),
-_(u"""The new quota limit will affect only those accounts for which the limit
-has not been overridden. If you want to restore the default to all accounts,
-you may pass the keyword 'force'. When the argument <messages> was omitted the
-default number of messages 0 (zero) will be applied."""),),
-    # TP: description of subcommand domainservices
-    'domainservices': (_(u"""To define which services could be used by the
-users of the domain — with the given <fqdn> — use this subcommand."""),
-_(u"""Each specified <service> will be enabled/usable. All other services
-will be deactivated/unusable. Possible <service> names are:"""),
-u"""    imap, pop3, sieve, smtp""",
-_(u"""The new service set will affect only those accounts for which the set has
-not been overridden. If you want to restore the default to all accounts, you
-may pass the keyword 'force'."""),),
-    # TP: description of subcommand domaintransport
-    'domaintransport': (_(u"""A new transport for the indicated domain can be
-set with this subcommand."""),
-_(u"""The new transport will affect only those accounts for which the transport
-has not been overridden. If you want to restore the default to all accounts,
-you may pass the keyword 'force'."""),),
-    # TP: description of subcommand domainnote
-    'domainnote': (_(u"""With this subcommand, it is possible to attach a
-note to the specified domain. Without an argument, an existing note is
-removed."""),),
-    # TP: description of subcommand aliasdomainadd
-    'aliasdomainadd': (_(u"""This subcommand adds the new alias domain
-(<fqdn>) to the destination <domain> that should be aliased."""),),
-    # TP: description of subcommand aliasdomaindelete
-    'aliasdomaindelete': (_(u"""Use this subcommand if the alias domain
-<fqdn> should be removed."""),),
-    # TP: description of subcommand aliasdomaininfo
-    'aliasdomaininfo': (_(u"""This subcommand shows to which domain the alias
-domain <fqdn> is assigned to."""),),
-    # TP: description of subcommand aliasdomainswitch
-    'aliasdomainswitch': (_(u"""If the destination of the existing alias
-domain <fqdn> should be switched to another <destination> use this
-subcommand."""),),
-    # TP: description of subcommand useradd
-    'useradd': (_(u"""Use this subcommand to create a new e-mail account for
-the given <address>."""),
-_(u"""If the <password> is not provided, vmm will prompt for it interactively.
-When no <password> is provided and account.random_password is set to true, vmm
-will generate a random password and print it to stdout after the account has
-been created."""),),
-    # TP: description of subcommand userdelete
-    'userdelete': (_(u"""Use this subcommand to delete the account with the
-given <address>."""),
-_(u"""If there are one or more aliases with an identical destination address,
-vmm will abort the requested operation and show an error message. To prevent
-this, specify the optional keyword 'force'."""),),
-    # TP: description of subcommand userinfo
-    'userinfo': (_(u"""This subcommand displays some information about the
-account specified by <address>."""),
-_(u"""If the optional argument <details> is given some more information will be
-displayed. Possible values for <details> are:"""),
-u"""    aliases, du. full""",),
-    # TP: description of subcommand username
-    'username': (_(u"""The user's real <name> can be set/updated with this
-subcommand."""),
-_(u"""If no <name> is given, the value stored for the account is erased."""),
-),
-    # TP: description of subcommand userpassword
-    'userpassword': (_(u"""The password of an account can be updated with this
-subcommand."""),
-_(u"""If no <password> was provided, vmm will prompt for it interactively."""),
-),
-    # TP: description of subcommand usernote
-    'usernote': (_(u"""With this subcommand, it is possible to attach a note
-to the specified account. Without an argument, an existing note is
-removed."""),),
-    # TP: description of subcommand userquota
-    'userquota': (_(u"""This subcommand is used to set a new quota limit for
-the given account."""),
-_(u"""When the argument <messages> was omitted the default number of messages
-0 (zero) will be applied."""),
-_(u"""Instead of <storage> pass the keyword 'domain' to remove the
-account-specific override, causing the domain's value to be in effect."""),),
-    # TP: description of subcommand userservices
-    'userservices': (_(u"""To grant a user access to the specified services,
-use this command."""),
-_(u"""All omitted services will be deactivated/unusable for the user with the
-given <address>."""),
-_(u"""Instead of <service> pass 'domain' to remove the account-specific
-override, causing the domain's value to be in effect."""),),
-    # TP: description of subcommand usertransport
-    'usertransport': (_(u"""A different <transport> for an account can be
-specified with this subcommand."""),
-_(u"""Instead of <transport> pass 'domain' to remove the account-specific
-override, causing the domain's value to be in effect."""),),
-    # TP: description of subcommand aliasadd
-    'aliasadd': (_(u"""This subcommand is used to create a new alias
-<address> with one or more <destination> addresses."""),
-_(u"""Within the destination address, the placeholders '%n', '%d', and '%='
-will be replaced by the local part, the domain, or the email address with '@'
-replaced by '=' respectively. In combination with alias domains, this enables
-domain-specific destinations."""),),
-    # TP: description of subcommand aliasdelete
-    'aliasdelete': (_(u"""This subcommand is used to delete one or multiple
-<destination>s from the alias with the given <address>."""),
-_(u"""When no <destination> address was specified the alias with all its
-destinations will be deleted."""),),
-    # TP: description of subcommand aliasinfo
-    'aliasinfo': (_(u"""Information about the alias with the given <address>
-can be displayed with this subcommand."""),),
-    # TP: description of subcommand relocatedadd
-    'relocatedadd': (_(u"""A new relocated user can be created with this
-subcommand."""),
-_(u"""<address> is the user's ex-email address, for example
-b.user@example.com, and <newaddress> points to the new email address where
-the user can be reached."""),),
-    # TP: description of subcommand relocatedinfo
-    'relocatedinfo': (_(u"""This subcommand shows the new address of the
-relocated user with the given <address>."""),),
-    # TP: description of subcommand relocateddelete
-    'relocateddelete': (_(u"""Use this subcommand in order to delete the
-relocated user with the given <address>."""),),
-    # TP: description of subcommand catchalladd
-    'catchalladd': (_(u"""This subcommand allows to specify destination
-addresses for a domain, which shall receive mail addressed to unknown
-local-parts within that domain. Those catch-all aliases hence "catch all" mail
-to any address in the domain (unless a more specific alias, mailbox or
-relocated user exists)."""),
-_(u"""WARNING: Catch-all addresses can cause mail server flooding because
-spammers like to deliver mail to all possible combinations of names, e.g.
-to all addresses between abba@example.org and zztop@example.org."""),),
-    # TP: description of subcommand catchallinfo
-    'catchallinfo': (_(u"""This subcommand displays information about catch-all
-aliases defined for the domain <fqdn>."""),),
-    # TP: description of subcommand catchalldelete
-    'catchalldelete': (_(u"""With this subcommand, catch-all aliases defined
-for a domain can be removed, either all of them, or those <destination>s which
-were specified explicitly."""),),
-}
-
-del _
--- a/VirtualMailManager/cli/config.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/cli/config.py	Sun Mar 09 18:52:27 2014 +0000
@@ -8,8 +8,9 @@
     Adds some interactive stuff to the Config class.
 """
 
-from ConfigParser import RawConfigParser
+from configparser import RawConfigParser
 from shutil import copy2
+from string import Template
 
 from VirtualMailManager import ENCODING
 from VirtualMailManager.config import Config, ConfigValueError, LazyConfig
@@ -29,33 +30,35 @@
     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]: ')
+        input_tpl = Template(_('Enter new value for option $option '
+                               '[$current_value]: '))
         failures = 0
 
-        w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
+        w_std(_('Using configuration file: %s\n') % self._cfg_filename)
         for section in sections:
-            w_std(_(u"* Configuration section: '%s'") % section)
+            w_std(_("* Configuration section: '%s'") % 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 isinstance(val, str):
+                        val = val.encode(ENCODING, 'replace').decode(ENCODING)
+                    newval = input(input_tpl.substitute(option=opt,
+                                                        current_value=val))
                     if newval and newval != val:
                         try:
                             LazyConfig.set(self, '%s.%s' % (section, opt),
                                            newval)
                             break
-                        except (ValueError, ConfigValueError, VMMError), err:
-                            w_err(0, _(u'Warning: %s') % err)
+                        except (ValueError, ConfigValueError, VMMError) as err:
+                            w_err(0, _('Warning: %s') % err)
                             failures += 1
                             if failures > 2:
-                                raise ConfigError(_(u'Too many failures - try '
-                                                    u'again later.'),
+                                raise ConfigError(_('Too many failures - try '
+                                                    'again later.'),
                                                   VMM_TOO_MANY_FAILURES)
                     else:
                         break
-            print
+            print()
         if self._modified:
             self._save_changes()
 
@@ -72,7 +75,7 @@
             val = self._cfg[section][option_].cls(value)
             if self._cfg[section][option_].validate:
                 val = self._cfg[section][option_].validate(val)
-        except (ValueError, ConfigValueError), err:
+        except (ValueError, ConfigValueError) as err:
             raise ConfigError(str(err), CONF_ERROR)
         # Do not write default values also skip identical values
         if not self._cfg[section][option_].default is None:
@@ -89,8 +92,7 @@
     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()
+        with open(self._cfg_filename, 'w', encoding='utf-8') as self._cfg_file:
+            self.write(self._cfg_file)
 
 del _
--- a/VirtualMailManager/cli/handler.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/cli/handler.py	Sun Mar 09 18:52:27 2014 +0000
@@ -16,7 +16,7 @@
 from VirtualMailManager.cli.config import CliConfig as Cfg
 from VirtualMailManager.constants import ACCOUNT_EXISTS, INVALID_SECTION, \
      NO_SUCH_ACCOUNT, TYPE_ACCOUNT
-from VirtualMailManager.password import randompw
+from VirtualMailManager.password import randompw, verify_scheme
 
 _ = lambda msg: msg
 
@@ -40,8 +40,8 @@
         """
         # 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')
+        skip_some_checks = os.sys.argv[1] in ('cf', 'configure',
+                                              'cs', 'configset')
         super(CliHandler, self).__init__(skip_some_checks)
 
         self._cfg = Cfg(self._cfg_fname)
@@ -63,10 +63,10 @@
         elif self._cfg.has_section(section):
             self._cfg.configure([section])
         else:
-            raise VMMError(_(u"Invalid section: '%s'") % section,
+            raise VMMError(_("Invalid section: '%s'") % section,
                            INVALID_SECTION)
 
-    def user_add(self, emailaddress, password=None):
+    def user_add(self, emailaddress, password=None, note=None):
         """Override the parent user_add() - add the interactive password
         dialog.
 
@@ -74,26 +74,32 @@
         """
         acc = self._get_account(emailaddress)
         if acc:
-            raise VMMError(_(u"The account '%s' already exists.") %
+            raise VMMError(_("The account '%s' already exists.") %
                            acc.address, ACCOUNT_EXISTS)
         self._is_other_address(acc.address, TYPE_ACCOUNT)
         rand_pass = self._cfg.dget('account.random_password')
         if password is None:
             password = (read_pass, randompw)[rand_pass]()
         acc.set_password(password)
+        if note:
+            acc.set_note(note)
         acc.save()
         self._make_account_dirs(acc)
         return (None, password)[rand_pass]
 
-    def user_password(self, emailaddress, password=None):
+    def user_password(self, emailaddress, password=None, scheme=None):
         """Override the parent user_password() - add the interactive
         password dialog."""
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
-        if not isinstance(password, basestring) or not password:
+        if scheme:
+            scheme, encoding = verify_scheme(scheme)
+            if encoding:
+                scheme = '%s.%s' % (scheme, encoding)
+        if not isinstance(password, str) or not password:
             password = read_pass()
-        acc.modify('password', password)
+        acc.update_password(password, scheme)
 
 del _
--- a/VirtualMailManager/cli/main.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/cli/main.py	Sun Mar 09 18:52:27 2014 +0000
@@ -8,16 +8,15 @@
     VirtualMailManager's command line interface.
 """
 
-from ConfigParser import NoOptionError, NoSectionError
+from configparser import NoOptionError, NoSectionError
 
 from VirtualMailManager import ENCODING, errors
 from VirtualMailManager.config import BadOptionError, ConfigValueError
 from VirtualMailManager.cli import w_err
 from VirtualMailManager.cli.handler import CliHandler
-from VirtualMailManager.constants import DATABASE_ERROR, EX_MISSING_ARGS, \
-     EX_SUCCESS, EX_UNKNOWN_COMMAND, EX_USER_INTERRUPT, INVALID_ARGUMENT
-from VirtualMailManager.cli.subcommands import RunContext, cmd_map, \
-     update_cmd_map, usage
+from VirtualMailManager.constants import EX_MISSING_ARGS, EX_SUCCESS, \
+     EX_USER_INTERRUPT, INVALID_ARGUMENT
+from VirtualMailManager.cli.subcommands import RunContext, setup_parser
 
 
 _ = lambda msg: msg
@@ -28,55 +27,43 @@
     try:
         handler = CliHandler()
     except (errors.NotRootError, errors.PermissionError, errors.VMMError,
-            errors.ConfigError), err:
-        w_err(err.code, _(u'Error: %s') % err.msg)
+            errors.ConfigError) as err:
+        w_err(err.code, _('Error: %s') % err.msg)
     else:
         handler.cfg_install()
         return handler
 
 
 def run(argv):
-    update_cmd_map()
+    parser = setup_parser()
     if len(argv) < 2:
-        usage(EX_MISSING_ARGS, _(u"You must specify a subcommand at least."))
-
-    sub_cmd = argv[1].lower()
-    if sub_cmd in cmd_map:
-        cmd_func = cmd_map[sub_cmd].func
-    else:
-        for cmd in cmd_map.itervalues():
-            if cmd.alias == sub_cmd:
-                cmd_func = cmd.func
-                sub_cmd = cmd.name
-                break
-        else:
-            usage(EX_UNKNOWN_COMMAND, _(u"Unknown subcommand: '%s'") % sub_cmd)
-
+        parser.print_usage()
+        parser.exit(status=EX_MISSING_ARGS,
+                   message=_('You must specify a subcommand at least.') + '\n')
+    args = parser.parse_args()
     handler = _get_handler()
-    run_ctx = RunContext(argv, handler, sub_cmd)
+    run_ctx = RunContext(args, handler)
     try:
-        cmd_func(run_ctx)
+        args.func(run_ctx)
     except (EOFError, KeyboardInterrupt):
         # TP: We have to cry, because root has killed/interrupted vmm
         # with Ctrl+C or Ctrl+D.
-        w_err(EX_USER_INTERRUPT, '', _(u'Ouch!'), '')
-    except errors.VMMError, err:
-        if err.code != DATABASE_ERROR:
-            if handler.has_warnings():
-                w_err(0, _(u'Warnings:'), *handler.get_warnings())
-            w_err(err.code, _(u'Error: %s') % err.msg)
-        w_err(err.code, unicode(err.msg, ENCODING, 'replace'))
-    except (BadOptionError, ConfigValueError), err:
-        w_err(INVALID_ARGUMENT, _(u'Error: %s') % err)
-    except NoSectionError, err:
+        w_err(EX_USER_INTERRUPT, '', _('Ouch!'), '')
+    except errors.VMMError as err:
+        if handler.has_warnings():
+            w_err(0, _('Warnings:'), *handler.get_warnings())
+        w_err(err.code, _('Error: %s') % err.msg)
+    except (BadOptionError, ConfigValueError) as err:
+        w_err(INVALID_ARGUMENT, _('Error: %s') % err)
+    except NoSectionError as err:
         w_err(INVALID_ARGUMENT,
-              _(u"Error: Unknown section: '%s'") % err.section)
-    except NoOptionError, err:
+              _("Error: Unknown section: '%s'") % err.section)
+    except NoOptionError as err:
         w_err(INVALID_ARGUMENT,
-              _(u"Error: No option '%(option)s' in section: '%(section)s'") %
+              _("Error: No option '%(option)s' in section: '%(section)s'") %
               {'option': err.option, 'section': err.section})
     if handler.has_warnings():
-        w_err(0, _(u'Warnings:'), *handler.get_warnings())
+        w_err(0, _('Warnings:'), *handler.get_warnings())
     return EX_SUCCESS
 
 del _
--- a/VirtualMailManager/cli/subcommands.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/cli/subcommands.py	Sun Mar 09 18:52:27 2014 +0000
@@ -9,150 +9,83 @@
 """
 
 import locale
-import os
+import platform
 
+from argparse import Action, ArgumentParser, ArgumentTypeError, \
+     RawDescriptionHelpFormatter
 from textwrap import TextWrapper
 from time import strftime, strptime
 
 from VirtualMailManager import ENCODING
-from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
-from VirtualMailManager.cli.clihelp import help_msgs
+from VirtualMailManager.cli import get_winsize, w_err, w_std
 from VirtualMailManager.common import human_size, size_in_bytes, \
      version_str, format_domain_default
 from VirtualMailManager.constants import __copyright__, __date__, \
      __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
-     DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \
-     RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
+     DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, RELOCATED_EXISTS, TYPE_ACCOUNT, \
+     TYPE_ALIAS, TYPE_RELOCATED
 from VirtualMailManager.errors import VMMError
 from VirtualMailManager.password import list_schemes
 from VirtualMailManager.serviceset import SERVICES
 
 __all__ = (
-    'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
-    'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
-    'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete',
-    'config_get', 'config_set', 'configure',
-    'domain_add', 'domain_delete',  'domain_info', 'domain_quota',
-    'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_',
-    'list_domains', 'list_pwschemes', 'list_users', 'list_aliases',
-    'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete',
-    'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name',
-    'user_password', 'user_quota', 'user_services', 'user_transport',
-    'user_note', 'version',
+    'RunContext', 'alias_add', 'alias_delete', 'alias_info', 'aliasdomain_add',
+    'aliasdomain_delete', 'aliasdomain_info', 'aliasdomain_switch',
+    'catchall_add', 'catchall_delete', 'catchall_info', 'config_get',
+    'config_set', 'configure', 'domain_add', 'domain_delete', 'domain_info',
+    'domain_note', 'domain_quota', 'domain_services', 'domain_transport',
+    'get_user', 'list_addresses', 'list_aliases', 'list_domains',
+    'list_pwschemes', 'list_relocated', 'list_users', 'relocated_add',
+    'relocated_delete', 'relocated_info', 'setup_parser', 'user_add',
+    'user_delete', 'user_info', 'user_name', 'user_note', 'user_password',
+    'user_quota', 'user_services', 'user_transport',
 )
 
+WS_ROWS = get_winsize()[1] - 2
+
 _ = lambda msg: msg
-txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
-cmd_map = {}
-
-
-class Command(object):
-    """Container class for command information."""
-    __slots__ = ('name', 'alias', 'func', 'args', 'descr')
-    FMT_HLP_USAGE = """
-usage: %(prog)s %(name)s %(args)s
-       %(prog)s %(alias)s %(args)s
-"""
-
-    def __init__(self, name, alias, func, args, descr):
-        """Create a new Command instance.
-
-        Arguments:
-
-        `name` : str
-          the command name, e.g. ``addalias``
-        `alias` : str
-          the command's short alias, e.g. ``aa``
-        `func` : callable
-          the function to handle the command
-        `args` : str
-          argument placeholders, e.g. ``aliasaddress``
-        `descr` : str
-          short description of the command
-        """
-        self.name = name
-        self.alias = alias
-        self.func = func
-        self.args = args
-        self.descr = descr
-
-    @property
-    def usage(self):
-        """the command's usage info."""
-        return u'%s %s %s' % (prog, self.name, self.args)
-
-    def help_(self):
-        """Print the Command's help message to stdout."""
-        old_ii = txt_wrpr.initial_indent
-        old_si = txt_wrpr.subsequent_indent
-
-        txt_wrpr.subsequent_indent = (len(self.name) + 2) * ' '
-        w_std(txt_wrpr.fill('%s: %s' % (self.name, self.descr)))
-
-        info = Command.FMT_HLP_USAGE % dict(alias=self.alias, args=self.args,
-                                            name=self.name, prog=prog)
-        w_std(info)
-
-        txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = ' '
-        try:
-            [w_std(txt_wrpr.fill(_(para)) + '\n') for para
-                    in help_msgs[self.name]]
-        except KeyError:
-            w_err(1, _(u"Subcommand '%s' is not yet documented." % self.name),
-                  'see also: vmm(1)')
+txt_wrpr = TextWrapper(width=WS_ROWS)
 
 
 class RunContext(object):
     """Contains all information necessary to run a subcommand."""
-    __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
-    plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
+    __slots__ = ('args', 'cget', 'hdlr')
+    plan_a_b = _('Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
 
-    def __init__(self, argv, handler, command):
+    def __init__(self, args, handler):
         """Create a new RunContext"""
-        self.argc = len(argv)
-        self.args = [unicode(arg, ENCODING) for arg in argv]
+        self.args = args
         self.cget = handler.cfg_dget
         self.hdlr = handler
-        self.scmd = command
 
 
 def alias_add(ctx):
     """create a new alias e-mail address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
-    ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
+    ctx.hdlr.alias_add(ctx.args.address.lower(), *ctx.args.destination)
 
 
 def alias_delete(ctx):
     """delete the specified alias e-mail address or one of its destinations"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
-    elif ctx.argc < 4:
-        ctx.hdlr.alias_delete(ctx.args[2].lower())
-    else:
-        ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3:])
+    destination = ctx.args.destination if ctx.args.destination else None
+    ctx.hdlr.alias_delete(ctx.args.address.lower(), destination)
 
 
 def alias_info(ctx):
     """show the destination(s) of the specified alias"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
-    address = ctx.args[2].lower()
+    address = ctx.args.address.lower()
     try:
         _print_aliase_info(address, ctx.hdlr.alias_info(address))
-    except VMMError, err:
+    except VMMError as err:
         if err.code is ACCOUNT_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+            w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
                   'object': address})
-            ctx.scmd = ctx.args[1] = 'userinfo'
+            ctx.args.scmd = 'userinfo'
+            ctx.args.details = None
             user_info(ctx)
         elif err.code is RELOCATED_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
+            w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
                   'object': address})
-            ctx.scmd = ctx.args[1] = 'relocatedinfo'
+            ctx.args.scmd = 'relocatedinfo'
             relocated_info(ctx)
         else:
             raise
@@ -160,33 +93,25 @@
 
 def aliasdomain_add(ctx):
     """create a new alias for an existing domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
-                                 u'domain name.'), ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
-              ctx.scmd)
-    ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
+    ctx.hdlr.aliasdomain_add(ctx.args.fqdn.lower(),
+                             ctx.args.destination.lower())
 
 
 def aliasdomain_delete(ctx):
     """delete the specified alias domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
-    ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
+    ctx.hdlr.aliasdomain_delete(ctx.args.fqdn.lower())
 
 
 def aliasdomain_info(ctx):
     """show the destination of the given alias domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
+    fqdn = ctx.args.fqdn.lower()
     try:
-        _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
-    except VMMError, err:
+        _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(fqdn))
+    except VMMError as err:
         if err.code is ALIASDOMAIN_ISDOMAIN:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
-                  'object': ctx.args[2].lower()})
-            ctx.scmd = ctx.args[1] = 'domaininfo'
+            w_err(0, ctx.plan_a_b % {'subcommand': 'domaininfo',
+                                     'object': fqdn})
+            ctx.args.scmd = 'domaininfo'
             domain_info(ctx)
         else:
             raise
@@ -194,327 +119,160 @@
 
 def aliasdomain_switch(ctx):
     """assign the given alias domain to an other domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
-                                 u'domain name.'), ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
-              ctx.scmd)
-    ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
+    ctx.hdlr.aliasdomain_switch(ctx.args.fqdn.lower(),
+                                ctx.args.destination.lower())
 
 
 def catchall_add(ctx):
     """create a new catchall alias e-mail address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain and destination.'),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
-    ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:])
+    ctx.hdlr.catchall_add(ctx.args.fqdn.lower(), *ctx.args.destination)
 
 
 def catchall_delete(ctx):
     """delete the specified destination or all of the catchall destination"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    elif ctx.argc < 4:
-        ctx.hdlr.catchall_delete(ctx.args[2].lower())
-    else:
-        ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3:])
+    destination = ctx.args.destination if ctx.args.destination else None
+    ctx.hdlr.catchall_delete(ctx.args.fqdn.lower(), destination)
 
 
 def catchall_info(ctx):
     """show the catchall destination(s) of the specified domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    address = ctx.args[2].lower()
+    address = ctx.args.fqdn.lower()
     _print_catchall_info(address, ctx.hdlr.catchall_info(address))
 
 
 def config_get(ctx):
     """show the actual value of the configuration option"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u"Missing option name."), ctx.scmd)
-
     noop = lambda option: option
     opt_formater = {
         'misc.dovecot_version': version_str,
         'domain.quota_bytes': human_size,
     }
 
-    option = ctx.args[2].lower()
+    option = ctx.args.option.lower()
     w_std('%s = %s' % (option, opt_formater.get(option,
                        noop)(ctx.cget(option))))
 
 
 def config_set(ctx):
     """set a new value for the configuration option"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing option and new value.'), ctx.scmd)
-    if ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing new configuration value.'),
-              ctx.scmd)
-    ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3])
+    ctx.hdlr.cfg_set(ctx.args.option.lower(), ctx.args.value)
 
 
 def configure(ctx):
     """start interactive configuration mode"""
-    if ctx.argc < 3:
-        ctx.hdlr.configure()
-    else:
-        ctx.hdlr.configure(ctx.args[2].lower())
+    ctx.hdlr.configure(ctx.args.section)
 
 
 def domain_add(ctx):
     """create a new domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    elif ctx.argc < 4:
-        ctx.hdlr.domain_add(ctx.args[2].lower())
-    else:
-        ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
+    fqdn = ctx.args.fqdn.lower()
+    transport = ctx.args.transport.lower() if ctx.args.transport else None
+    ctx.hdlr.domain_add(fqdn, transport, ctx.args.note)
     if ctx.cget('domain.auto_postmaster'):
-        w_std(_(u'Creating account for postmaster@%s') % ctx.args[2].lower())
-        ctx.scmd = 'useradd'
-        ctx.args = [prog, ctx.scmd, u'postmaster@' + ctx.args[2].lower()]
-        ctx.argc = 3
+        w_std(_('Creating account for postmaster@%s') % fqdn)
+        ctx.args.scmd = 'useradd'
+        ctx.args.address = 'postmaster@%s' % fqdn
+        ctx.args.password = None
+        ctx.args.note = None
         user_add(ctx)
 
 
 def domain_delete(ctx):
     """delete the given domain and all its alias domains"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    elif ctx.argc < 4:
-        ctx.hdlr.domain_delete(ctx.args[2].lower())
-    elif ctx.args[3].lower() == 'force':
-        ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
-    else:
-        usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
-              ctx.scmd)
+    ctx.hdlr.domain_delete(ctx.args.fqdn.lower(), ctx.args.force)
 
 
 def domain_info(ctx):
     """display information about the given domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    if ctx.argc < 4:
-        details = None
-    else:
-        details = ctx.args[3].lower()
-        if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
-                           'relocated', 'catchall'):
-            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
-                  ctx.scmd)
+    fqdn = ctx.args.fqdn.lower()
+    details = ctx.args.details
     try:
-        info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
-    except VMMError, err:
+        info = ctx.hdlr.domain_info(fqdn, details)
+    except VMMError as err:
         if err.code is DOMAIN_ALIAS_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
-                  'object': ctx.args[2].lower()})
-            ctx.scmd = ctx.args[1] = 'aliasdomaininfo'
+            w_err(0, ctx.plan_a_b % {'subcommand': 'aliasdomaininfo',
+                                     'object': fqdn})
+            ctx.args.scmd = 'aliasdomaininfo'
             aliasdomain_info(ctx)
         else:
             raise
     else:
-        q_limit = u'Storage: %(bytes)s; Messages: %(messages)s'
+        q_limit = 'Storage: %(bytes)s; Messages: %(messages)s'
         if not details:
             info['bytes'] = human_size(info['bytes'])
-            info['messages'] = locale.format('%d', info['messages'],
-                                             True).decode(ENCODING, 'replace')
+            info['messages'] = locale.format('%d', info['messages'], True)
             info['quota limit/user'] = q_limit % info
-            _print_info(ctx, info, _(u'Domain'))
+            _print_info(ctx, info, _('Domain'))
         else:
             info[0]['bytes'] = human_size(info[0]['bytes'])
             info[0]['messages'] = locale.format('%d', info[0]['messages'],
-                                                True).decode(ENCODING,
-                                                             'replace')
+                                                True)
             info[0]['quota limit/user'] = q_limit % info[0]
-            _print_info(ctx, info[0], _(u'Domain'))
-            if details == u'accounts':
-                _print_list(info[1], _(u'accounts'))
-            elif details == u'aliasdomains':
-                _print_list(info[1], _(u'alias domains'))
-            elif details == u'aliases':
-                _print_list(info[1], _(u'aliases'))
-            elif details == u'relocated':
-                _print_list(info[1], _(u'relocated users'))
-            elif details == u'catchall':
-                _print_list(info[1], _(u'catch-all destinations'))
+            _print_info(ctx, info[0], _('Domain'))
+            if details == 'accounts':
+                _print_list(info[1], _('accounts'))
+            elif details == 'aliasdomains':
+                _print_list(info[1], _('alias domains'))
+            elif details == 'aliases':
+                _print_list(info[1], _('aliases'))
+            elif details == 'relocated':
+                _print_list(info[1], _('relocated users'))
+            elif details == 'catchall':
+                _print_list(info[1], _('catch-all destinations'))
             else:
-                _print_list(info[1], _(u'alias domains'))
-                _print_list(info[2], _(u'accounts'))
-                _print_list(info[3], _(u'aliases'))
-                _print_list(info[4], _(u'relocated users'))
-                _print_list(info[5], _(u'catch-all destinations'))
+                _print_list(info[1], _('alias domains'))
+                _print_list(info[2], _('accounts'))
+                _print_list(info[3], _('aliases'))
+                _print_list(info[4], _('relocated users'))
+                _print_list(info[5], _('catch-all destinations'))
 
 
 def domain_quota(ctx):
     """update the quota limit of the specified domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name and storage value.'),
-              ctx.scmd)
-    if ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
-    messages = 0
-    force = None
-    try:
-        bytes_ = size_in_bytes(ctx.args[3])
-    except (ValueError, TypeError):
-        usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
-              ctx.args[3], ctx.scmd)
-    if ctx.argc < 5:
-        pass
-    elif ctx.argc < 6:
-        try:
-            messages = int(ctx.args[4])
-        except ValueError:
-            if ctx.args[4].lower() != 'force':
-                usage(INVALID_ARGUMENT,
-                      _(u"Neither a valid number of messages nor the keyword "
-                        u"'force': '%s'") % ctx.args[4], ctx.scmd)
-            force = 'force'
-    else:
-        try:
-            messages = int(ctx.args[4])
-        except ValueError:
-            usage(INVALID_ARGUMENT,
-                  _(u"Not a valid number of messages: '%s'") % ctx.args[4],
-                  ctx.scmd)
-        if ctx.args[5].lower() != 'force':
-            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[5],
-                  ctx.scmd)
-        force = 'force'
-    ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force)
+    ctx.hdlr.domain_quotalimit(ctx.args.fqdn.lower(), ctx.args.storage,
+                               ctx.args.messages, ctx.args.force)
 
 
 def domain_services(ctx):
     """allow all named service and block the uncredited."""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
-    services = []
-    force = False
-    if ctx.argc is 3:
-        pass
-    elif ctx.argc is 4:
-        arg = ctx.args[3].lower()
-        if arg in SERVICES:
-            services.append(arg)
-        elif arg == 'force':
-            force = True
-        else:
-            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % arg,
-                  ctx.scmd)
-    else:
-        services.extend([service.lower() for service in ctx.args[3:-1]])
-        arg = ctx.args[-1].lower()
-        if arg == 'force':
-            force = True
-        else:
-            services.append(arg)
-        unknown = [service for service in services if service not in SERVICES]
-        if unknown:
-            usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
-                  ' '.join(unknown), ctx.scmd)
-    ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force],
-                             *services)
+    services = ctx.args.services if ctx.args.services else []
+    ctx.hdlr.domain_services(ctx.args.fqdn.lower(), ctx.args.force, *services)
 
 
 def domain_transport(ctx):
     """update the transport of the specified domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
-              ctx.scmd)
-    if ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
-    if ctx.argc < 5:
-        ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
-    else:
-        force = ctx.args[4].lower()
-        if force != 'force':
-            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
-                  ctx.scmd)
-        ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
+    ctx.hdlr.domain_transport(ctx.args.fqdn.lower(),
+                              ctx.args.transport.lower(), ctx.args.force)
 
 
 def domain_note(ctx):
     """update the note of the given domain"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing domain name.'),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        note = None
-    else:
-        note = ' '.join(ctx.args[3:])
-    ctx.hdlr.domain_note(ctx.args[2].lower(), note)
+    ctx.hdlr.domain_note(ctx.args.fqdn.lower(), ctx.args.note)
 
 
 def get_user(ctx):
     """get the address of the user with the given UID"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing UID.'), ctx.scmd)
-    _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
-
-
-def help_(ctx):
-    """print help messages."""
-    if ctx.argc > 2:
-        hlptpc = ctx.args[2].lower()
-        if hlptpc in cmd_map:
-            topic = hlptpc
-        else:
-            for scmd in cmd_map.itervalues():
-                if scmd.alias == hlptpc:
-                    topic = scmd.name
-                    break
-            else:
-                usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
-                      ctx.args[2], ctx.scmd)
-        if topic != u'help':
-            return cmd_map[topic].help_()
-
-    old_ii = txt_wrpr.initial_indent
-    old_si = txt_wrpr.subsequent_indent
-    txt_wrpr.initial_indent = ' '
-    # len(max(_overview.iterkeys(), key=len)) #Py25
-    txt_wrpr.subsequent_indent = 20 * ' '
-    order = cmd_map.keys()
-    order.sort()
-
-    w_std(_(u'List of available subcommands:') + '\n')
-    for key in order:
-        w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
-
-    txt_wrpr.initial_indent = old_ii
-    txt_wrpr.subsequent_indent = old_si
-    txt_wrpr.initial_indent = ''
+    _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args.uid), _('Account'))
 
 
 def list_domains(ctx):
     """list all domains / search domains by pattern"""
-    matching = ctx.argc > 2
-    if matching:
-        gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
-    else:
-        gids, domains = ctx.hdlr.domain_list()
+    matching = True if ctx.args.pattern else False
+    pattern = ctx.args.pattern.lower() if matching else None
+    gids, domains = ctx.hdlr.domain_list(pattern)
     _print_domain_list(gids, domains, matching)
 
 
 def list_pwschemes(ctx_unused):
     """Prints all usable password schemes and password encoding suffixes."""
-    # TODO: Remove trailing colons from keys.
-    # For now it is to late, the translators has stared their work
-    keys = (_(u'Usable password schemes:'), _(u'Usable encoding suffixes:'))
+    keys = (_('Usable password schemes'), _('Usable encoding suffixes'))
     old_ii, old_si = txt_wrpr.initial_indent, txt_wrpr.subsequent_indent
     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
     txt_wrpr.width = txt_wrpr.width - 8
 
     for key, value in zip(keys, list_schemes()):
-        if key.endswith(':'):  # who knows … (see TODO above)
-            #key = key.rpartition(':')[0]
-            key = key[:-1]  # This one is for Py24
         w_std(key, len(key) * '-')
-        w_std('\n'.join(txt_wrpr.wrap(' '.join(value))), '')
+        w_std('\n'.join(txt_wrpr.wrap(' '.join(sorted(value)))), '')
 
     txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si
     txt_wrpr.width = txt_wrpr.width + 8
@@ -527,11 +285,9 @@
     combining all three."""
     if limit is None:
         limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
-    matching = ctx.argc > 2
-    if matching:
-        gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower())
-    else:
-        gids, addresses = ctx.hdlr.address_list(limit)
+    matching = True if ctx.args.pattern else False
+    pattern = ctx.args.pattern.lower() if matching else None
+    gids, addresses = ctx.hdlr.address_list(limit, pattern)
     _print_address_list(limit, gids, addresses, matching)
 
 
@@ -552,39 +308,31 @@
 
 def relocated_add(ctx):
     """create a new record for a relocated user"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS,
-              _(u'Missing relocated address and destination.'), ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
-    ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
+    ctx.hdlr.relocated_add(ctx.args.address.lower(), ctx.args.newaddress)
 
 
 def relocated_delete(ctx):
     """delete the record of the relocated user"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
-    ctx.hdlr.relocated_delete(ctx.args[2].lower())
+    ctx.hdlr.relocated_delete(ctx.args.address.lower())
 
 
 def relocated_info(ctx):
     """print information about a relocated user"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
-    relocated = ctx.args[2].lower()
+    relocated = ctx.args.address.lower()
     try:
         _print_relocated_info(addr=relocated,
                               dest=ctx.hdlr.relocated_info(relocated))
-    except VMMError, err:
+    except VMMError as err:
         if err.code is ACCOUNT_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+            w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
                   'object': relocated})
-            ctx.scmd = ctx.args[1] = 'userinfoi'
+            ctx.args.scmd = 'userinfo'
+            ctx.args.details = None
             user_info(ctx)
         elif err.code is ALIAS_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
+            w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
                   'object': relocated})
-            ctx.scmd = ctx.args[1] = 'aliasinfo'
+            ctx.args.scmd = 'aliasinfo'
             alias_info(ctx)
         else:
             raise
@@ -592,65 +340,44 @@
 
 def user_add(ctx):
     """create a new e-mail user with the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
-    elif ctx.argc < 4:
-        password = None
-    else:
-        password = ctx.args[3]
-    gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password)
-    if ctx.argc < 4 and gen_pass:
-        w_std(_(u"Generated password: %s") % gen_pass)
+    gen_pass = ctx.hdlr.user_add(ctx.args.address.lower(), ctx.args.password,
+                                 ctx.args.note)
+    if not ctx.args.password and gen_pass:
+        w_std(_("Generated password: %s") % gen_pass)
 
 
 def user_delete(ctx):
     """delete the specified user"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
-    elif ctx.argc < 4:
-        ctx.hdlr.user_delete(ctx.args[2].lower())
-    elif ctx.args[3].lower() == 'force':
-        ctx.hdlr.user_delete(ctx.args[2].lower(), True)
-    else:
-        usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
-              ctx.scmd)
+    ctx.hdlr.user_delete(ctx.args.address.lower(), ctx.args.force)
 
 
 def user_info(ctx):
     """display information about the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
-    if ctx.argc < 4:
-        details = None
-    else:
-        details = ctx.args[3].lower()
-        if details not in ('aliases', 'du', 'full'):
-            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
-                  ctx.scmd)
+    address = ctx.args.address.lower()
     try:
-        info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
-    except VMMError, err:
+        info = ctx.hdlr.user_info(address, ctx.args.details)
+    except VMMError as err:
         if err.code is ALIAS_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
-                  'object': ctx.args[2].lower()})
-            ctx.scmd = ctx.args[1] = 'aliasinfo'
+            w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
+                  'object': address})
+            ctx.args.scmd = 'aliasinfo'
             alias_info(ctx)
         elif err.code is RELOCATED_EXISTS:
-            w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
-                  'object': ctx.args[2].lower()})
-            ctx.scmd = ctx.args[1] = 'relocatedinfo'
+            w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
+                  'object': address})
+            ctx.args.scmd = 'relocatedinfo'
             relocated_info(ctx)
         else:
             raise
     else:
-        if details in (None, 'du'):
+        if ctx.args.details in (None, 'du'):
             info['quota storage'] = _format_quota_usage(info['ql_bytes'],
                     info['uq_bytes'], True, info['ql_domaindefault'])
             info['quota messages'] = \
                 _format_quota_usage(info['ql_messages'],
                                     info['uq_messages'],
                                     domaindefault=info['ql_domaindefault'])
-            _print_info(ctx, info, _(u'Account'))
+            _print_info(ctx, info, _('Account'))
         else:
             info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'],
                     info[0]['uq_bytes'], True, info[0]['ql_domaindefault'])
@@ -658,286 +385,677 @@
                 _format_quota_usage(info[0]['ql_messages'],
                                     info[0]['uq_messages'],
                                     domaindefault=info[0]['ql_domaindefault'])
-            _print_info(ctx, info[0], _(u'Account'))
-            _print_list(info[1], _(u'alias addresses'))
+            _print_info(ctx, info[0], _('Account'))
+            _print_list(info[1], _('alias addresses'))
 
 
 def user_name(ctx):
     """set or update the real name for an address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u"Missing e-mail address and user's name."),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        name = None
-    else:
-        name = ctx.args[3]
-    ctx.hdlr.user_name(ctx.args[2].lower(), name)
+    ctx.hdlr.user_name(ctx.args.address.lower(), ctx.args.name)
 
 
 def user_password(ctx):
     """update the password for the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
-    elif ctx.argc < 4:
-        password = None
+    if ctx.args.pwhash:
+        if ctx.args.scheme:
+            w_std(_('Info: The -s option is ignored when --hash was given.'))
+        ctx.hdlr.user_pwhash(ctx.args.address.lower(), ctx.args.pwhash)
     else:
-        password = ctx.args[3]
-    ctx.hdlr.user_password(ctx.args[2].lower(), password)
+        ctx.hdlr.user_password(ctx.args.address.lower(), ctx.args.password,
+                               ctx.args.scheme)
 
 
 def user_note(ctx):
     """update the note of the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        note = None
-    else:
-        note = ' '.join(ctx.args[3:])
-    ctx.hdlr.user_note(ctx.args[2].lower(), note)
+    ctx.hdlr.user_note(ctx.args.address.lower(), ctx.args.note)
 
 
 def user_quota(ctx):
     """update the quota limit for the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address and storage value.'),
-              ctx.scmd)
-    elif ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
-    if ctx.args[3] != 'domain':
-        try:
-            bytes_ = size_in_bytes(ctx.args[3])
-        except (ValueError, TypeError):
-            usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
-                  ctx.args[3], ctx.scmd)
-    else:
-        bytes_ = ctx.args[3]
-    if ctx.argc < 5:
-        messages = 0
-    else:
-        try:
-            messages = int(ctx.args[4])
-        except ValueError:
-            usage(INVALID_ARGUMENT,
-                  _(u"Not a valid number of messages: '%s'") % ctx.args[4],
-                  ctx.scmd)
-    ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages)
+    ctx.hdlr.user_quotalimit(ctx.args.address.lower(), ctx.args.storage,
+                             ctx.args.messages)
 
 
 def user_services(ctx):
     """allow all named service and block the uncredited."""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
-    services = []
-    if ctx.argc >= 4:
-        services.extend([service.lower() for service in ctx.args[3:]])
-        unknown = [service for service in services if service not in SERVICES]
-        if unknown and ctx.args[3] != 'domain':
-            usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
-                  ' '.join(unknown), ctx.scmd)
-    ctx.hdlr.user_services(ctx.args[2].lower(), *services)
+    if 'domain' in ctx.args.services:
+        services = ['domain']
+    else:
+        services = ctx.args.services
+    ctx.hdlr.user_services(ctx.args.address.lower(), *services)
 
 
 def user_transport(ctx):
     """update the transport of the given address"""
-    if ctx.argc < 3:
-        usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
-              ctx.scmd)
-    if ctx.argc < 4:
-        usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
-    ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
-
-
-def usage(errno, errmsg, subcommand=None):
-    """print usage message for the given command or all commands.
-    When errno > 0, sys,exit(errno) will interrupt the program.
-    """
-    if subcommand and subcommand in cmd_map:
-        w_err(errno, _(u"Error: %s") % errmsg,
-              _(u"usage: ") + cmd_map[subcommand].usage)
-
-    # TP: Please adjust translated words like the original text.
-    # (It's a table header.) Extract from usage text:
-    # usage: vmm subcommand arguments
-    #   short long
-    #   subcommand                arguments
-    #
-    #   da    domainadd           fqdn [transport]
-    #   dd    domaindelete        fqdn [force]
-    u_head = _(u"""usage: %s subcommand arguments
-  short long
-  subcommand                arguments\n""") % prog
-    order = cmd_map.keys()
-    order.sort()
-    w_err(0, u_head)
-    for key in order:
-        scmd = cmd_map[key]
-        w_err(0, '  %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
-    w_err(errno, '', _(u"Error: %s") % errmsg)
-
-
-def version(ctx_unused):
-    """Write version and copyright information to stdout."""
-    w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
-    # TP: The words 'from', 'version' and 'on' are used in
-    # the version information, e.g.:
-    # vmm, version 0.5.2 (from 09/09/09)
-    # Python 2.5.4 on FreeBSD
-        _(u'version'), __version__, _(u'from'),
-        strftime(locale.nl_langinfo(locale.D_FMT),
-            strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
-        os.sys.version.split()[0], _(u'on'), os.uname()[0],
-        __copyright__, prog,
-        _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
+    ctx.hdlr.user_transport(ctx.args.address.lower(), ctx.args.transport)
 
 
-def update_cmd_map():
-    """Update the cmd_map, after gettext's _ was installed."""
-    cmd = Command
-    cmd_map.update({
-    # Account commands
-    'getuser': cmd('getuser', 'gu', get_user, 'uid',
-                   _(u'get the address of the user with the given UID')),
-    'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
-                   _(u'create a new e-mail user with the given address')),
-    'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
-                      _(u'delete the specified user')),
-    'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
-                    _(u'display information about the given address')),
-    'username': cmd('username', 'un', user_name, 'address [name]',
-                    _(u'set, update or delete the real name for an address')),
-    'userpassword': cmd('userpassword', 'up', user_password,
-                        'address [password]',
-                        _(u'update the password for the given address')),
-    'userquota': cmd('userquota', 'uq', user_quota,
-                     'address storage [messages] | address domain',
-                     _(u'update the quota limit for the given address')),
-    'userservices': cmd('userservices', 'us', user_services,
-                        'address [service ...] | address domain',
-                        _(u'enables the specified services and disables all '
-                          u'not specified services')),
-    'usertransport': cmd('usertransport', 'ut', user_transport,
-                         'address transport | address domain',
-                         _(u'update the transport of the given address')),
-    'usernote': cmd('usernote', 'uo', user_note, 'address [note]',
-                    _(u'set, update or delete the note of the given address')),
-    # Alias commands
-    'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
-                    _(u'create a new alias e-mail address with one or more '
-                      u'destinations')),
-    'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
-                       'address [destination ...]',
-                       _(u'delete the specified alias e-mail address or one '
-                         u'of its destinations')),
-    'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
-                     _(u'show the destination(s) of the specified alias')),
-    # AliasDomain commands
-    'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
-                          'fqdn destination',
-                          _(u'create a new alias for an existing domain')),
-    'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
-                             'fqdn', _(u'delete the specified alias domain')),
-    'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
-                         _(u'show the destination of the given alias domain')),
-    'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
-                             'fqdn destination', _(u'assign the given alias '
-                             'domain to an other domain')),
-    # CatchallAlias commands
-    'catchalladd': cmd('catchalladd', 'caa', catchall_add,
-                       'fqdn destination ...',
-                       _(u'add one or more catch-all destinations for a '
-                         u'domain')),
-    'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete,
-                       'fqdn [destination ...]',
-                       _(u'delete the specified catch-all destination or all '
-                         u'of a domain\'s destinations')),
-    'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn',
-                        _(u'show the catch-all destination(s) of the '
-                          u'specified domain')),
-    # Domain commands
-    'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
-                     _(u'create a new domain')),
-    'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
-                      _(u'delete the given domain and all its alias domains')),
-    'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
-                      _(u'display information about the given domain')),
-    'domainquota': cmd('domainquota', 'dq', domain_quota,
-                       'fqdn storage [messages] [force]',
-                       _(u'update the quota limit of the specified domain')),
-    'domainservices': cmd('domainservices', 'ds', domain_services,
-                          'fqdn [service ...] [force]',
-                          _(u'enables the specified services and disables all '
-                            u'not specified services of the given domain')),
-    'domaintransport': cmd('domaintransport', 'dt', domain_transport,
-                           'fqdn transport [force]',
-                           _(u'update the transport of the specified domain')),
-    'domainnote': cmd('domainnote', 'do', domain_note, 'fqdn [note]',
-                     _(u'set, update or delete the note of the given domain')),
-    # List commands
-    'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
-                      _(u'list all domains or search for domains by pattern')),
-    'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]',
-                         _(u'list all addresses or search for addresses by '
-                           u'pattern')),
-    'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
-                     _(u'list all user accounts or search for accounts by '
-                       u'pattern')),
-    'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]',
-                      _(u'list all aliases or search for aliases by pattern')),
-    'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]',
-                         _(u'list all relocated users or search for relocated '
-                           u'users by pattern')),
-    # Relocated commands
-    'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
-                        'address newaddress',
-                        _(u'create a new record for a relocated user')),
-    'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
-                           'address',
-                           _(u'delete the record of the relocated user')),
-    'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
-                         _(u'print information about a relocated user')),
-    # cli commands
-    'configget': cmd('configget', 'cg', config_get, 'option',
-                     _('show the actual value of the configuration option')),
-    'configset': cmd('configset', 'cs', config_set, 'option value',
-                      _('set a new value for the configuration option')),
-    'configure': cmd('configure', 'cf', configure, '[section]',
-                     _(u'start interactive configuration mode')),
-    'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '',
-                         _(u'lists all usable password schemes and password '
-                           u'encoding suffixes')),
-    'help': cmd('help', 'h', help_, '[subcommand]',
-                _(u'show a help overview or help for the given subcommand')),
-    'version': cmd('version', 'v', version, '',
-                   _(u'show version and copyright information')),
-    })
+def setup_parser():
+    """Create the argument parser, add all the subcommands and return it."""
+    class ArgParser(ArgumentParser):
+        """This class fixes the 'width detection'."""
+        def _get_formatter(self):
+            return self.formatter_class(prog=self.prog, width=WS_ROWS,
+                                        max_help_position=26)
+
+    class VersionAction(Action):
+        """Show version and copyright information."""
+        def __call__(self, parser, namespace, values, option_string=None):
+            """implements the Action API."""
+            vers_info = _('{program}, version {version} (from {rel_date})\n'
+                          'Python {py_vers} on {sysname}'.format(
+                              program=parser.prog, version=__version__,
+                              rel_date=strftime(
+                                            locale.nl_langinfo(locale.D_FMT),
+                                            strptime(__date__, '%Y-%m-%d')),
+                              py_vers=platform.python_version(),
+                              sysname=platform.system()))
+            copy_info = _('{copyright}\n{program} is free software and comes '
+                          'with ABSOLUTELY NO WARRANTY.'.format(
+                              copyright=__copyright__, program=parser.prog))
+            parser.exit(message='\n\n'.join((vers_info, copy_info)) + '\n')
+
+    def quota_storage(string):
+        if string == 'domain':
+            return string
+        try:
+            storage = size_in_bytes(string)
+        except (TypeError, ValueError) as error:
+            raise ArgumentTypeError(str(error))
+        return storage
+
+    old_rw = txt_wrpr.replace_whitespace
+    txt_wrpr.replace_whitespace = False
+    fill = lambda t: '\n'.join(txt_wrpr.fill(l) for l in t.splitlines(True))
+    mklst = lambda iterable: '\n\t - ' + '\n\t - '.join(iterable)
+
+    description = _('%(prog)s - command line tool to manage email '
+                    'domains/accounts/aliases/...')
+    epilog = _('use "%(prog)s <subcommand> -h" for information about the '
+               'given subcommand')
+    parser = ArgParser(description=description, epilog=epilog)
+    parser.add_argument('-v', '--version', action=VersionAction, nargs=0,
+                        help=_("show %(prog)s's version and copyright "
+                               "information and exit"))
+    subparsers = parser.add_subparsers(metavar=_('<subcommand>'),
+                                     title=_('list of available subcommands'))
+    a = subparsers.add_parser
+
+    ###
+    # general subcommands
+    ###
+    cg = a('configget', aliases=('cg',),
+           help=_('show the actual value of the configuration option'),
+           epilog=_("This subcommand is used to display the actual value of "
+           "the given configuration option."))
+    cg.add_argument('option', help=_('the name of a configuration option'))
+    cg.set_defaults(func=config_get, scmd='configget')
+
+    cs = a('configset', aliases=('cs',),
+           help=_('set a new value for the configuration option'),
+           epilog=fill(_("Use this subcommand to set or update a single "
+               "configuration option's value. option is the configuration "
+               "option, value is the option's new value.\n\nNote: This "
+               "subcommand will create a new vmm.cfg without any comments. "
+               "Your current configuration file will be backed as "
+               "vmm.cfg.bak.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    cs.add_argument('option', help=_('the name of a configuration option'))
+    cs.add_argument('value', help=_("the option's new value"))
+    cs.set_defaults(func=config_set, scmd='configset')
+
+    sections = ('account', 'bin', 'database', 'domain', 'mailbox', 'misc')
+    cf = a('configure', aliases=('cf',),
+           help=_('start interactive configuration mode'),
+           epilog=fill(_("Starts the interactive configuration for all "
+               "configuration sections.\n\nIn this process the currently set "
+               "value of each option will be displayed in square brackets. "
+               "If no value is configured, the default value of each option "
+               "will be displayed in square brackets. Press the return key, "
+               "to accept the displayed value.\n\n"
+               "If the optional argument section is given, only the "
+               "configuration options from the given section will be "
+               "displayed and will be configurable. The following sections "
+               "are available:\n") + mklst(sections)),
+           formatter_class=RawDescriptionHelpFormatter)
+    cf.add_argument('-s', choices=sections, metavar='SECTION', dest='section',
+                    help=_("configure only options of the given section"))
+    cf.set_defaults(func=configure, scmd='configure')
+
+    gu = a('getuser', aliases=('gu',),
+           help=_('get the address of the user with the given UID'),
+           epilog=_("If only the uid is available, for example from process "
+                    "list, the subcommand getuser will show the user's "
+                    "address."))
+    gu.add_argument('uid', type=int, help=_("a user's unique identifier"))
+    gu.set_defaults(func=get_user, scmd='getuser')
+
+    ll = a('listaddresses', aliases=('ll',),
+           help=_('list all addresses or search for addresses by pattern'),
+           epilog=fill(_("This command lists all defined addresses. "
+               "Addresses belonging to alias-domains are prefixed with a '-', "
+               "addresses of regular domains with a '+'. Additionally, the "
+               "letters 'u', 'a', and 'r' indicate the type of each address: "
+               "user, alias and relocated respectively. The output can be "
+               "limited with an optional pattern.\n\nTo perform a wild card "
+               "search, the % character can be used at the start and/or the "
+               "end of the pattern.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    ll.add_argument('-p', help=_("the pattern to search for"),
+                    metavar='PATTERN', dest='pattern')
+    ll.set_defaults(func=list_addresses, scmd='listaddresses')
+
+    la = a('listaliases', aliases=('la',),
+           help=_('list all aliases or search for aliases by pattern'),
+           epilog=fill(_("This command lists all defined aliases. Aliases "
+               "belonging to alias-domains are prefixed with a '-', addresses "
+               "of regular domains with a '+'. The output can be limited "
+               "with an optional pattern.\n\nTo perform a wild card search, "
+               "the % character can be used at the start and/or the end of "
+               "the pattern.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    la.add_argument('-p', help=_("the pattern to search for"),
+                    metavar='PATTERN', dest='pattern')
+    la.set_defaults(func=list_aliases, scmd='listaliases')
+
+    ld = a('listdomains', aliases=('ld',),
+           help=_('list all domains or search for domains by pattern'),
+           epilog=fill(_("This subcommand lists all available domains. All "
+               "domain names will be prefixed either with `[+]', if the "
+               "domain is a primary domain, or with `[-]', if it is an alias "
+               "domain name. The output can be limited with an optional "
+               "pattern.\n\nTo perform a wild card search, the % character "
+               "can be used at the start and/or the end of the pattern.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    ld.add_argument('-p', help=_("the pattern to search for"),
+                    metavar='PATTERN', dest='pattern')
+    ld.set_defaults(func=list_domains, scmd='listdomains')
+
+    lr = a('listrelocated', aliases=('lr',),
+           help=_('list all relocated users or search for relocated users by '
+                  'pattern'),
+           epilog=fill(_("This command lists all defined relocated addresses. "
+               "Relocated entries belonging to alias-domains are prefixed "
+               "with a '-', addresses of regular domains with a '+'. The "
+               "output can be limited with an optional pattern.\n\nTo "
+               "perform a wild card search, the % character can be used at "
+               "the start and/or the end of the pattern.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    lr.add_argument('-p', help=_("the pattern to search for"),
+                    metavar='PATTERN', dest='pattern')
+    lr.set_defaults(func=list_relocated, scmd='listrelocated')
+
+    lu = a('listusers', aliases=('lu',),
+           help=_('list all user accounts or search for accounts by pattern'),
+           epilog=fill(_("This command lists all user accounts. User accounts "
+               "belonging to alias-domains are prefixed with a '-', "
+               "addresses of regular domains with a '+'. The output can be "
+               "limited with an optional pattern.\n\nTo perform a wild card "
+               "search, the % character can be used at the start and/or the "
+               "end of the pattern.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    lu.add_argument('-p', help=_("the pattern to search for"),
+                    metavar='PATTERN', dest='pattern')
+    lu.set_defaults(func=list_users, scmd='listusers')
+
+    lp = a('listpwschemes', aliases=('lp',),
+           help=_('lists all usable password schemes and password encoding '
+                  'suffixes'),
+           epilog=fill(_("This subcommand lists all password schemes which "
+               "could be used in the vmm.cfg as value of the "
+               "misc.password_scheme option. The output varies, depending "
+               "on the used Dovecot version and the system's libc.\n"
+               "Additionally a few usable encoding suffixes will be "
+               "displayed. One of them can be appended to the password "
+               "scheme.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    lp.set_defaults(func=list_pwschemes, scmd='listpwschemes')
+
+    ###
+    # domain subcommands
+    ###
+    da = a('domainadd', aliases=('da',), help=_('create a new domain'),
+           epilog=fill(_("Adds the new domain into the database and creates "
+               "the domain directory.\n\nIf the optional argument transport "
+               "is given, it will override the default transport "
+               "(domain.transport) from vmm.cfg. The specified transport "
+               "will be the default transport for all new accounts in this "
+               "domain.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    da.add_argument('fqdn', help=_('a fully qualified domain name'))
+    da.add_argument('-n', metavar='NOTE', dest='note',
+                    help=_('the note that should be set'))
+    da.add_argument('-t', metavar='TRANSPORT', dest='transport',
+                    help=_('a Postfix transport (transport: or '
+                           'transport:nexthop)'))
+    da.set_defaults(func=domain_add, scmd='domainadd')
+
+    details = ('accounts', 'aliasdomains', 'aliases', 'catchall', 'relocated',
+               'full')
+    di = a('domaininfo', aliases=('di',),
+           help=_('display information about the given domain'),
+           epilog=fill(_("This subcommand shows some information about the "
+               "given domain.\n\nFor a more detailed information about the "
+               "domain the optional argument details can be specified. A "
+               "possible details value can be one of the following six "
+               "keywords:\n") + mklst(details)),
+           formatter_class=RawDescriptionHelpFormatter)
+    di.add_argument('fqdn', help=_('a fully qualified domain name'))
+    di.add_argument('-d', choices=details, dest='details', metavar='DETAILS',
+                    help=_('additionally details to display'))
+    di.set_defaults(func=domain_info, scmd='domaininfo')
+
+    do = a('domainnote', aliases=('do',),
+           help=_('set, update or delete the note of the given domain'),
+           epilog=_('With this subcommand, it is possible to attach a note to '
+                    'the specified domain. In order to delete an existing '
+                    'note, pass the -d option.'))
+    do.add_argument('fqdn', help=_('a fully qualified domain name'))
+    do_grp = do.add_mutually_exclusive_group(required=True)
+    do_grp.add_argument('-d', action='store_true', dest='delete',
+                        help=_('delete the note, if any'))
+    do_grp.add_argument('-n', metavar='NOTE', dest='note',
+                        help=_('the note that should be set'))
+    do.set_defaults(func=domain_note, scmd='domainnote')
+
+    dq = a('domainquota', aliases=('dq',),
+           help=_('update the quota limit of the specified domain'),
+           epilog=fill(_("This subcommand is used to configure a new quota "
+               "limit for the accounts of the domain - not for the domain "
+               "itself.\n\nThe default quota limit for accounts is defined "
+               "in the vmm.cfg (domain.quota_bytes and "
+               "domain.quota_messages).\n\nThe new quota limit will affect "
+               "only those accounts for which the limit has not been "
+               "overridden. If you want to restore the default to all "
+               "accounts, you may pass the optional argument --force. When "
+               "the argument messages was omitted the default number of "
+               "messages 0 (zero) will be applied.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    dq.add_argument('fqdn', help=_('a fully qualified domain name'))
+    dq.add_argument('storage', type=quota_storage,
+                    help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+                           'or 2048M',))
+    dq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+                    dest='messages',
+                    help=_('quota limit in number of messages (default: 0)'))
+    dq.add_argument('--force', action='store_true',
+                    help=_('enforce the limit for all accounts'))
+    dq.set_defaults(func=domain_quota, scmd='domainquota')
+
+    ds = a('domainservices', aliases=('ds',),
+           help=_('enables the specified services and disables all not '
+                  'specified services of the given domain'),
+           epilog=fill(_("To define which services could be used by the users "
+               "of the domain — with the given fqdn — use this "
+               "subcommand.\n\nEach specified service will be enabled/"
+               "usable. All other services will be deactivated/unusable. "
+               "Possible service names are: imap, pop3, sieve and smtp.\nThe "
+               "new service set will affect only those accounts for which "
+               "the set has not been overridden. If you want to restore the "
+               "default to all accounts, you may pass the option--force.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    ds.add_argument('fqdn', help=_('a fully qualified domain name'))
+    ds.add_argument('-s', choices=SERVICES,
+                    help=_('services which should be usable'),
+                    metavar='SERVICE', nargs='+', dest='services')
+    ds.add_argument('--force', action='store_true',
+                    help=_('enforce the service set for all accounts'))
+    ds.set_defaults(func=domain_services, scmd='domainservices')
+
+    dt = a('domaintransport', aliases=('dt',),
+           help=_('update the transport of the specified domain'),
+           epilog=fill(_("A new transport for the indicated domain can be set "
+               "with this subcommand.\n\nThe new transport will affect only "
+               "those accounts for which the transport has not been "
+               "overridden. If you want to restore the default to all "
+               "accounts, you may give the option --force.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    dt.add_argument('fqdn', help=_('a fully qualified domain name'))
+    dt.add_argument('transport', help=_('a Postfix transport (transport: or '
+                                        'transport:nexthop)'))
+    dt.add_argument('--force', action='store_true',
+                    help=_('enforce the transport for all accounts'))
+    dt.set_defaults(func=domain_transport, scmd='domaintransport')
+
+    dd = a('domaindelete', aliases=('dd',),
+           help=_('delete the given domain and all its alias domains'),
+           epilog=fill(_("This subcommand deletes the domain specified by "
+               "fqdn.\n\nIf there are accounts, aliases and/or relocated "
+               "users assigned to the given domain, vmm will abort the "
+               "requested operation and show an error message. If you know, "
+               "what you are doing, you can specify the optional argument "
+               "--force.\n\nIf you really always know what you are doing, "
+               "edit your vmm.cfg and set the option domain.force_deletion "
+               "to true.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    dd.add_argument('fqdn', help=_('a fully qualified domain name'))
+    dd.add_argument('--force', action='store_true',
+                    help=_('also delete all accounts, aliases and/or '
+                           'relocated users'))
+    dd.set_defaults(func=domain_delete, scmd='domaindelete')
+
+    ###
+    # alias domain subcommands
+    ###
+    ada = a('aliasdomainadd', aliases=('ada',),
+            help=_('create a new alias for an existing domain'),
+            epilog=_('This subcommand adds the new alias domain (fqdn) to '
+                     'the destination domain that should be aliased.'))
+    ada.add_argument('fqdn', help=_('a fully qualified domain name'))
+    ada.add_argument('destination',
+                     help=_('the fqdn of the destination domain'))
+    ada.set_defaults(func=aliasdomain_add, scmd='aliasdomainadd')
+
+    adi = a('aliasdomaininfo', aliases=('adi',),
+            help=_('show the destination of the given alias domain'),
+            epilog=_('This subcommand shows to which domain the alias domain '
+                     'fqdn is assigned to.'))
+    adi.add_argument('fqdn', help=_('a fully qualified domain name'))
+    adi.set_defaults(func=aliasdomain_info, scmd='aliasdomaininfo')
+
+    ads = a('aliasdomainswitch', aliases=('ads',),
+            help=_('assign the given alias domain to an other domain'),
+            epilog=_('If the destination of the existing alias domain fqdn '
+                     'should be switched to another destination use this '
+                     'subcommand.'))
+    ads.add_argument('fqdn', help=_('a fully qualified domain name'))
+    ads.add_argument('destination',
+                     help=_('the fqdn of the destination domain'))
+    ads.set_defaults(func=aliasdomain_switch, scmd='aliasdomainswitch')
+
+    add = a('aliasdomaindelete', aliases=('add',),
+            help=_('delete the specified alias domain'),
+            epilog=_('Use this subcommand if the alias domain fqdn should be '
+                     'removed.'))
+    add.add_argument('fqdn', help=_('a fully qualified domain name'))
+    add.set_defaults(func=aliasdomain_delete, scmd='aliasdomaindelete')
+
+    ###
+    # account subcommands
+    ###
+    ua = a('useradd', aliases=('ua',),
+           help=_('create a new e-mail user with the given address'),
+           epilog=fill(_('Use this subcommand to create a new e-mail account '
+               'for the given address.\n\nIf the password is not provided, '
+               'vmm will prompt for it interactively. When no password is '
+               'provided and account.random_password is set to true, vmm '
+               'will generate a random password and print it to stdout '
+               'after the account has been created.')),
+           formatter_class=RawDescriptionHelpFormatter)
+    ua.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    ua.add_argument('-n', metavar='NOTE', dest='note',
+                    help=_('the note that should be set'))
+    ua.add_argument('-p', metavar='PASSWORD', dest='password',
+                    help=_("the new user's password"))
+    ua.set_defaults(func=user_add, scmd='useradd')
+
+    details = ('aliases', 'du', 'full')
+    ui = a('userinfo', aliases=('ui',),
+           help=_('display information about the given address'),
+           epilog=fill(_('This subcommand displays some information about '
+               'the account specified by the given address.\n\nIf the '
+               'optional argument details is given some more information '
+               'will be displayed.\nPossible values for details are:\n') +
+               mklst(details)),
+           formatter_class=RawDescriptionHelpFormatter)
+    ui.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    ui.add_argument('-d', choices=details, metavar='DETAILS', dest='details',
+                    help=_('additionally details to display'))
+    ui.set_defaults(func=user_info, scmd='userinfo')
+
+    un = a('username', aliases=('un',),
+           help=_('set, update or delete the real name for an address'),
+           epilog=fill(_("The user's real name can be set/updated with this "
+               "subcommand.\n\nIn order to delete the value stored for the "
+               "account, pass the -d option.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    un.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    un_grp = un.add_mutually_exclusive_group(required=True)
+    un_grp.add_argument('-d', action='store_true', dest='delete',
+                        help=_("delete the user's name if any"))
+    un_grp.add_argument('-n', help=_("a user's real name"), metavar='NAME',
+                        dest='name')
+    un.set_defaults(func=user_name, scmd='username')
+
+    uo = a('usernote', aliases=('uo',),
+           help=_('set, update or delete the note of the given address'),
+           epilog=_('With this subcommand, it is possible to attach a note to '
+               'the specified account. In order to delete an existing note, '
+               'pass the -d option.'))
+    uo.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    uo_grp = uo.add_mutually_exclusive_group(required=True)
+    uo_grp.add_argument('-d', action='store_true', dest='delete',
+                        help=_('delete the note, if any'))
+    uo_grp.add_argument('-n', metavar='NOTE', dest='note',
+                        help=_('the note that should be set'))
+    uo.set_defaults(func=user_note, scmd='usernote')
+
+    up = a('userpassword', aliases=('up',),
+           help=_('update the password for the given address'),
+           epilog=fill(_("The password of an account can be updated with this "
+               "subcommand.\n\nIf neither a password nor a password hash was "
+               "provided, vmm will prompt for the new password interactively. "
+               "When the scheme was omitted, vmm will use "
+               "misc.password_scheme from vmm.cfg. ")),
+           formatter_class=RawDescriptionHelpFormatter)
+    up.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    up_grp = up.add_mutually_exclusive_group()
+    up_grp.add_argument('-p', metavar='PASSWORD', dest='password',
+                        help=_("the user's new password"))
+    up.add_argument('-s', metavar='SCHEME', dest='scheme',
+                    help=_('scheme used for password hashing'))
+    up_grp.add_argument('--hash', metavar='PWHASH', dest='pwhash',
+                        help=_('set the given password hash as-is as new '
+                               'password'))
+    up.set_defaults(func=user_password, scmd='userpassword')
+
+    uq = a('userquota', aliases=('uq',),
+           help=_('update the quota limit for the given address'),
+           epilog=fill(_("This subcommand is used to set a new quota limit "
+               "for the given account.\n\nWhen the argument messages was "
+               "omitted the default number of messages 0 (zero) will be "
+               "applied.\n\nInstead of a storage limit pass the keyword "
+               "'domain' to remove the account-specific override, causing "
+               "the domain's value to be in effect.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    uq.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    uq.add_argument('storage', type=quota_storage,
+                    help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+                           'or 2048M'))
+    uq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+                    dest='messages',
+                    help=_('quota limit in number of messages (default: 0)'))
+    uq.set_defaults(func=user_quota, scmd='userquota')
+
+    us = a('userservices', aliases=('us',),
+           help=_('enable the specified services and disables all not '
+                  'specified services'),
+           epilog=fill(_("To grant a user access to the specified service(s), "
+               "use this command.\n\nAll omitted services will be "
+               "deactivated/unusable for the user with the given "
+               "address.\n\nInstead of any service pass the keyword "
+               "'domain' to remove the account-specific override, causing "
+               "the domain's value to be in effect.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    us.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    us.add_argument('-s', choices=SERVICES + ('domain',),
+                    help=_('services which should be usable'),
+                    metavar='SERVICE', nargs='+', dest='services')
+    us.set_defaults(func=user_services, scmd='userservices')
+
+    ut = a('usertransport', aliases=('ut',),
+           help=_('update the transport of the given address'),
+           epilog=fill(_("A different transport for an account can be "
+               "specified with this subcommand.\n\nInstead of a transport "
+               "pass the keyword 'domain' to remove the account-specific "
+               "override, causing the domain's value to be in effect.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    ut.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    ut.add_argument('transport', help=_('a Postfix transport (transport: or '
+                                        'transport:nexthop)'))
+    ut.set_defaults(func=user_transport, scmd='usertransport')
+
+    ud = a('userdelete', aliases=('ud',),
+           help=_('delete the specified user'),
+           epilog=fill(_('Use this subcommand to delete the account with the '
+               'given address.\n\nIf there are one or more aliases with an '
+               'identical destination address, vmm will abort the requested '
+               'operation and show an error message. To prevent this, '
+               'give the optional argument --force.')),
+           formatter_class=RawDescriptionHelpFormatter)
+    ud.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    ud.add_argument('--force', action='store_true',
+                    help=_('also delete assigned alias addresses'))
+    ud.set_defaults(func=user_delete, scmd='userdelete')
+
+    ###
+    # alias subcommands
+    ###
+    aa = a('aliasadd', aliases=('aa',),
+           help=_('create a new alias e-mail address with one or more '
+                  'destinations'),
+           epilog=fill(_("This subcommand is used to create a new alias "
+               "address with one or more destination addresses.\n\nWithin "
+               "the destination address, the placeholders %n, %d, and %= "
+               "will be replaced by the local part, the domain, or the "
+               "email address with '@' replaced by '=' respectively. In "
+               "combination with alias domains, this enables "
+               "domain-specific destinations.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    aa.add_argument('address',
+                    help=_("an alias' e-mail address (local-part@fqdn)"))
+    aa.add_argument('destination', nargs='+',
+                    help=_("a destination's e-mail address (local-part@fqdn)"))
+    aa.set_defaults(func=alias_add, scmd='aliasadd')
+
+    ai = a('aliasinfo', aliases=('ai',),
+           help=_('show the destination(s) of the specified alias'),
+           epilog=_('Information about the alias with the given address can '
+                    'be displayed with this subcommand.'))
+    ai.add_argument('address',
+                    help=_("an alias' e-mail address (local-part@fqdn)"))
+    ai.set_defaults(func=alias_info, scmd='aliasinfo')
+
+    ad = a('aliasdelete', aliases=('ad',),
+           help=_('delete the specified alias e-mail address or one of its '
+                  'destinations'),
+           epilog=fill(_("This subcommand is used to delete one or multiple "
+               "destinations from the alias with the given address.\n\nWhen "
+               "no destination address was specified the alias with all its "
+               "destinations will be deleted.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    ad.add_argument('address',
+                    help=_("an alias' e-mail address (local-part@fqdn)"))
+    ad.add_argument('destination', nargs='*',
+                    help=_("a destination's e-mail address (local-part@fqdn)"))
+    ad.set_defaults(func=alias_delete, scmd='aliasdelete')
+
+    ###
+    # catch-all subcommands
+    ###
+    caa = a('catchalladd', aliases=('caa',),
+            help=_('add one or more catch-all destinations for a domain'),
+            epilog=fill(_('This subcommand allows to specify destination '
+                'addresses for a domain, which shall receive mail addressed '
+                'to unknown local parts within that domain. Those catch-all '
+                'aliases hence "catch all" mail to any address in the domain '
+                '(unless a more specific alias, mailbox or relocated entry '
+                'exists).\n\nWARNING: Catch-all addresses can cause mail '
+                'server flooding because spammers like to deliver mail to '
+                'all possible combinations of names, e.g. to all addresses '
+                'between abba@example.org and zztop@example.org.')),
+           formatter_class=RawDescriptionHelpFormatter)
+    caa.add_argument('fqdn', help=_('a fully qualified domain name'))
+    caa.add_argument('destination', nargs='+',
+                    help=_("a destination's e-mail address (local-part@fqdn)"))
+    caa.set_defaults(func=catchall_add, scmd='catchalladd')
+
+    cai = a('catchallinfo', aliases=('cai',),
+            help=_('show the catch-all destination(s) of the specified '
+                   'domain'),
+            epilog=_('This subcommand displays information about catch-all '
+                     'aliases defined for a domain.'))
+    cai.add_argument('fqdn', help=_('a fully qualified domain name'))
+    cai.set_defaults(func=catchall_info, scmd='catchallinfo')
+
+    cad = a('catchalldelete', aliases=('cad',),
+            help=_("delete the specified catch-all destination or all of a "
+                   "domain's destinations"),
+            epilog=_('With this subcommand, catch-all aliases defined for a '
+                     'domain can be removed, either all of them, or those '
+                     'destinations which were specified explicitly.'))
+    cad.add_argument('fqdn', help=_('a fully qualified domain name'))
+    cad.add_argument('destination', nargs='*',
+                    help=_("a destination's e-mail address (local-part@fqdn)"))
+    cad.set_defaults(func=catchall_delete, scmd='catchalldelete')
+
+    ###
+    # relocated subcommands
+    ###
+    ra = a('relocatedadd', aliases=('ra',),
+           help=_('create a new record for a relocated user'),
+           epilog=_("A new relocated user can be created with this "
+                    "subcommand."))
+    ra.add_argument('address', help=_("a relocated user's e-mail address "
+                                      "(local-part@fqdn)"))
+    ra.add_argument('newaddress',
+                   help=_('e-mail address where the user can be reached now'))
+    ra.set_defaults(func=relocated_add, scmd='relocatedadd')
+
+    ri = a('relocatedinfo', aliases=('ri',),
+           help=_('print information about a relocated user'),
+           epilog=_('This subcommand shows the new address of the relocated '
+                    'user with the given address.'))
+    ri.add_argument('address', help=_("a relocated user's e-mail address "
+                                      "(local-part@fqdn)"))
+    ri.set_defaults(func=relocated_info, scmd='relocatedinfo')
+
+    rd = a('relocateddelete', aliases=('rd',),
+           help=_('delete the record of the relocated user'),
+           epilog=_('Use this subcommand in order to delete the relocated '
+                    'user with the given address.'))
+    rd.add_argument('address', help=_("a relocated user's e-mail address "
+                                      "(local-part@fqdn)"))
+    rd.set_defaults(func=relocated_delete, scmd='relocateddelete')
+
+    txt_wrpr.replace_whitespace = old_rw
+    return parser
 
 
 def _get_order(ctx):
     """returns a tuple with (key, 1||0) tuples. Used by functions, which
     get a dict from the handler."""
     order = ()
-    if ctx.scmd == 'domaininfo':
-        order = ((u'domain name', 0), (u'gid', 1), (u'domain directory', 0),
-                 (u'quota limit/user', 0), (u'active services', 0),
-                 (u'transport', 0), (u'alias domains', 0), (u'accounts', 0),
-                 (u'aliases', 0), (u'relocated', 0), (u'catch-all dests', 0))
-    elif ctx.scmd == 'userinfo':
-        if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
+    if ctx.args.scmd == 'domaininfo':
+        order = (('domain name', 0), ('gid', 1), ('domain directory', 0),
+                 ('quota limit/user', 0), ('active services', 0),
+                 ('transport', 0), ('alias domains', 0), ('accounts', 0),
+                 ('aliases', 0), ('relocated', 0), ('catch-all dests', 0))
+    elif ctx.args.scmd == 'userinfo':
+        if ctx.args.details in ('du', 'full') or \
            ctx.cget('account.disk_usage'):
-            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
-                     (u'home', 0), (u'mail_location', 0),
-                     (u'quota storage', 0), (u'quota messages', 0),
-                     (u'disk usage', 0), (u'transport', 0), (u'smtp', 1),
-                     (u'pop3', 1), (u'imap', 1), (u'sieve', 1))
+            order = (('address', 0), ('name', 0), ('uid', 1), ('gid', 1),
+                     ('home', 0), ('mail_location', 0),
+                     ('quota storage', 0), ('quota messages', 0),
+                     ('disk usage', 0), ('transport', 0), ('smtp', 1),
+                     ('pop3', 1), ('imap', 1), ('sieve', 1))
         else:
-            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
-                     (u'home', 0), (u'mail_location', 0),
-                     (u'quota storage', 0), (u'quota messages', 0),
-                     (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
-                     (u'imap', 1), (u'sieve', 1))
-    elif ctx.scmd == 'getuser':
-        order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
+            order = (('address', 0), ('name', 0), ('uid', 1), ('gid', 1),
+                     ('home', 0), ('mail_location', 0),
+                     ('quota storage', 0), ('quota messages', 0),
+                     ('transport', 0), ('smtp', 1), ('pop3', 1),
+                     ('imap', 1), ('sieve', 1))
+    elif ctx.args.scmd == 'getuser':
+        order = (('uid', 1), ('gid', 1), ('address', 0))
     return order
 
 
@@ -950,43 +1068,37 @@
         }
     else:
         q_usage = {
-            'used': locale.format('%d', used, True).decode(ENCODING,
-                                                           'replace'),
-            'limit': locale.format('%d', limit, True).decode(ENCODING,
-                                                             'replace'),
+            'used': locale.format('%d', used, True),
+            'limit': locale.format('%d', limit, True),
         }
     if limit:
         q_usage['percent'] = locale.format('%6.2f', 100. / limit * used, True)
     else:
         q_usage['percent'] = locale.format('%6.2f', 0, True)
-    #  Py25: fmt = format_domain_default if domaindefault else lambda s: s
-    if domaindefault:
-        fmt = format_domain_default
-    else:
-        fmt = lambda s: s
+    fmt = format_domain_default if domaindefault else lambda s: s
     # TP: e.g.: [  0.00%] 21.09 KiB/1.00 GiB
-    return fmt(_(u'[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
+    return fmt(_('[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
 
 
 def _print_info(ctx, info, title):
     """Print info dicts."""
     # TP: used in e.g. 'Domain information' or 'Account information'
-    msg = u'%s %s' % (title, _(u'information'))
-    w_std(msg, u'-' * len(msg))
+    msg = '%s %s' % (title, _('information'))
+    w_std(msg, '-' * len(msg))
     for key, upper in _get_order(ctx):
         if upper:
-            w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key]))
+            w_std('\t%s: %s' % (key.upper().ljust(17, '.'), info[key]))
         else:
-            w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key]))
-    print
+            w_std('\t%s: %s' % (key.title().ljust(17, '.'), info[key]))
+    print()
     note = info.get('note')
     if note:
         _print_note(note + '\n')
 
 
 def _print_note(note):
-    msg = _(u'Note')
-    w_std(msg, u'-' * len(msg))
+    msg = _('Note')
+    w_std(msg, '-' * len(msg))
     old_ii = txt_wrpr.initial_indent
     old_si = txt_wrpr.subsequent_indent
     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
@@ -1001,63 +1113,61 @@
 def _print_list(alist, title):
     """Print a list."""
     # TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
-    msg = u'%s %s' % (_(u'Existing'), title)
-    w_std(msg, u'-' * len(msg))
+    msg = '%s %s' % (_('Existing'), title)
+    w_std(msg, '-' * len(msg))
     if alist:
-        if title != _(u'alias domains'):
-            w_std(*(u'\t%s' % item for item in alist))
+        if title != _('alias domains'):
+            w_std(*('\t%s' % item for item in alist))
         else:
             for domain in alist:
                 if not domain.startswith('xn--'):
-                    w_std(u'\t%s' % domain)
+                    w_std('\t%s' % domain)
                 else:
-                    w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
-        print
+                    w_std('\t%s (%s)' % (domain,
+                                        domain.encode('utf-8').decode('idna')))
+        print()
     else:
-        w_std(_(u'\tNone'), '')
+        w_std(_('\tNone'), '')
 
 
 def _print_aliase_info(alias, destinations):
     """Print the alias address and all its destinations"""
-    title = _(u'Alias information')
-    w_std(title, u'-' * len(title))
-    w_std(_(u'\tMail for %s will be redirected to:') % alias)
-    w_std(*(u'\t     * %s' % dest for dest in destinations))
-    print
+    title = _('Alias information')
+    w_std(title, '-' * len(title))
+    w_std(_('\tMail for %s will be redirected to:') % alias)
+    w_std(*('\t     * %s' % dest for dest in destinations))
+    print()
 
 
 def _print_catchall_info(domain, destinations):
     """Print the catchall destinations of a domain"""
-    title = _(u'Catch-all information')
-    w_std(title, u'-' * len(title))
-    w_std(_(u'\tMail to unknown local-parts in domain %s will be sent to:')
+    title = _('Catch-all information')
+    w_std(title, '-' * len(title))
+    w_std(_('\tMail to unknown local-parts in domain %s will be sent to:')
           % domain)
-    w_std(*(u'\t     * %s' % dest for dest in destinations))
-    print
+    w_std(*('\t     * %s' % dest for dest in destinations))
+    print()
 
 
 def _print_relocated_info(**kwargs):
     """Print the old and new addresses of a relocated user."""
-    title = _(u'Relocated information')
-    w_std(title, u'-' * len(title))
-    w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
+    title = _('Relocated information')
+    w_std(title, '-' * len(title))
+    w_std(_("\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
 
 
 def _format_domain(domain, main=True):
     """format (prefix/convert) the domain name."""
     if domain.startswith('xn--'):
-        domain = u'%s (%s)' % (domain, domain.decode('idna'))
+        domain = '%s (%s)' % (domain, domain.encode('utf-8').decode('idna'))
     if main:
-        return u'\t[+] %s' % domain
-    return u'\t[-]     %s' % domain
+        return '\t[+] %s' % domain
+    return '\t[-]     %s' % domain
 
 
 def _print_domain_list(dids, domains, matching):
     """Print a list of (matching) domains/alias domains."""
-    if matching:
-        title = _(u'Matching domains')
-    else:
-        title = _(u'Existing domains')
+    title = _('Matching domains') if matching else _('Existing domains')
     w_std(title, '-' * len(title))
     if domains:
         for did in dids:
@@ -1067,7 +1177,7 @@
                 w_std(*(_format_domain(a, False) for a in domains[did][1:]))
     else:
         w_std(_('\tNone'))
-    print
+    print()
 
 
 def _print_address_list(which, dids, addresses, matching):
@@ -1083,9 +1193,9 @@
     }
     try:
         if matching:
-            title = _(u'Matching %s') % _trans[which]
+            title = _('Matching %s') % _trans[which]
         else:
-            title = _(u'Existing %s') % _trans[which]
+            title = _('Existing %s') % _trans[which]
         w_std(title, '-' * len(title))
     except KeyError:
         raise VMMError(_("Invalid address type for list: '%s'") % which,
@@ -1111,15 +1221,16 @@
                 w_std('\t%s %s' % (leader, addr))
     else:
         w_std(_('\tNone'))
-    print
+    print()
 
 
 def _print_aliasdomain_info(info):
     """Print alias domain information."""
-    title = _(u'Alias domain information')
+    title = _('Alias domain information')
     for key in ('alias', 'domain'):
         if info[key].startswith('xn--'):
-            info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
+            info[key] = '%s (%s)' % (info[key],
+                                     info[key].encode(ENCODING).decode('idna'))
     w_std(title, '-' * len(title),
           _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
           info, '')
--- a/VirtualMailManager/common.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/common.py	Sun Mar 09 18:52:27 2014 +0000
@@ -36,9 +36,9 @@
 
 def get_unicode(string):
     """Converts `string` to `unicode`, if necessary."""
-    if isinstance(string, unicode):
+    if isinstance(string, str):
         return string
-    return unicode(string, ENCODING, 'replace')
+    return str(string, ENCODING, 'replace')
 
 
 def lisdir(path):
@@ -60,44 +60,44 @@
     """
     binary = expand_path(binary)
     if not os.path.isfile(binary):
-        raise VMMError(_(u"No such file: '%s'") % get_unicode(binary),
+        raise VMMError(_("No such file: '%s'") % get_unicode(binary),
                        NO_SUCH_BINARY)
     if not os.access(binary, os.X_OK):
-        raise VMMError(_(u"File is not executable: '%s'") %
+        raise VMMError(_("File is not executable: '%s'") %
                        get_unicode(binary), NOT_EXECUTABLE)
     return binary
 
 
 def human_size(size):
     """Converts the `size` in bytes in human readable format."""
-    if not isinstance(size, (long, int)):
+    if not isinstance(size, int):
         try:
-            size = long(size)
+            size = int(size)
         except ValueError:
-            raise TypeError("'size' must be a positive long or int.")
+            raise TypeError("'size' must be a positive integer.")
     if size < 0:
-        raise ValueError("'size' must be a positive long or int.")
+        raise ValueError("'size' must be a positive integer.")
     if size < 1024:
         return str(size)
     # TP: abbreviations of gibibyte, tebibyte kibibyte and mebibyte
-    prefix_multiply = ((_(u'TiB'), 1 << 40), (_(u'GiB'), 1 << 30),
-                       (_(u'MiB'), 1 << 20), (_(u'KiB'), 1 << 10))
+    prefix_multiply = ((_('TiB'), 1 << 40), (_('GiB'), 1 << 30),
+                       (_('MiB'), 1 << 20), (_('KiB'), 1 << 10))
     for prefix, multiply in prefix_multiply:
         if size >= multiply:
             # TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
-            return _(u'%(size)s %(prefix)s') % {
+            return _('%(size)s %(prefix)s') % {
                     'size': locale.format('%.2f', float(size) / multiply,
-                                          True).decode(ENCODING, 'replace'),
+                                          True),
                     'prefix': prefix}
 
 
 def size_in_bytes(size):
-    """Converts the string `size` to a long (size in bytes).
+    """Converts the string `size` to an integer (size in bytes).
 
     The string `size` can be suffixed with *b* (bytes), *k* (kilobytes),
     *M* (megabytes) or *G* (gigabytes).
     """
-    if not isinstance(size, basestring) or not size:
+    if not isinstance(size, str) or not size:
         raise TypeError('size must be a non empty string.')
     if size[-1].upper() in ('B', 'K', 'M', 'G'):
         try:
@@ -108,11 +108,11 @@
         if unit == 'B':
             return num
         elif unit == 'K':
-            return num << 10L
+            return num << 10
         elif unit == 'M':
-            return num << 20L
+            return num << 20
         else:
-            return num << 30L
+            return num << 30
     else:
         try:
             num = int(size)
@@ -136,8 +136,8 @@
     """
     if transport.transport in ('virtual', 'virtual:') and \
       not maillocation.postfix:
-        raise VMMError(_(u"Invalid transport '%(transport)s' for mailbox "
-                         u"format '%(mbfmt)s'.") %
+        raise VMMError(_("Invalid transport '%(transport)s' for mailbox "
+                         "format '%(mbfmt)s'.") %
                        {'transport': transport.transport,
                         'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
 
@@ -183,21 +183,22 @@
 def version_str(version):
     """Converts a Dovecot version previously converted with version_hex back to
     a string.
-    Raises a `TypeError` if *version* is not an int/long.
+    Raises a `TypeError` if *version* is not an integer.
     Raises a `ValueError` if *version* is an incorrect int version.
     """
     global _version_cache
     if version in _version_cache:
         return _version_cache[version]
-    if not isinstance(version, (int, long)):
-        raise TypeError('Argument is not a int/long: %r', version)
+    if not isinstance(version, int):
+        raise TypeError('Argument is not a integer: %r', version)
     major = (version >> 28) & 0xFF
     minor = (version >> 20) & 0xFF
     patch = (version >> 12) & 0xFF
     level = (version >> 8) & 0x0F
     serial = version & 0xFF
 
-    levels = dict(zip(_version_level.values(), _version_level.keys()))
+    levels = dict(list(zip(list(_version_level.values()),
+                  list(_version_level.keys()))))
     if level == 0xF and not serial:
         version_string = '%u.%u.%u' % (major, minor, patch)
     elif level in levels and not patch:
@@ -214,7 +215,7 @@
     # TP: [domain default] indicates that a user's setting is the same as
     # configured in the user's domain.
     # e.g.: [  0.84%] 42/5,000 [domain default]
-    return _(u'%s [domain default]') % domaindata
+    return _('%s [domain default]') % domaindata
 
 
 def search_addresses(dbh, typelimit=None, lpattern=None, llike=False,
--- a/VirtualMailManager/config.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/config.py	Sun Mar 09 18:52:27 2014 +0000
@@ -8,19 +8,21 @@
     VMM's configuration module for simplified configuration access.
 """
 
-from ConfigParser import \
+import collections
+
+from configparser import \
      Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
      ParsingError, RawConfigParser
-from cStringIO import StringIO
+from io import StringIO
 
 from VirtualMailManager.common import VERSION_RE, \
-     exec_ok, expand_path, get_unicode, lisdir, size_in_bytes, version_hex
-from VirtualMailManager.constants import CONF_ERROR
+     exec_ok, expand_path, get_unicode, lisdir, size_in_bytes, version_hex, \
+     version_str
+from VirtualMailManager.constants import CONF_ERROR, MIN_DOVECOT_VERSION
 from VirtualMailManager.errors import ConfigError, VMMError
 from VirtualMailManager.maillocation import known_format
 from VirtualMailManager.password import verify_scheme as _verify_scheme
 
-DB_MODULES = ('psycopg2', 'pypgsql')
 DB_SSL_MODES = ('allow', 'disabled', 'prefer', 'require', 'verify-ca',
                 'verify-full')
 
@@ -83,10 +85,10 @@
         """
         if isinstance(value, bool):
             return value
-        if value.lower() in self._boolean_states:
-            return self._boolean_states[value.lower()]
+        if value.lower() in self.BOOLEAN_STATES:
+            return self.BOOLEAN_STATES[value.lower()]
         else:
-            raise ConfigValueError(_(u"Not a boolean: '%s'") %
+            raise ConfigValueError(_("Not a boolean: '%s'") %
                                    get_unicode(value))
 
     def getboolean(self, section, option):
@@ -103,9 +105,9 @@
         tmp = self.get(section, option)
         if isinstance(tmp, bool):
             return tmp
-        if not tmp.lower() in self._boolean_states:
+        if not tmp.lower() in self.BOOLEAN_STATES:
             raise ValueError('Not a boolean: %s' % tmp)
-        return self._boolean_states[tmp.lower()]
+        return self.BOOLEAN_STATES[tmp.lower()]
 
     def _get_section_option(self, section_option):
         """splits ``section_option`` (section.option) in two parts and
@@ -124,8 +126,8 @@
         sect_opt = section_option.lower().split('.')
         # TODO: cache it
         if len(sect_opt) != 2 or not sect_opt[0] or not sect_opt[1]:
-            raise BadOptionError(_(u"Bad format: '%s' - expected: "
-                                   u"section.option") %
+            raise BadOptionError(_("Bad format: '%s' - expected: "
+                                   "section.option") %
                                  get_unicode(section_option))
         if not sect_opt[0] in self._cfg:
             raise NoSectionError(sect_opt[0])
@@ -143,14 +145,14 @@
             raise NoSectionError(section)
         else:
             return ((k, self._cfg[section][k].default)
-                    for k in self._cfg[section].iterkeys())
+                    for k in self._cfg[section].keys())
         # 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())
+                        for k in self._cfg[section].keys())
         defaults.update(sect)
         if '__name__' in defaults:
             del defaults['__name__']
-        return defaults.iteritems()
+        return iter(defaults.items())
 
     def dget(self, option):
         """Returns the value of the `option`.
@@ -216,7 +218,7 @@
 
     def sections(self):
         """Returns an iterator object for all configuration sections."""
-        return self._cfg.iterkeys()
+        return iter(self._cfg.keys())
 
 
 class LazyConfigOption(object):
@@ -246,15 +248,12 @@
           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):
+        self.__default = default if default is None else self.__cls(default)
+        if not isinstance(getter, collections.Callable):
             raise TypeError('getter has to be a callable, got a %r' %
                             getter.__class__.__name__)
         self.__getter = getter
-        if validate and not callable(validate):
+        if validate and not isinstance(validate, collections.Callable):
             raise TypeError('validate has to be callable or None, got a %r' %
                             validate.__class__.__name__)
         self.__validate = validate
@@ -307,14 +306,12 @@
                 'random_password': LCO(bool_t, False, self.getboolean),
             },
             'bin': {
-                'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
-                                 exec_ok),
+                'doveadm': LCO(str, '/usr/bin/doveadm', 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),
-                'module': LCO(str, 'psycopg2', self.get, check_db_module),
                 'name': LCO(str, 'mailsys', self.get),
                 'pass': LCO(str, None, self.get),
                 'port': LCO(int, 5432, self.getint),
@@ -333,13 +330,13 @@
                 'quota_bytes': LCO(str, '0', self.get_in_bytes,
                                    check_size_value),
                 'quota_messages': LCO(int, 0, self.getint),
-                'transport': LCO(str, 'dovecot:', self.get),
+                'transport': LCO(str, 'lmtp:unix:private/dovecot-lmtp',
+                                 self.get),
             },
             'mailbox': {
-                'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
-                               self.unicode),
+                'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.str),
                 'format': LCO(str, 'maildir', self.get, check_mailbox_format),
-                'root': LCO(str, 'Maildir', self.unicode),
+                'root': LCO(str, 'Maildir', self.str),
                 'subscribe': LCO(bool_t, True, self.getboolean),
             },
             'misc': {
@@ -348,7 +345,7 @@
                 '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),
+                                       check_dovecot_version),
                 'password_scheme': LCO(str, 'CRAM-MD5', self.get,
                                        verify_scheme),
             },
@@ -360,12 +357,11 @@
         Raises a ConfigError if the configuration syntax is
         invalid.
         """
-        self._cfg_file = open(self._cfg_filename, 'r')
-        try:
-            self.readfp(self._cfg_file)
-        except (MissingSectionHeaderError, ParsingError), err:
-            raise ConfigError(str(err), CONF_ERROR)
-        self._cfg_file.close()
+        with open(self._cfg_filename, 'r', encoding='utf-8') as self._cfg_file:
+            try:
+                self.readfp(self._cfg_file)
+            except (MissingSectionHeaderError, ParsingError) as err:
+                raise ConfigError(str(err), CONF_ERROR)
 
     def check(self):
         """Performs a configuration check.
@@ -374,9 +370,9 @@
         Or some settings have a invalid value.
         """
         def iter_dict():
-            for section, options in self._missing.iteritems():
-                errmsg.write(_(u'* Section: %s\n') % section)
-                errmsg.writelines(u'    %s\n' % option for option in options)
+            for section, options in self._missing.items():
+                errmsg.write(_('* Section: %s\n') % section)
+                errmsg.writelines('    %s\n' % option for option in options)
             self._missing.clear()
 
         errmsg = None
@@ -385,19 +381,19 @@
                     'dovecot_version' in self._missing['misc']
         if self._missing:
             errmsg = StringIO()
-            errmsg.write(_(u'Check of configuration file %s failed.\n') %
+            errmsg.write(_('Check of configuration file %s failed.\n') %
                          self._cfg_filename)
-            errmsg.write(_(u'Missing options, which have no default value.\n'))
+            errmsg.write(_('Missing options, which have no default value.\n'))
             iter_dict()
         self._chk_possible_values(miss_vers)
         if self._missing:
             if not errmsg:
                 errmsg = StringIO()
-                errmsg.write(_(u'Check of configuration file %s failed.\n') %
+                errmsg.write(_('Check of configuration file %s failed.\n') %
                              self._cfg_filename)
-                errmsg.write(_(u'Invalid configuration values.\n'))
+                errmsg.write(_('Invalid configuration values.\n'))
             else:
-                errmsg.write('\n' + _(u'Invalid configuration values.\n'))
+                errmsg.write('\n' + _('Invalid configuration values.\n'))
             iter_dict()
         if errmsg:
             raise ConfigError(errmsg.getvalue(), CONF_ERROR)
@@ -409,10 +405,10 @@
 
     def get_in_bytes(self, section, option):
         """Converts the size value (e.g.: 1024k) from the *option*'s
-        value to a long"""
+        value to a integer"""
         return size_in_bytes(self.get(section, option))
 
-    def unicode(self, section, option):
+    def str(self, section, option):
         """Returns the value of the `option` from `section`, converted
         to Unicode."""
         return get_unicode(self.get(section, option))
@@ -421,9 +417,9 @@
         """Checks all section's options for settings w/o a default
         value. Missing items will be stored in _missing.
         """
-        for section in self._cfg.iterkeys():
+        for section in self._cfg.keys():
             missing = []
-            for option, value in self._cfg[section].iteritems():
+            for option, value in self._cfg[section].items():
                 if (value.default is None and
                     not RawConfigParser.has_option(self, section, option)):
                     missing.append(option)
@@ -434,32 +430,28 @@
         """Check settings for which the possible values are known."""
         if not miss_vers:
             value = self.get('misc', 'dovecot_version')
-            if not VERSION_RE.match(value):
-                self._missing['misc'] = ['dovecot_version: ' +
-                        _(u"Not a valid Dovecot version: '%s'") % value]
+            try:
+                checked = check_dovecot_version(value)
+            except ConfigValueError as err:
+                self._missing['misc'] = ['dovecot_version: %s' % str(err)]
         # section database
         db_err = []
-        value = self.dget('database.module').lower()
-        if value not in DB_MODULES:
-            db_err.append('module: ' +
-                          _(u"Unsupported database module: '%s'") % value)
-        if value == 'psycopg2':
-            value = self.dget('database.sslmode')
-            if value not in DB_SSL_MODES:
-                db_err.append('sslmode: ' +
-                              _(u"Unknown pgsql SSL mode: '%s'") % value)
+        value = self.dget('database.sslmode')
+        if value not in DB_SSL_MODES:
+            db_err.append('sslmode: ' +
+                          _("Unknown pgsql SSL mode: '%s'") % value)
         if db_err:
             self._missing['database'] = db_err
         # section mailbox
         value = self.dget('mailbox.format')
         if not known_format(value):
             self._missing['mailbox'] = ['format: ' +
-                              _(u"Unsupported mailbox format: '%s'") % value]
+                              _("Unsupported mailbox format: '%s'") % value]
         # section domain
         try:
             value = self.dget('domain.quota_bytes')
-        except (ValueError, TypeError), err:
-            self._missing['domain'] = [u'quota_bytes: ' + str(err)]
+        except (ValueError, TypeError) as err:
+            self._missing['domain'] = ['quota_bytes: ' + str(err)]
 
 
 def is_dir(path):
@@ -470,22 +462,14 @@
     path = expand_path(path)
     if lisdir(path):
         return path
-    raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path))
-
-
-def check_db_module(module):
-    """Check if the *module* is a supported pgsql module."""
-    if module.lower() in DB_MODULES:
-        return module
-    raise ConfigValueError(_(u"Unsupported database module: '%s'") %
-                           get_unicode(module))
+    raise ConfigValueError(_("No such directory: %s") % get_unicode(path))
 
 
 def check_db_ssl_mode(ssl_mode):
     """Check if the *ssl_mode* is one of the SSL modes, known by pgsql."""
     if ssl_mode in DB_SSL_MODES:
         return ssl_mode
-    raise ConfigValueError(_(u"Unknown pgsql SSL mode: '%s'") %
+    raise ConfigValueError(_("Unknown pgsql SSL mode: '%s'") %
                            get_unicode(ssl_mode))
 
 
@@ -498,7 +482,7 @@
     format = format.lower()
     if known_format(format):
         return format
-    raise ConfigValueError(_(u"Unsupported mailbox format: '%s'") %
+    raise ConfigValueError(_("Unsupported mailbox format: '%s'") %
                            get_unicode(format))
 
 
@@ -508,20 +492,24 @@
     Otherwise a `ConfigValueError` will be raised."""
     try:
         tmp = size_in_bytes(value)
-    except (TypeError, ValueError), err:
-        raise ConfigValueError(_(u"Not a valid size value: '%s'") %
+    except (TypeError, ValueError) as err:
+        raise ConfigValueError(_("Not a valid size value: '%s'") %
                                get_unicode(value))
     return value
 
 
-def check_version_format(version_string):
-    """Check if the *version_string* has the proper format, e.g.: '1.2.3'.
+def check_dovecot_version(version_string):
+    """Check if the *version_string* has the proper format, e.g.: '2.0.0',
+    and if the configured version is >= MIN_DOVECOT_VERSION.
     Returns the validated version string if it has the expected format.
     Otherwise a `ConfigValueError` will be raised.
     """
     if not VERSION_RE.match(version_string):
-        raise ConfigValueError(_(u"Not a valid Dovecot version: '%s'") %
+        raise ConfigValueError(_("Not a valid Dovecot version: '%s'") %
                                get_unicode(version_string))
+    if version_hex(version_string) < MIN_DOVECOT_VERSION:
+        raise ConfigValueError(_("vmm requires Dovecot >= %s") %
+                               version_str(MIN_DOVECOT_VERSION))
     return version_string
 
 
@@ -531,7 +519,7 @@
     """
     try:
         scheme, encoding = _verify_scheme(scheme)
-    except VMMError, err:  # 'cast' it
+    except VMMError as err:  # 'cast' it
         raise ConfigValueError(err.msg)
     if not encoding:
         return scheme
--- a/VirtualMailManager/constants.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/constants.py	Sun Mar 09 18:52:27 2014 +0000
@@ -28,12 +28,12 @@
 MIN_GID = 70000
 MIN_UID = 70000
 
+MIN_DOVECOT_VERSION = 0x20000f00
 
 # exit codes
 
 EX_SUCCESS = 0
 EX_MISSING_ARGS = 1
-EX_UNKNOWN_COMMAND = 2
 EX_USER_INTERRUPT = 3
 
 
--- a/VirtualMailManager/domain.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/domain.py	Sun Mar 09 18:52:27 2014 +0000
@@ -18,7 +18,6 @@
 from VirtualMailManager.common import validate_transport
 from VirtualMailManager.errors import VMMError, DomainError as DomErr
 from VirtualMailManager.maillocation import MailLocation
-from VirtualMailManager.pycompat import all, any
 from VirtualMailManager.quotalimit import QuotaLimit
 from VirtualMailManager.serviceset import ServiceSet
 from VirtualMailManager.transport import Transport
@@ -49,7 +48,7 @@
 
         Arguments:
 
-        `dbh` : pyPgSQL.PgSQL.Connection
+        `dbh` : psycopg2._psycopg.connection
           a database connection for the database access
         `domainname` : basestring
           The name of the domain
@@ -81,7 +80,7 @@
         dbc.close()
         if result:
             if not result[5]:
-                raise DomErr(_(u"The domain '%s' is an alias domain.") %
+                raise DomErr(_("The domain '%s' is an alias domain.") %
                              self._name, DOMAIN_ALIAS_EXISTS)
             self._gid, self._directory = result[0], result[4]
             self._qlimit = QuotaLimit(self._dbh, qid=result[1])
@@ -116,9 +115,9 @@
         result = result[0]
         if any(result):
             keys = ('account_count', 'alias_count', 'relocated_count')
-            raise DomErr(_(u'There are %(account_count)u accounts, '
-                           u'%(alias_count)u aliases and %(relocated_count)u '
-                           u'relocated users.') % dict(zip(keys, result)),
+            raise DomErr(_('There are %(account_count)u accounts, '
+                           '%(alias_count)u aliases and %(relocated_count)u '
+                           'relocated users.') % dict(list(zip(keys, result))),
                          ACCOUNT_AND_ALIAS_PRESENT)
 
     def _chk_state(self, must_exist=True):
@@ -128,10 +127,10 @@
           - or *must_exist* is `False` and the domain exists
         """
         if must_exist and self._new:
-            raise DomErr(_(u"The domain '%s' does not exist.") % self._name,
+            raise DomErr(_("The domain '%s' does not exist.") % self._name,
                          NO_SUCH_DOMAIN)
         elif not must_exist and not self._new:
-            raise DomErr(_(u"The domain '%s' already exists.") % self._name,
+            raise DomErr(_("The domain '%s' already exists.") % self._name,
                          DOMAIN_EXISTS)
 
     def _update_tables(self, column, value):
@@ -152,7 +151,7 @@
 
         `column` : basestring
           Name of the table column. Currently: qid, ssid and tid
-        `value` : long
+        `value` : int
           The referenced key
         `force` : bool
           reset existing users. Default: `False`
@@ -266,7 +265,7 @@
           The note, or None to remove
         """
         self._chk_state(False)
-        assert note is None or isinstance(note, basestring)
+        assert note is None or isinstance(note, str)
         self._note = note
 
     def save(self):
@@ -327,9 +326,6 @@
         `force` : bool
           enforce new quota limit for all accounts, default `False`
         """
-        if cfg_dget('misc.dovecot_version') < 0x10102f00:
-            raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
-                             u'Dovecot >= v1.1.2.'), VMM_ERROR)
         self._chk_state()
         assert isinstance(quotalimit, QuotaLimit)
         if not force and quotalimit == self._qlimit:
@@ -391,7 +387,7 @@
           the new note
         """
         self._chk_state()
-        assert note is None or isinstance(note, basestring)
+        assert note is None or isinstance(note, str)
         if note == self._note:
             return
         self._update_tables('note', note)
@@ -408,7 +404,7 @@
         dbc.close()
         keys = ('alias domains', 'accounts', 'aliases', 'relocated',
                 'catch-all dests')
-        info = dict(zip(keys, info))
+        info = dict(list(zip(keys, info)))
         info['gid'] = self._gid
         info['domain name'] = self._name
         info['transport'] = self._transport.transport
@@ -435,7 +431,7 @@
         dbc.close()
         accounts = []
         if users:
-            addr = u'@'.join
+            addr = '@'.join
             _dom = self._name
             accounts = [addr((account[0], _dom)) for account in users]
         return accounts
@@ -450,7 +446,7 @@
         dbc.close()
         aliases = []
         if addresses:
-            addr = u'@'.join
+            addr = '@'.join
             _dom = self._name
             aliases = [addr((alias[0], _dom)) for alias in addresses]
         return aliases
@@ -465,7 +461,7 @@
         dbc.close()
         relocated = []
         if addresses:
-            addr = u'@'.join
+            addr = '@'.join
             _dom = self._name
             relocated = [addr((address[0], _dom)) for address in addresses]
         return relocated
@@ -502,11 +498,11 @@
 
     """
     if not RE_DOMAIN.match(domainname):
-        domainname = domainname.encode('idna')
+        domainname = domainname.encode('idna').decode()
     if len(domainname) > 255:
-        raise DomErr(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
+        raise DomErr(_('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,
+        raise DomErr(_("The domain name '%s' is invalid") % domainname,
                      DOMAIN_INVALID)
     return domainname
 
--- a/VirtualMailManager/emailaddress.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/emailaddress.py	Sun Mar 09 18:52:27 2014 +0000
@@ -16,7 +16,7 @@
 from VirtualMailManager.errors import DomainError, EmailAddressError as EAErr
 
 
-RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]", re.ASCII)
 _ = lambda msg: msg
 
 
@@ -26,7 +26,7 @@
 
     def __init__(self, address, _validate=True):
         """Creates a new instance from the string/unicode ``address``."""
-        assert isinstance(address, basestring)
+        assert isinstance(address, str)
         self._localpart = None
         self._domainname = None
         if _validate:
@@ -70,16 +70,16 @@
         parts = address.split('@')
         p_len = len(parts)
         if p_len < 2:
-            raise EAErr(_(u"Missing the '@' sign in address: '%s'") % address,
+            raise EAErr(_("Missing the '@' sign in address: '%s'") % address,
                         INVALID_ADDRESS)
         elif p_len > 2:
-            raise EAErr(_(u"Too many '@' signs in address: '%s'") % address,
+            raise EAErr(_("Too many '@' signs in address: '%s'") % address,
                         INVALID_ADDRESS)
         if not parts[0]:
-            raise EAErr(_(u"Missing local-part in address: '%s'") % address,
+            raise EAErr(_("Missing local-part in address: '%s'") % address,
                         LOCALPART_INVALID)
         if not parts[1]:
-            raise EAErr(_(u"Missing domain name in address: '%s'") % address,
+            raise EAErr(_("Missing domain name in address: '%s'") % address,
                         DOMAIN_NO_NAME)
         self._localpart = check_localpart(parts[0])
         self._domainname = check_domainname(parts[1])
@@ -97,7 +97,7 @@
 
         `address`: string/unicode
           a e-mail address like user@example.com
-        `dbh`: pyPgSQL.PgSQL.Connection/pyPgSQL.PgSQL.connection
+        `dbh`: psycopg2._psycopg.connection
           a database connection for the database access
         """
         super(DestinationEmailAddress, self).__init__(address, _validate)
@@ -105,7 +105,7 @@
         if not _validate:
             try:
                 self._chk_address(address)
-            except DomainError, err:
+            except DomainError as err:
                 if err.code is DOMAIN_INVALID and \
                    address.split('@')[1] == 'localhost':
                     self._localhost = True
@@ -142,13 +142,13 @@
     invalid characters.
     """
     if len(localpart) > 64:
-        raise EAErr(_(u"The local-part '%s' is too long.") % localpart,
+        raise EAErr(_("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 = ''.join(('"%s" ' % c for c in invalid_chars))
+        raise EAErr(_("The local-part '%(l_part)s' contains invalid "
+                      "characters: %(i_chars)s") % {'l_part': localpart,
                     'i_chars': i_chars}, LOCALPART_INVALID)
     return localpart
 
--- a/VirtualMailManager/ext/postconf.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/ext/postconf.py	Sun Mar 09 18:52:27 2014 +0000
@@ -25,8 +25,8 @@
 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')
+    _parameter_re = re.compile(r'^\w+$', re.ASCII)
+    _variables_re = re.compile(r'\$\b\w+\b', re.ASCII)
 
     def __init__(self, postconf_bin):
         """Creates a new Postconf instance.
@@ -53,10 +53,10 @@
         stderr = Popen((self._bin, '-e', parameter + '=' + str(value)),
                        stderr=PIPE).communicate()[1]
         if stderr:
-            raise VMMError(stderr.strip(), VMM_ERROR)
+            raise VMMError(stderr.strip().decode(), VMM_ERROR)
 
     def read(self, parameter, expand_vars=True):
-        """Returns the parameters value.
+        """Returns the parameter's value.
 
         If expand_vars is True (default), all variables in the value will be
         expanded:
@@ -81,8 +81,8 @@
         """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' does not look like a valid "
-                             u"Postfix configuration parameter name.") %
+            raise VMMError(_("The value '%s' does not look like a valid "
+                             "Postfix configuration parameter name.") %
                            parameter, VMM_ERROR)
 
     def _expand_vars(self):
@@ -99,7 +99,7 @@
 
     def _expand_multi_vars(self, old_new):
         """Replace all $vars in self._val with their values."""
-        for old, new in old_new.iteritems():
+        for old, new in old_new.items():
             self._val = self._val.replace('$' + old, new)
 
     def _read(self, parameter):
@@ -107,8 +107,8 @@
         stdout, stderr = Popen([self._bin, '-h', parameter], stdout=PIPE,
                                stderr=PIPE).communicate()
         if stderr:
-            raise VMMError(stderr.strip(), VMM_ERROR)
-        return stdout.strip()
+            raise VMMError(stderr.strip().decode(), VMM_ERROR)
+        return stdout.strip().decode()
 
     def _read_multi(self, parameters):
         """Ask postconf for multiple configuration parameters. Returns a dict
@@ -117,9 +117,9 @@
         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)
+            raise VMMError(stderr.strip().decode(), VMM_ERROR)
         par_val = {}
-        for line in stdout.splitlines():
+        for line in stdout.decode().splitlines():
             par, val = line.split(' = ')
             par_val[par] = val
         return par_val
--- a/VirtualMailManager/handler.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/handler.py	Sun Mar 09 18:52:27 2014 +0000
@@ -16,8 +16,11 @@
 import re
 
 from shutil import rmtree
+from stat import S_IRGRP, S_IROTH, S_IWGRP, S_IWOTH
 from subprocess import Popen, PIPE
 
+import psycopg2
+
 from VirtualMailManager.account import Account
 from VirtualMailManager.alias import Alias
 from VirtualMailManager.aliasdomain import AliasDomain
@@ -37,7 +40,7 @@
 from VirtualMailManager.errors import \
      DomainError, NotRootError, PermissionError, VMMError
 from VirtualMailManager.mailbox import new as new_mailbox
-from VirtualMailManager.pycompat import all, any
+from VirtualMailManager.password import extract_scheme, verify_scheme
 from VirtualMailManager.quotalimit import QuotaLimit
 from VirtualMailManager.relocated import Relocated
 from VirtualMailManager.serviceset import ServiceSet, SERVICES
@@ -45,22 +48,21 @@
 
 
 _ = lambda msg: msg
-_db_mod = None
 
 CFG_FILE = 'vmm.cfg'
 CFG_PATH = '/root:/usr/local/etc:/etc'
 RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
 OTHER_TYPES = {
-    TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
-    TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
-    TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
+    TYPE_ACCOUNT: (_('an account'), ACCOUNT_EXISTS),
+    TYPE_ALIAS: (_('an alias'), ALIAS_EXISTS),
+    TYPE_RELOCATED: (_('a relocated user'), RELOCATED_EXISTS),
 }
 
 
 class Handler(object):
     """Wrapper class to simplify the access on all the stuff from
     VirtualMailManager"""
-    __slots__ = ('_cfg', '_cfg_fname', '_db_connect', '_dbh', '_warnings')
+    __slots__ = ('_cfg', '_cfg_fname', '_dbh', '_warnings')
 
     def __init__(self, skip_some_checks=False):
         """Creates a new Handler instance.
@@ -76,10 +78,9 @@
         self._warnings = []
         self._cfg = None
         self._dbh = None
-        self._db_connect = None
 
         if os.geteuid():
-            raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
+            raise NotRootError(_("You are not root.\n\tGood bye!\n"),
                                CONF_NOPERM)
         if self._check_cfg_file():
             self._cfg = Cfg(self._cfg_fname)
@@ -87,7 +88,6 @@
         if not skip_some_checks:
             self._cfg.check()
             self._chkenv()
-            self._set_db_connect()
 
     def _find_cfg_file(self):
         """Search the CFG_FILE in CFG_PATH.
@@ -99,22 +99,24 @@
                 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,
+            raise VMMError(_("Could not find '%(cfg_file)s' in: "
+                             "'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
                            'cfg_path': CFG_PATH}, CONF_NOFILE)
 
     def _check_cfg_file(self):
         """Checks the configuration file, returns bool"""
+        GRPRW = S_IRGRP | S_IWGRP
+        OTHRW = S_IROTH | S_IWOTH
         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:
+        if (fstat.st_uid == fstat.st_gid and fstat.st_mode & OTHRW) or \
+           (fstat.st_uid != fstat.st_gid and fstat.st_mode & (GRPRW | OTHRW)):
             # TP: Please keep the backticks around the command. `chmod 0600 …`
-            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)
+            raise PermissionError(_("wrong permissions for '%(file)s': "
+                                    "%(perms)s\n`chmod 0600 %(file)s` would "
+                                    "be great.") % {'file': self._cfg_fname,
+                                  'perms': oct(fstat.st_mode)[-4:]},
+                                  CONF_WRONGPERM)
         else:
             return True
 
@@ -125,72 +127,35 @@
         dir_created = False
         basedir = self._cfg.dget('misc.base_directory')
         if not os.path.exists(basedir):
-            old_umask = os.umask(0006)
-            os.makedirs(basedir, 0771)
+            old_umask = os.umask(0o006)
+            os.makedirs(basedir, 0o771)
             os.chown(basedir, 0, 0)
             os.umask(old_umask)
             dir_created = True
         if not dir_created and not lisdir(basedir):
-            raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
-                             u"section 'misc', option 'base_directory')") %
+            raise VMMError(_("'%(path)s' is not a directory.\n(%(cfg_file)s: "
+                             "section 'misc', option 'base_directory')") %
                            {'path': basedir, 'cfg_file': self._cfg_fname},
                            NO_SUCH_DIRECTORY)
         for opt, val in self._cfg.items('bin'):
             try:
                 exec_ok(val)
-            except VMMError, err:
+            except VMMError as err:
                 if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
-                    raise VMMError(err.msg + _(u"\n(%(cfg_file)s: section "
-                                   u"'bin', option '%(option)s')") %
+                    raise VMMError(err.msg + _("\n(%(cfg_file)s: section "
+                                   "'bin', option '%(option)s')") %
                                    {'cfg_file': self._cfg_fname,
                                     'option': opt}, err.code)
                 else:
                     raise
 
-    def _set_db_connect(self):
-        """check which module to use and set self._db_connect"""
-        global _db_mod
-        if self._cfg.dget('database.module').lower() == 'psycopg2':
-            try:
-                _db_mod = __import__('psycopg2')
-            except ImportError:
-                raise VMMError(_(u"Unable to import database module '%s'.") %
-                               'psycopg2', VMM_ERROR)
-            self._db_connect = self._psycopg2_connect
-        else:
-            try:
-                tmp = __import__('pyPgSQL', globals(), locals(), ['PgSQL'])
-            except ImportError:
-                raise VMMError(_(u"Unable to import database module '%s'.") %
-                               'pyPgSQL', VMM_ERROR)
-            _db_mod = tmp.PgSQL
-            self._db_connect = self._pypgsql_connect
-
-    def _pypgsql_connect(self):
-        """Creates a pyPgSQL.PgSQL.connection instance."""
-        if self._dbh is None or (isinstance(self._dbh, _db_mod.Connection) and
-                                  not self._dbh._isOpen):
-            try:
-                self._dbh = _db_mod.connect(
-                        database=self._cfg.dget('database.name'),
-                        user=self._cfg.pget('database.user'),
-                        host=self._cfg.dget('database.host'),
-                        port=self._cfg.dget('database.port'),
-                        password=self._cfg.pget('database.pass'),
-                        client_encoding='utf8', unicode_results=True)
-                dbc = self._dbh.cursor()
-                dbc.execute("SET NAMES 'UTF8'")
-                dbc.close()
-            except _db_mod.libpq.DatabaseError, err:
-                raise VMMError(str(err), DATABASE_ERROR)
-
-    def _psycopg2_connect(self):
+    def _db_connect(self):
         """Return a new psycopg2 connection object."""
         if self._dbh is None or \
-          (isinstance(self._dbh, _db_mod.extensions.connection) and
+          (isinstance(self._dbh, psycopg2.extensions.connection) and
            self._dbh.closed):
             try:
-                self._dbh = _db_mod.connect(
+                self._dbh = psycopg2.connect(
                         host=self._cfg.dget('database.host'),
                         sslmode=self._cfg.dget('database.sslmode'),
                         port=self._cfg.dget('database.port'),
@@ -198,11 +163,10 @@
                         user=self._cfg.pget('database.user'),
                         password=self._cfg.pget('database.pass'))
                 self._dbh.set_client_encoding('utf8')
-                _db_mod.extensions.register_type(_db_mod.extensions.UNICODE)
                 dbc = self._dbh.cursor()
                 dbc.execute("SET NAMES 'UTF8'")
                 dbc.close()
-            except _db_mod.DatabaseError, err:
+            except psycopg2.DatabaseError as err:
                 raise VMMError(str(err), DATABASE_ERROR)
 
     def _chk_other_address_types(self, address, exclude):
@@ -240,7 +204,7 @@
             return False
         # TP: %(a_type)s will be one of: 'an account', 'an alias' or
         # 'a relocated user'
-        msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
+        msg = _("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])
 
@@ -282,7 +246,7 @@
         """
         if lisdir(directory):
             return Popen([self._cfg.dget('bin.du'), "-hs", directory],
-                         stdout=PIPE).communicate()[0].split('\t')[0]
+                         stdout=PIPE).communicate()[0].decode().split('\t')[0]
         else:
             self._warnings.append(_('No such directory: %s') % directory)
             return 0
@@ -293,16 +257,16 @@
         hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
         dir_created = False
         os.chdir(self._cfg.dget('misc.base_directory'))
-        old_umask = os.umask(0022)
+        old_umask = os.umask(0o022)
         if not os.path.exists(hashdir):
-            os.mkdir(hashdir, 0711)
+            os.mkdir(hashdir, 0o711)
             os.chown(hashdir, 0, 0)
             dir_created = True
         if not dir_created and not lisdir(hashdir):
-            raise VMMError(_(u"'%s' is not a directory.") % hashdir,
+            raise VMMError(_("'%s' is not a directory.") % hashdir,
                            NO_SUCH_DIRECTORY)
         if os.path.exists(domain.directory):
-            raise VMMError(_(u"The file/directory '%s' already exists.") %
+            raise VMMError(_("The file/directory '%s' already exists.") %
                            domain.directory, VMM_ERROR)
         os.mkdir(os.path.join(hashdir, domdir),
                  self._cfg.dget('domain.directory_mode'))
@@ -315,7 +279,7 @@
         domdir = account.domain.directory
         if not lisdir(domdir):
             self._make_domain_dir(account.domain)
-        os.umask(0007)
+        os.umask(0o007)
         uid = account.uid
         os.chdir(domdir)
         os.mkdir('%s' % uid, self._cfg.dget('account.directory_mode'))
@@ -332,7 +296,7 @@
             bad = mailbox.add_boxes(folders,
                                     self._cfg.dget('mailbox.subscribe'))
             if bad:
-                self._warnings.append(_(u"Skipped mailbox folders:") +
+                self._warnings.append(_("Skipped mailbox folders:") +
                                       '\n\t- ' + '\n\t- '.join(bad))
         os.chdir(oldpwd)
 
@@ -344,34 +308,34 @@
         `domdir` : basestring
           The directory of the domain the user belongs to
           (commonly AccountObj.domain.directory)
-        `uid` : int/long
+        `uid` : int
           The user's UID (commonly AccountObj.uid)
-        `gid` : int/long
+        `gid` : int
           The user's GID (commonly AccountObj.gid)
         """
-        assert all(isinstance(xid, (long, int)) for xid in (uid, gid)) and \
-                isinstance(domdir, basestring)
+        assert all(isinstance(xid, int) for xid in (uid, gid)) and \
+                isinstance(domdir, str)
         if uid < MIN_UID or gid < MIN_GID:
-            raise VMMError(_(u"UID '%(uid)u' and/or GID '%(gid)u' are less "
-                             u"than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
+            raise VMMError(_("UID '%(uid)u' and/or GID '%(gid)u' are less "
+                             "than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
                            'gid': gid, 'min_gid': MIN_GID, 'min_uid': MIN_UID},
                            MAILDIR_PERM_MISMATCH)
         if domdir.count('..'):
-            raise VMMError(_(u'Found ".." in domain directory path: %s') %
+            raise VMMError(_('Found ".." in domain directory path: %s') %
                            domdir, FOUND_DOTS_IN_PATH)
         if not lisdir(domdir):
-            raise VMMError(_(u"No such directory: %s") % domdir,
+            raise VMMError(_("No such directory: %s") % domdir,
                            NO_SUCH_DIRECTORY)
         os.chdir(domdir)
         userdir = '%s' % uid
         if not lisdir(userdir):
-            self._warnings.append(_(u"No such directory: %s") %
+            self._warnings.append(_("No such directory: %s") %
                                   os.path.join(domdir, userdir))
             return
         mdstat = os.lstat(userdir)
         if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
-            raise VMMError(_(u'Detected owner/group mismatch in home '
-                             u'directory.'), MAILDIR_PERM_MISMATCH)
+            raise VMMError(_('Detected owner/group mismatch in home '
+                             'directory.'), MAILDIR_PERM_MISMATCH)
         rmtree(userdir, ignore_errors=True)
 
     def _delete_domain_dir(self, domdir, gid):
@@ -381,24 +345,24 @@
 
         `domdir` : basestring
           The domain's directory (commonly DomainObj.directory)
-        `gid` : int/long
+        `gid` : int
           The domain's GID (commonly DomainObj.gid)
         """
-        assert isinstance(domdir, basestring) and isinstance(gid, (long, int))
+        assert isinstance(domdir, str) and isinstance(gid, int)
         if gid < MIN_GID:
-            raise VMMError(_(u"GID '%(gid)u' is less than '%(min_gid)u'.") %
+            raise VMMError(_("GID '%(gid)u' is less than '%(min_gid)u'.") %
                            {'gid': gid, 'min_gid': MIN_GID},
                            DOMAINDIR_GROUP_MISMATCH)
         if domdir.count('..'):
-            raise VMMError(_(u'Found ".." in domain directory path: %s') %
+            raise VMMError(_('Found ".." in domain directory path: %s') %
                            domdir, FOUND_DOTS_IN_PATH)
         if not lisdir(domdir):
             self._warnings.append(_('No such directory: %s') % domdir)
             return
         dirst = os.lstat(domdir)
         if dirst.st_gid != gid:
-            raise VMMError(_(u'Detected group mismatch in domain directory: '
-                             u'%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
+            raise VMMError(_('Detected group mismatch in domain directory: '
+                             '%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
         rmtree(domdir, ignore_errors=True)
 
     def has_warnings(self):
@@ -426,11 +390,11 @@
     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
+        import builtins
+        assert 'cfg_dget' not in builtins.__dict__
+        builtins.__dict__['cfg_dget'] = self._cfg.dget
 
-    def domain_add(self, domainname, transport=None):
+    def domain_add(self, domainname, transport=None, note=None):
         """Wrapper around Domain's set_quotalimit, set_transport and save."""
         dom = self._get_domain(domainname)
         if transport is None:
@@ -438,8 +402,10 @@
                               transport=self._cfg.dget('domain.transport')))
         else:
             dom.set_transport(Transport(self._dbh, transport=transport))
+        if note:
+            dom.set_note(note)
         dom.set_quotalimit(QuotaLimit(self._dbh,
-                           bytes=long(self._cfg.dget('domain.quota_bytes')),
+                           bytes=int(self._cfg.dget('domain.quota_bytes')),
                            messages=self._cfg.dget('domain.quota_messages')))
         dom.set_serviceset(ServiceSet(self._dbh,
                                       imap=self._cfg.dget('domain.imap'),
@@ -450,48 +416,36 @@
         dom.save()
         self._make_domain_dir(dom)
 
-    def domain_quotalimit(self, domainname, bytes_, messages=0, force=None):
+    def domain_quotalimit(self, domainname, bytes_, messages=0, force=False):
         """Wrapper around Domain.update_quotalimit()."""
-        if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
+        if not all(isinstance(i, int) for i in (bytes_, messages)):
             raise TypeError("'bytes_' and 'messages' have to be "
                             "integers or longs.")
-        if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
-                              INVALID_ARGUMENT)
+        assert isinstance(force, bool)
         dom = self._get_domain(domainname)
         quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
-        if force is None:
-            dom.update_quotalimit(quotalimit)
-        else:
-            dom.update_quotalimit(quotalimit, force=True)
+        dom.update_quotalimit(quotalimit, force)
 
-    def domain_services(self, domainname, force=None, *services):
+    def domain_services(self, domainname, force=False, *services):
         """Wrapper around Domain.update_serviceset()."""
+        assert isinstance(force, bool)
         kwargs = dict.fromkeys(SERVICES, False)
-        if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
-                              INVALID_ARGUMENT)
         for service in set(services):
             if service not in SERVICES:
-                raise DomainError(_(u"Unknown service: '%s'") % service,
+                raise DomainError(_("Unknown service: '%s'") % service,
                                   UNKNOWN_SERVICE)
             kwargs[service] = True
 
         dom = self._get_domain(domainname)
         serviceset = ServiceSet(self._dbh, **kwargs)
-        dom.update_serviceset(serviceset, (True, False)[not force])
+        dom.update_serviceset(serviceset, force)
 
-    def domain_transport(self, domainname, transport, force=None):
+    def domain_transport(self, domainname, transport, force=False):
         """Wrapper around Domain.update_transport()"""
-        if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
-                              INVALID_ARGUMENT)
+        assert isinstance(force, bool)
         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)
+        dom.update_transport(trsp, force)
 
     def domain_note(self, domainname, note):
         """Wrapper around Domain.update_note()"""
@@ -518,14 +472,14 @@
         Domain.get_relocated."""
         if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
                            'relocated', 'catchall']:
-            raise VMMError(_(u"Invalid argument: '%s'") % details,
+            raise VMMError(_("Invalid argument: '%s'") % details,
                            INVALID_ARGUMENT)
         dom = self._get_domain(domainname)
         dominfo = dom.get_info()
         if dominfo['domain name'].startswith('xn--') or \
            dominfo['domain name'].count('.xn--'):
             dominfo['domain name'] += ' (%s)' % \
-                                      dominfo['domain name'].decode('idna')
+                         dominfo['domain name'].encode('utf-8').decode('idna')
         if details is None:
             return dominfo
         elif details == 'accounts':
@@ -598,8 +552,8 @@
         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)
+                raise VMMError(_("The pattern '%s' contains invalid "
+                                 "characters.") % pattern, DOMAIN_INVALID)
         self._db_connect()
         return search(self._dbh, pattern=pattern, like=like)
 
@@ -617,13 +571,10 @@
                 dpattern = parts[1]
                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
 
-                if llike:
-                    checkp = lpattern.strip('%')
-                else:
-                    checkp = lpattern
+                checkp = lpattern.strip('%') if llike else lpattern
                 if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
-                    raise VMMError(_(u"The pattern '%s' contains invalid "
-                                     u"characters.") % pattern,
+                    raise VMMError(_("The pattern '%s' contains invalid "
+                                     "characters.") % pattern,
                                    LOCALPART_INVALID)
             else:
                 # else just match on domains
@@ -631,27 +582,26 @@
                 dpattern = parts[0]
                 dlike = dpattern.startswith('%') or dpattern.endswith('%')
 
-            if dlike:
-                checkp = dpattern.strip('%')
-            else:
-                checkp = dpattern
+            checkp = dpattern.strip('%') if dlike else dpattern
             if len(checkp) > 0 and not re.match(RE_DOMAIN_SEARCH, checkp):
-                raise VMMError(_(u"The pattern '%s' contains invalid "
-                                 u"characters.") % pattern, DOMAIN_INVALID)
+                raise VMMError(_("The pattern '%s' contains invalid "
+                                 "characters.") % pattern, DOMAIN_INVALID)
         self._db_connect()
         from VirtualMailManager.common import search_addresses
         return search_addresses(self._dbh, typelimit=typelimit,
                                 lpattern=lpattern, llike=llike,
                                 dpattern=dpattern, dlike=dlike)
 
-    def user_add(self, emailaddress, password):
+    def user_add(self, emailaddress, password, note=None):
         """Wrapper around Account.set_password() and Account.save()."""
         acc = self._get_account(emailaddress)
         if acc:
-            raise VMMError(_(u"The account '%s' already exists.") %
+            raise VMMError(_("The account '%s' already exists.") %
                            acc.address, ACCOUNT_EXISTS)
         self._is_other_address(acc.address, TYPE_ACCOUNT)
         acc.set_password(password)
+        if note:
+            acc.set_note(note)
         acc.save()
         self._make_account_dirs(acc)
 
@@ -671,8 +621,8 @@
         for destination in destinations:
             if destination.gid and \
                not self._chk_other_address_types(destination, TYPE_RELOCATED):
-                self._warnings.append(_(u"The destination account/alias '%s' "
-                                        u"does not exist.") % destination)
+                self._warnings.append(_("The destination account/alias '%s' "
+                                        "does not exist.") % destination)
 
     def user_delete(self, emailaddress, force=False):
         """Wrapper around Account.delete(...)"""
@@ -680,7 +630,7 @@
             raise TypeError('force must be a bool')
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
         uid = acc.uid
         gid = acc.gid
@@ -690,10 +640,10 @@
         if self._cfg.dget('account.delete_directory'):
             try:
                 self._delete_home(dom_dir, uid, gid)
-            except VMMError, err:
+            except VMMError as err:
                 if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
                                 NO_SUCH_DIRECTORY):
-                    warning = _(u"""\
+                    warning = _("""\
 The account has been successfully deleted from the database.
     But an error occurred while deleting the following directory:
     '%(directory)s'
@@ -709,7 +659,7 @@
         if alias:
             return alias.get_destinations()
         if not self._is_other_address(alias.address, TYPE_ALIAS):
-            raise VMMError(_(u"The alias '%s' does not exist.") %
+            raise VMMError(_("The alias '%s' does not exist.") %
                            alias.address, NO_SUCH_ALIAS)
 
     def alias_delete(self, aliasaddress, targetaddresses=None):
@@ -726,7 +676,7 @@
             warnings = []
             try:
                 alias.del_destinations(destinations, warnings)
-            except VMMError, err:
+            except VMMError as err:
                 error = err
             if warnings:
                 self._warnings.append(_('Ignored destination addresses:'))
@@ -748,8 +698,8 @@
         for destination in destinations:
             if destination.gid and \
                not self._chk_other_address_types(destination, TYPE_RELOCATED):
-                self._warnings.append(_(u"The destination account/alias '%s' "
-                                        u"does not exist.") % destination)
+                self._warnings.append(_("The destination account/alias '%s' "
+                                        "does not exist.") % destination)
 
     def catchall_info(self, domain):
         """Returns an iterator object for all destinations (`EmailAddress`
@@ -770,7 +720,7 @@
             warnings = []
             try:
                 catchall.del_destinations(destinations, warnings)
-            except VMMError, err:
+            except VMMError as err:
                 error = err
             if warnings:
                 self._warnings.append(_('Ignored destination addresses:'))
@@ -781,12 +731,12 @@
     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,
+            raise VMMError(_("Invalid argument: '%s'") % details,
                            INVALID_ARGUMENT)
         acc = self._get_account(emailaddress)
         if not acc:
             if not self._is_other_address(acc.address, TYPE_ACCOUNT):
-                raise VMMError(_(u"The account '%s' does not exist.") %
+                raise VMMError(_("The account '%s' does not exist.") %
                                acc.address, NO_SUCH_ACCOUNT)
         info = acc.get_info()
         if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
@@ -805,22 +755,36 @@
         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,
+    def user_password(self, emailaddress, password, scheme=None):
+        """Wrapper for Account.update_password(...)."""
+        if not isinstance(password, str) or not password:
+            raise VMMError(_("Could not accept password: '%s'") % password,
                            INVALID_ARGUMENT)
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
-        acc.modify('password', password)
+        acc.update_password(password, scheme)
+
+    def user_pwhash(self, emailaddress, pwhash):
+        """Wrapper for Account.modify('pwhash', ...)"""
+        scheme = extract_scheme(pwhash)
+        if not scheme:
+            raise VMMError(_('Missing {SCHEME} prefix from password hash.'),
+                           INVALID_ARGUMENT)
+        else:
+            scheme, encoding = verify_scheme(scheme)  # or die …
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_("The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('pwhash', pwhash)
 
     def user_name(self, emailaddress, name):
         """Wrapper for Account.modify('name', ...)."""
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
         acc.modify('name', name)
 
@@ -828,7 +792,7 @@
         """Wrapper for Account.modify('note', ...)."""
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
         acc.modify('note', note)
 
@@ -836,12 +800,12 @@
         """Wrapper for Account.update_quotalimit(QuotaLimit)."""
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                         acc.address, NO_SUCH_ACCOUNT)
         if bytes_ == 'domain':
             quotalimit = None
         else:
-            if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
+            if not all(isinstance(i, int) for i in (bytes_, messages)):
                 raise TypeError("'bytes_' and 'messages' have to be "
                                 "integers or longs.")
             quotalimit = QuotaLimit(self._dbh, bytes=bytes_,
@@ -850,24 +814,22 @@
 
     def user_transport(self, emailaddress, transport):
         """Wrapper for Account.update_transport(Transport)."""
-        if not isinstance(transport, basestring) or not transport:
-            raise VMMError(_(u"Could not accept transport: '%s'") % transport,
+        if not isinstance(transport, str) or not transport:
+            raise VMMError(_("Could not accept transport: '%s'") % transport,
                            INVALID_ARGUMENT)
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                            acc.address, NO_SUCH_ACCOUNT)
-        if transport == 'domain':
-            transport = None
-        else:
-            transport = Transport(self._dbh, transport=transport)
+        transport = None if transport == 'domain' \
+                         else Transport(self._dbh, transport=transport)
         acc.update_transport(transport)
 
     def user_services(self, emailaddress, *services):
         """Wrapper around Account.update_serviceset()."""
         acc = self._get_account(emailaddress)
         if not acc:
-            raise VMMError(_(u"The account '%s' does not exist.") %
+            raise VMMError(_("The account '%s' does not exist.") %
                         acc.address, NO_SUCH_ACCOUNT)
         if len(services) == 1 and services[0] == 'domain':
             serviceset = None
@@ -875,7 +837,7 @@
             kwargs = dict.fromkeys(SERVICES, False)
             for service in set(services):
                 if service not in SERVICES:
-                    raise VMMError(_(u"Unknown service: '%s'") % service,
+                    raise VMMError(_("Unknown service: '%s'") % service,
                                 UNKNOWN_SERVICE)
                 kwargs[service] = True
             serviceset = ServiceSet(self._dbh, **kwargs)
@@ -892,8 +854,8 @@
         relocated.set_destination(destination)
         if destination.gid and \
            not self._chk_other_address_types(destination, TYPE_RELOCATED):
-            self._warnings.append(_(u"The destination account/alias '%s' "
-                                    u"does not exist.") % destination)
+            self._warnings.append(_("The destination account/alias '%s' "
+                                    "does not exist.") % destination)
 
     def relocated_info(self, emailaddress):
         """Returns the target address of the relocated user with the given
@@ -902,7 +864,7 @@
         if relocated:
             return relocated.get_info()
         if not self._is_other_address(relocated.address, TYPE_RELOCATED):
-            raise VMMError(_(u"The relocated user '%s' does not exist.") %
+            raise VMMError(_("The relocated user '%s' does not exist.") %
                            relocated.address, NO_SUCH_RELOCATED)
 
     def relocated_delete(self, emailaddress):
--- a/VirtualMailManager/mailbox.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/mailbox.py	Sun Mar 09 18:52:27 2014 +0000
@@ -14,6 +14,7 @@
 from binascii import a2b_base64, b2a_base64
 from subprocess import Popen, PIPE
 
+from VirtualMailManager import ENCODING
 from VirtualMailManager.account import Account
 from VirtualMailManager.common import lisdir
 from VirtualMailManager.errors import VMMError
@@ -29,13 +30,14 @@
 
 def _mbase64_encode(inp, dest):
     if inp:
-        mb64 = b2a_base64(''.join(inp).encode('utf-16be'))
+        mb64 = b2a_base64(''.join(inp).encode('utf-16be')).decode()
         dest.append('&%s-' % mb64.rstrip('\n=').replace('/', ','))
         del inp[:]
 
 
 def _mbase64_to_unicode(mb64):
-    return unicode(a2b_base64(mb64.replace(',', '/') + '==='), 'utf-16be')
+    return str(a2b_base64(mb64.replace(',', '/').encode() + b'==='),
+               'utf-16be')
 
 
 def utf8_to_mutf7(src):
@@ -86,7 +88,7 @@
 class Mailbox(object):
     """Base class of all mailbox classes."""
     __slots__ = ('_boxes', '_root', '_sep', '_user')
-    FILE_MODE = 0600
+    FILE_MODE = 0o600
     _ctrl_chr_re = re.compile('[\x00-\x1F\x7F-\x9F]')
     _box_name_re = re.compile('^[\x20-\x25\x27-\x7E]+$')
 
@@ -206,11 +208,9 @@
         """Writes all created mailboxes to the subscriptions file."""
         if not self._boxes:
             return
-        subscriptions = open('subscriptions', 'w')
-        subscriptions.write('\n'.join(self._boxes))
-        subscriptions.write('\n')
-        subscriptions.flush()
-        subscriptions.close()
+        with open('subscriptions', 'w') as subscriptions:
+            subscriptions.write('\n'.join(self._boxes))
+            subscriptions.write('\n')
         os.chown('subscriptions', self._user.uid, self._user.gid)
         os.chmod('subscriptions', self.__class__.FILE_MODE)
         del self._boxes[:]
@@ -244,12 +244,12 @@
         For additional mailboxes use the add_boxes() method.
         """
         assert cfg_dget('misc.dovecot_version') >= \
-                account.mail_location.dovecot_version
+            account.mail_location.dovecot_version
         super(SingleDbox, self).__init__(account)
 
     def _doveadm_create(self, mailboxes, subscribe):
         """Wrap around Dovecot's doveadm"""
-        cmd_args = [cfg_dget('bin.dovecotpw'), 'mailbox', 'create', '-u',
+        cmd_args = [cfg_dget('bin.doveadm'), 'mailbox', 'create', '-u',
                     str(self._user.address)]
         if subscribe:
             cmd_args.append('-s')
@@ -257,8 +257,8 @@
         process = Popen(cmd_args, stderr=PIPE)
         stderr = process.communicate()[1]
         if process.returncode:
-            e_msg = _(u'Failed to create mailboxes: %r\n') % mailboxes
-            raise VMMError(e_msg + stderr.strip(), VMM_ERROR)
+            e_msg = _('Failed to create mailboxes: %r\n') % mailboxes
+            raise VMMError(e_msg + stderr.strip().decode(ENCODING), VMM_ERROR)
 
     def create(self):
         """Create a dbox INBOX"""
--- a/VirtualMailManager/maillocation.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/maillocation.py	Sun Mar 09 18:52:27 2014 +0000
@@ -12,7 +12,6 @@
 
 from VirtualMailManager.constants import MAILLOCATION_INIT
 from VirtualMailManager.errors import MailLocationError as MLErr
-from VirtualMailManager.pycompat import all
 
 
 __all__ = ('MailLocation', 'known_format')
@@ -38,7 +37,7 @@
 
         Arguments:
 
-        `dbh` : pyPgSQL.PgSQL.Connection
+        `dbh` : psycopg2._psycopg.connection
           A database connection for the database access.
 
         Keyword arguments:
@@ -56,29 +55,29 @@
         self._mbfmt = None
         self._mid = 0
 
-        for key in kwargs.iterkeys():
+        for key in kwargs.keys():
             if key not in self.__class__._kwargs:
                 raise ValueError('unrecognized keyword: %r' % key)
         mid = kwargs.get('mid')
         if mid:
-            assert isinstance(mid, (int, long))
+            assert isinstance(mid, int)
             self._load_by_mid(mid)
         else:
             args = kwargs.get('mbfmt'), kwargs.get('directory')
-            assert all(isinstance(arg, basestring) for arg in args)
+            assert all(isinstance(arg, str) for arg in args)
             if args[0].lower() not in _format_info:
-                raise MLErr(_(u"Unsupported mailbox format: '%s'") % args[0],
+                raise MLErr(_("Unsupported mailbox format: '%s'") % args[0],
                             MAILLOCATION_INIT)
             directory = args[1].strip()
             if not directory:
-                raise MLErr(_(u"Empty directory name"), MAILLOCATION_INIT)
+                raise MLErr(_("Empty directory name"), MAILLOCATION_INIT)
             if len(directory) > 20:
-                raise MLErr(_(u"Directory name is too long: '%s'") % directory,
+                raise MLErr(_("Directory name is too long: '%s'") % directory,
                             MAILLOCATION_INIT)
             self._load_by_names(args[0].lower(), directory)
 
     def __str__(self):
-        return u'%s:~/%s' % (self._mbfmt, self._directory)
+        return '%s:~/%s' % (self._mbfmt, self._directory)
 
     @property
     def directory(self):
--- a/VirtualMailManager/network.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/network.py	Sun Mar 09 18:52:27 2014 +0000
@@ -10,6 +10,8 @@
 
 import socket
 
+from binascii import b2a_hex
+
 
 class NetInfo(object):
     """Simple class for CIDR network addresses an IP addresses."""
@@ -81,7 +83,7 @@
     `(address_family, address_as_long)` will be returned. The
     `address_family`will be either `socket.AF_INET` or `socket.AF_INET6`.
     """
-    if not isinstance(ip_address, basestring) or not ip_address:
+    if not isinstance(ip_address, str) or not ip_address:
         raise TypeError('ip_address must be a non empty string.')
     if not ip_address.count(':'):
         family = socket.AF_INET
@@ -97,4 +99,4 @@
             address = socket.inet_pton(family, ip_address)
         except socket.error:
             raise ValueError('Not a valid IPv6 address: %r' % ip_address)
-    return (family, long(address.encode('hex'), 16))
+    return (family, int(b2a_hex(address), 16))
--- a/VirtualMailManager/password.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/password.py	Sun Mar 09 18:52:27 2014 +0000
@@ -13,24 +13,24 @@
         random_password = randompw()
         scheme, encoding = verify_scheme(scheme)
         schemes, encodings = list_schemes()
+        scheme = extract_scheme(hashed_password)
 """
 
+import hashlib
+import re
+
+from base64 import b64encode
+from binascii import b2a_hex
 from crypt import crypt
 from random import SystemRandom
 from subprocess import Popen, PIPE
 
-try:
-    import hashlib
-except ImportError:
-    from VirtualMailManager.pycompat import hashlib
-
 from VirtualMailManager import ENCODING
 from VirtualMailManager.emailaddress import EmailAddress
 from VirtualMailManager.common import get_unicode, version_str
 from VirtualMailManager.constants import VMM_ERROR
 from VirtualMailManager.errors import VMMError
 
-COMPAT = hasattr(hashlib, 'compat')
 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
 PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
 DEFAULT_B64 = (None, 'B64', 'BASE64')
@@ -55,58 +55,36 @@
 cfg_dget = lambda option: None
 _sys_rand = SystemRandom()
 _choice = _sys_rand.choice
-_get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in xrange(s_len))
+_get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in range(s_len))
 
 
-def _dovecotpw(password, scheme, encoding):
-    """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
+def _doveadmpw(password, scheme, encoding):
+    """Communicates with Dovecot's doveadm and returns
     the hashed password: {scheme[.encoding]}hash
     """
     if encoding:
         scheme = '.'.join((scheme, encoding))
-    cmd_args = [cfg_dget('bin.dovecotpw'), '-s', scheme, '-p',
+    cmd_args = [cfg_dget('bin.doveadm'), 'pw', '-s', scheme, '-p',
                 get_unicode(password)]
-    if cfg_dget('misc.dovecot_version') >= 0x20000a01:
-        cmd_args.insert(1, 'pw')
     process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
     stdout, stderr = process.communicate()
     if process.returncode:
-        raise VMMError(stderr.strip(), VMM_ERROR)
-    hashed = stdout.strip()
+        raise VMMError(stderr.strip().decode(ENCODING), VMM_ERROR)
+    hashed = stdout.strip().decode(ENCODING)
     if not hashed.startswith('{%s}' % scheme):
         raise VMMError('Unexpected result from %s: %s' %
-                       (cfg_dget('bin.dovecotpw'), hashed), VMM_ERROR)
+                       (cfg_dget('bin.doveadm'), hashed), VMM_ERROR)
     return hashed
 
 
 def _md4_new():
-    """Returns an new MD4-hash object if supported by the hashlib or
-    provided by PyCrypto - other `None`.
+    """Returns an new MD4-hash object if supported by the hashlib -
+    otherwise `None`.
     """
     try:
         return hashlib.new('md4')
-    except ValueError, err:
-        if str(err) == 'unsupported hash type':
-            if not COMPAT:
-                try:
-                    from Crypto.Hash import MD4
-                    return MD4.new()
-                except ImportError:
-                    return None
-        else:
-            raise
-
-
-def _sha256_new(data=''):
-    """Returns a new sha256 object from the hashlib.
-
-    Returns `None` if the PyCrypto in pycompat.hashlib is too old."""
-    if not COMPAT:
-        return hashlib.sha256(data)
-    try:
-        return hashlib.new('sha256', data)
-    except ValueError, err:
-        if str(err) == 'unsupported hash type':
+    except ValueError as err:
+        if err.args[0].startswith('unsupported hash type'):
             return None
         else:
             raise
@@ -121,13 +99,14 @@
 
 def _clear_hash(password, scheme, encoding):
     """Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""
+    password = password.decode(ENCODING)
     if encoding:
         if encoding == 'HEX':
-            password = password.encode('hex')
+            password = b2a_hex(password.encode()).decode()
         else:
-            password = password.encode('base64').replace('\n', '')
+            password = b64encode(password.encode()).decode()
         return _format_digest(password, scheme, encoding)
-    return get_unicode('{%s}%s' % (scheme, password))
+    return '{%s}%s' % (scheme, password)
 
 
 def _get_crypt_blowfish_salt():
@@ -174,15 +153,12 @@
         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
     else:
         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
-    encrypted = crypt(password, salt)
+    encrypted = crypt(password.decode(ENCODING), salt)
     if encoding:
         if encoding == 'HEX':
-            encrypted = encrypted.encode('hex')
+            encrypted = b2a_hex(encrypted.encode()).decode()
         else:
-            encrypted = encrypted.encode('base64').replace('\n', '')
-    if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
-       cfg_dget('misc.dovecot_version') < 0x20000b06:
-        scheme = 'CRYPT'
+            encrypted = b64encode(encrypted.encode()).decode()
     return _format_digest(encrypted, scheme, encoding)
 
 
@@ -194,31 +170,23 @@
         if encoding in DEFAULT_HEX:
             digest = md4.hexdigest()
         else:
-            digest = md4.digest().encode('base64').rstrip()
+            digest = b64encode(md4.digest()).decode()
         return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    return _doveadmpw(password, scheme, encoding)
 
 
 def _md5_hash(password, scheme, encoding, user=None):
     """Generates DIGEST-MD5 aka PLAIN-MD5 and LDAP-MD5 hashes."""
     md5 = hashlib.md5()
     if scheme == 'DIGEST-MD5':
-        #  Prior to Dovecot v1.1.12/v1.2.beta2 there was a problem with a
-        #  empty auth_realms setting in dovecot.conf and user@domain.tld
-        #  usernames. So we have to generate different hashes for different
-        #  versions. See also:
-        #       http://dovecot.org/list/dovecot-news/2009-March/000103.html
-        #       http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae
-        if cfg_dget('misc.dovecot_version') >= 0x1010cf00:
-            md5.update('%s:%s:' % (user.localpart, user.domainname))
-        else:
-            md5.update('%s::' % user)
+        md5.update(user.localpart.encode() + b':' +
+                   user.domainname.encode() + b':')
     md5.update(password)
     if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) or \
        (scheme == 'LDAP-MD5' and encoding == 'HEX'):
         digest = md5.hexdigest()
     else:
-        digest = md5.digest().encode('base64').rstrip()
+        digest = b64encode(md5.digest()).decode()
     return _format_digest(digest, scheme, encoding)
 
 
@@ -226,21 +194,22 @@
     """Generates NTLM hashes."""
     md4 = _md4_new()
     if md4:
-        password = ''.join('%s\x00' % c for c in password)
+        password = b''.join(bytes(x)
+                            for x in zip(password, bytes(len(password))))
         md4.update(password)
         if encoding in DEFAULT_HEX:
             digest = md4.hexdigest()
         else:
-            digest = md4.digest().encode('base64').rstrip()
+            digest = b64encode(md4.digest()).decode()
         return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    return _doveadmpw(password, scheme, encoding)
 
 
 def _sha1_hash(password, scheme, encoding):
     """Generates SHA1 aka SHA hashes."""
     sha1 = hashlib.sha1(password)
     if encoding in DEFAULT_B64:
-        digest = sha1.digest().encode('base64').rstrip()
+        digest = b64encode(sha1.digest()).decode()
     else:
         digest = sha1.hexdigest()
     return _format_digest(digest, scheme, encoding)
@@ -248,101 +217,93 @@
 
 def _sha256_hash(password, scheme, encoding):
     """Generates SHA256 hashes."""
-    sha256 = _sha256_new(password)
-    if sha256:
-        if encoding in DEFAULT_B64:
-            digest = sha256.digest().encode('base64').rstrip()
-        else:
-            digest = sha256.hexdigest()
-        return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    sha256 = hashlib.sha256(password)
+    if encoding in DEFAULT_B64:
+        digest = b64encode(sha256.digest()).decode()
+    else:
+        digest = sha256.hexdigest()
+    return _format_digest(digest, scheme, encoding)
 
 
 def _sha512_hash(password, scheme, encoding):
     """Generates SHA512 hashes."""
-    if not COMPAT:
-        sha512 = hashlib.sha512(password)
-        if encoding in DEFAULT_B64:
-            digest = sha512.digest().encode('base64').replace('\n', '')
-        else:
-            digest = sha512.hexdigest()
-        return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    sha512 = hashlib.sha512(password)
+    if encoding in DEFAULT_B64:
+        digest = b64encode(sha512.digest()).decode()
+    else:
+        digest = sha512.hexdigest()
+    return _format_digest(digest, scheme, encoding)
 
 
 def _smd5_hash(password, scheme, encoding):
     """Generates SMD5 (salted PLAIN-MD5) hashes."""
     md5 = hashlib.md5(password)
-    salt = _get_salt(SALTED_ALGO_SALT_LEN)
+    salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
     md5.update(salt)
     if encoding in DEFAULT_B64:
-        digest = (md5.digest() + salt).encode('base64').rstrip()
+        digest = b64encode(md5.digest() + salt).decode()
     else:
-        digest = md5.hexdigest() + salt.encode('hex')
+        digest = md5.hexdigest() + b2a_hex(salt).decode()
     return _format_digest(digest, scheme, encoding)
 
 
 def _ssha1_hash(password, scheme, encoding):
     """Generates SSHA (salted SHA/SHA1) hashes."""
     sha1 = hashlib.sha1(password)
-    salt = _get_salt(SALTED_ALGO_SALT_LEN)
+    salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
     sha1.update(salt)
     if encoding in DEFAULT_B64:
-        digest = (sha1.digest() + salt).encode('base64').rstrip()
+        digest = b64encode(sha1.digest() + salt).decode()
     else:
-        digest = sha1.hexdigest() + salt.encode('hex')
+        digest = sha1.hexdigest() + b2a_hex(salt).decode()
     return _format_digest(digest, scheme, encoding)
 
 
 def _ssha256_hash(password, scheme, encoding):
     """Generates SSHA256 (salted SHA256) hashes."""
-    sha256 = _sha256_new(password)
-    if sha256:
-        salt = _get_salt(SALTED_ALGO_SALT_LEN)
-        sha256.update(salt)
-        if encoding in DEFAULT_B64:
-            digest = (sha256.digest() + salt).encode('base64').rstrip()
-        else:
-            digest = sha256.hexdigest() + salt.encode('hex')
-        return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    sha256 = hashlib.sha256(password)
+    salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
+    sha256.update(salt)
+    if encoding in DEFAULT_B64:
+        digest = b64encode(sha256.digest() + salt).decode()
+    else:
+        digest = sha256.hexdigest() + b2a_hex(salt).decode()
+    return _format_digest(digest, scheme, encoding)
 
 
 def _ssha512_hash(password, scheme, encoding):
     """Generates SSHA512 (salted SHA512) hashes."""
-    if not COMPAT:
-        salt = _get_salt(SALTED_ALGO_SALT_LEN)
-        sha512 = hashlib.sha512(password + salt)
-        if encoding in DEFAULT_B64:
-            digest = (sha512.digest() + salt).encode('base64').replace('\n',
-                                                                       '')
-        else:
-            digest = sha512.hexdigest() + salt.encode('hex')
-        return _format_digest(digest, scheme, encoding)
-    return _dovecotpw(password, scheme, encoding)
+    salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
+    sha512 = hashlib.sha512(password + salt)
+    if encoding in DEFAULT_B64:
+        digest = b64encode(sha512.digest() + salt).decode()
+    else:
+        digest = sha512.hexdigest() + b2a_hex(salt).decode()
+    return _format_digest(digest, scheme, encoding)
 
 _scheme_info = {
+    'CLEAR': (_clear_hash, 0x2010df00),
     'CLEARTEXT': (_clear_hash, 0x10000f00),
-    'CRAM-MD5': (_dovecotpw, 0x10000f00),
+    'CRAM-MD5': (_doveadmpw, 0x10000f00),
     'CRYPT': (_crypt_hash, 0x10000f00),
     'DIGEST-MD5': (_md5_hash, 0x10000f00),
-    'HMAC-MD5': (_dovecotpw, 0x10000f00),
-    'LANMAN': (_dovecotpw, 0x10000f00),
+    'HMAC-MD5': (_doveadmpw, 0x10000f00),
+    'LANMAN': (_doveadmpw, 0x10000f00),
     'LDAP-MD5': (_md5_hash, 0x10000f00),
     'MD5': (_crypt_hash, 0x10000f00),
     'MD5-CRYPT': (_crypt_hash, 0x10000f00),
     'NTLM': (_ntlm_hash, 0x10000f00),
-    'OTP': (_dovecotpw, 0x10100a01),
+    'OTP': (_doveadmpw, 0x10100a01),
     'PLAIN': (_clear_hash, 0x10000f00),
     'PLAIN-MD4': (_md4_hash, 0x10000f00),
     'PLAIN-MD5': (_md5_hash, 0x10000f00),
-    'RPA': (_dovecotpw, 0x10000f00),
-    'SCRAM-SHA-1': (_dovecotpw, 0x20200a01),
+    'RPA': (_doveadmpw, 0x10000f00),
+    'SCRAM-SHA-1': (_doveadmpw, 0x20200a01),
     'SHA': (_sha1_hash, 0x10000f00),
     'SHA1': (_sha1_hash, 0x10000f00),
     'SHA256': (_sha256_hash, 0x10100a01),
     'SHA512': (_sha512_hash, 0x20000b03),
-    'SKEY': (_dovecotpw, 0x10100a01),
+    'SKEY': (_doveadmpw, 0x10100a01),
     'SMD5': (_smd5_hash, 0x10000f00),
     'SSHA': (_ssha1_hash, 0x10000f00),
     'SSHA256': (_ssha256_hash, 0x10200a04),
@@ -350,20 +311,27 @@
 }
 
 
+def extract_scheme(password_hash):
+    """Returns the extracted password scheme from *password_hash*.
+
+    If the scheme couldn't be extracted, **None** will be returned.
+    """
+    scheme = re.match(r'^\{([^\}]{3,37})\}', password_hash)
+    if scheme:
+        return scheme.groups()[0]
+    return scheme
+
+
 def list_schemes():
     """Returns the tuple (schemes, encodings).
 
     `schemes` is an iterator for all supported password schemes (depends on
     the used Dovecot version and features of the libc).
-    `encodings` is a tuple with all usable encoding suffixes. The tuple may
-    be empty.
+    `encodings` is a tuple with all usable encoding suffixes.
     """
     dcv = cfg_dget('misc.dovecot_version')
-    schemes = (k for (k, v) in _scheme_info.iteritems() if v[1] <= dcv)
-    if dcv >= 0x10100a01:
-        encodings = ('.B64', '.BASE64', '.HEX')
-    else:
-        encodings = ()
+    schemes = (k for (k, v) in _scheme_info.items() if v[1] <= dcv)
+    encodings = ('.B64', '.BASE64', '.HEX')
     return schemes, encodings
 
 
@@ -382,23 +350,20 @@
       * depends on a newer Dovecot version
       * has a unknown encoding suffix
     """
-    assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme
+    assert isinstance(scheme, str), 'Not a str: {!r}'.format(scheme)
     scheme_encoding = scheme.upper().split('.')
     scheme = scheme_encoding[0]
     if scheme not in _scheme_info:
-        raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme,
+        raise VMMError(_("Unsupported password scheme: '%s'") % scheme,
                        VMM_ERROR)
     if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]:
-        raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot "
-                         u">= v%(version)s.") % {'scheme': scheme,
+        raise VMMError(_("The password scheme '%(scheme)s' requires Dovecot "
+                         ">= v%(version)s.") % {'scheme': scheme,
                        'version': version_str(_scheme_info[scheme][1])},
                        VMM_ERROR)
     if len(scheme_encoding) > 1:
-        if cfg_dget('misc.dovecot_version') < 0x10100a01:
-            raise VMMError(_(u'Encoding suffixes for password schemes require '
-                             u'Dovecot >= v1.1.alpha1.'), VMM_ERROR)
         if scheme_encoding[1] not in ('B64', 'BASE64', 'HEX'):
-            raise VMMError(_(u"Unsupported password encoding: '%s'") %
+            raise VMMError(_("Unsupported password encoding: '%s'") %
                            scheme_encoding[1], VMM_ERROR)
         encoding = scheme_encoding[1]
     else:
@@ -413,11 +378,9 @@
     be used for the hash generation.  When 'DIGEST-MD5' is used as scheme,
     also an EmailAddress instance must be given as *user* argument.
     """
-    if not isinstance(password, basestring):
+    if not isinstance(password, str):
         raise TypeError('Password is not a string: %r' % password)
-    if isinstance(password, unicode):
-        password = password.encode(ENCODING)
-    password = password.strip()
+    password = password.encode(ENCODING).strip()
     if not password:
         raise ValueError("Could not accept empty password.")
     if scheme is None:
@@ -450,11 +413,11 @@
 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1'
 
     if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish:
-        _scheme_info['BLF-CRYPT'] = (_crypt_hash, 0x10000f00)
+        _scheme_info['BLF-CRYPT'] = (_crypt_hash, 0x20000b06)
     if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256:
-        _scheme_info['SHA256-CRYPT'] = (_crypt_hash, 0x10000f00)
+        _scheme_info['SHA256-CRYPT'] = (_crypt_hash, 0x20000b06)
     if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512:
-        _scheme_info['SHA512-CRYPT'] = (_crypt_hash, 0x10000f00)
+        _scheme_info['SHA512-CRYPT'] = (_crypt_hash, 0x20000b06)
 
 _test_crypt_algorithms()
 del _, cfg_dget, _test_crypt_algorithms
--- a/VirtualMailManager/pycompat/__init__.py	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2014, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.pycompat
-
-    VirtualMailManager's compatibility stuff for Python 2.4
-"""
-
-# http://docs.python.org/library/functions.html#all
-try:
-    all = all
-except NameError:
-    def all(iterable):
-        """Return True if all elements of the *iterable* are true
-        (or if the iterable is empty).
-
-        """
-        for element in iterable:
-            if not element:
-                return False
-        return True
-
-
-# http://docs.python.org/library/functions.html#any
-try:
-    any = any
-except NameError:
-    def any(iterable):
-        """Return True if any element of the *iterable* is true.  If the
-        iterable is empty, return False.
-
-        """
-        for element in iterable:
-            if element:
-                return True
-        return False
--- a/VirtualMailManager/pycompat/hashlib.py	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2014, Pascal Volk
-# See COPYING for distribution information.
-
-"""
-    VirtualMailManager.pycompat.hashlib
-
-    VirtualMailManager's minimal hashlib emulation for Python 2.4
-
-    hashlib.md5(...), hashlib.sha1(...), hashlib.new('md5', ...) and
-    hashlib.new('sha1', ...) will work always.
-
-    When the PyCrypto module <http://www.pycrypto.org/> could be found in
-    sys.path hashlib.new('md4', ...) will also work.
-
-    With PyCrypto >= 2.1.0alpha1 hashlib.new('sha256', ...) and
-    hashlib.sha256(...) becomes functional.
-"""
-
-
-import md5 as _md5
-import sha as _sha1
-
-try:
-    import Crypto
-except ImportError:
-    _md4 = None
-    SHA256 = None
-else:
-    from Crypto.Hash import MD4 as _md4
-    if hasattr(Crypto, 'version_info'):  # <- Available since v2.1.0alpha1
-        from Crypto.Hash import SHA256   # SHA256 works since v2.1.0alpha1
-        sha256 = SHA256.new
-    else:
-        SHA256 = None
-    del Crypto
-
-
-compat = 0x01
-md5 = _md5.new
-sha1 = _sha1.new
-
-
-def new(name, string=''):
-    """Return a new hashing object using the named algorithm, optionally
-    initialized with the provided string.
-    """
-    if name in ('md5', 'MD5'):
-        return _md5.new(string)
-    if name in ('sha1', 'SHA1'):
-        return _sha1.new(string)
-    if not _md4:
-        raise ValueError('unsupported hash type')
-    if name in ('md4', 'MD4'):
-        return _md4.new(string)
-    if name in ('sha256', 'SHA256') and SHA256:
-        return SHA256.new(string)
-    raise ValueError('unsupported hash type')
--- a/VirtualMailManager/quotalimit.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/quotalimit.py	Sun Mar 09 18:52:27 2014 +0000
@@ -9,8 +9,6 @@
     for domains and accounts.
 """
 
-from VirtualMailManager.pycompat import all
-
 _ = lambda msg: msg
 
 
@@ -27,14 +25,14 @@
 
         Arguments:
 
-        `dbh` : pyPgSQL.PgSQL.Connection || psycopg2._psycopg.connection
+        `dbh` : psycopg2._psycopg.connection
           A database connection for the database access.
 
         Keyword arguments:
 
         `qid` : int
           The id of a quota limit
-        `bytes` : long
+        `bytes` : int
           The quota limit in bytes.
         `messages` : int
           The quota limit in number of messages
@@ -44,24 +42,18 @@
         self._bytes = 0
         self._messages = 0
 
-        for key in kwargs.iterkeys():
+        for key in kwargs.keys():
             if key not in self.__class__._kwargs:
                 raise ValueError('unrecognized keyword: %r' % key)
         qid = kwargs.get('qid')
         if qid is not None:
-            assert isinstance(qid, (int, long))
+            assert isinstance(qid, int)
             self._load_by_qid(qid)
         else:
             bytes_, msgs = kwargs.get('bytes'), kwargs.get('messages')
-            assert all(isinstance(i, (int, long)) for i in (bytes_, msgs))
-            if bytes_ < 0:
-                self._bytes = -bytes_
-            else:
-                self._bytes = bytes_
-            if msgs < 0:
-                self._messages = -msgs
-            else:
-                self._messages = msgs
+            assert all(isinstance(i, int) for i in (bytes_, msgs))
+            self._bytes = -bytes_ if bytes_ < 0 else bytes_
+            self._messages = -msgs if msgs < 0 else msgs
             self._load_by_limit()
 
     @property
--- a/VirtualMailManager/relocated.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/relocated.py	Sun Mar 09 18:52:27 2014 +0000
@@ -36,13 +36,13 @@
         self._dbh = dbh
         self._gid = get_gid(self._dbh, self._addr.domainname)
         if not self._gid:
-            raise RErr(_(u"The domain '%s' does not exist.") %
+            raise RErr(_("The domain '%s' does not exist.") %
                        self._addr.domainname, NO_SUCH_DOMAIN)
         self._dest = None
 
         self._load()
 
-    def __nonzero__(self):
+    def __bool__(self):
         """Returns `True` if the Relocated is known, `False` if it's new."""
         return self._dest is not None
 
@@ -59,8 +59,8 @@
         if destination:
             destination = DestinationEmailAddress(destination[0], self._dbh)
             if destination.at_localhost:
-                raise RErr(_(u"The destination address' domain name must not "
-                             u"be localhost."), DOMAIN_INVALID)
+                raise RErr(_("The destination address' domain name must not "
+                             "be localhost."), DOMAIN_INVALID)
             self._dest = destination
 
     @property
@@ -73,14 +73,14 @@
         update = False
         assert isinstance(destination, DestinationEmailAddress)
         if destination.at_localhost:
-            raise RErr(_(u"The destination address' domain name must not be "
-                         u"localhost."), DOMAIN_INVALID)
+            raise RErr(_("The destination address' domain name must not be "
+                         "localhost."), DOMAIN_INVALID)
         if self._addr == destination:
-            raise RErr(_(u'Address and destination are identical.'),
+            raise RErr(_('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.") %
+                raise RErr(_("The relocated user '%s' already exists.") %
                            self._addr, RELOCATED_EXISTS)
             else:
                 self._dest = destination
@@ -103,14 +103,14 @@
     def get_info(self):
         """Returns the address to which mails should be sent."""
         if not self._dest:
-            raise RErr(_(u"The relocated user '%s' does not exist.") %
+            raise RErr(_("The relocated user '%s' does not 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' does not exist.") %
+            raise RErr(_("The relocated user '%s' does not exist.") %
                        self._addr, NO_SUCH_RELOCATED)
         dbc = self._dbh.cursor()
         dbc.execute('DELETE FROM relocated WHERE gid = %s AND address = %s',
--- a/VirtualMailManager/serviceset.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/serviceset.py	Sun Mar 09 18:52:27 2014 +0000
@@ -11,15 +11,13 @@
 
 SERVICES = ('smtp', 'pop3', 'imap', 'sieve')
 
-cfg_dget = lambda option: None
-
 
 class ServiceSet(object):
     """A wrapper class that provides access to the service_set table.
 
     Each ServiceSet object provides following - read only - attributes:
 
-    `ssid` : long
+    `ssid` : int
       The id of the service set
     `smtp` : bool
       Boolean flag for service smtp
@@ -32,7 +30,7 @@
     `services` : dict
       The four services above with boolean values
     """
-    __slots__ = ('_ssid', '_services', '_sieve_col', '_dbh')
+    __slots__ = ('_ssid', '_services', '_dbh')
     _kwargs = (('ssid',) + SERVICES)
 
     def __init__(self, dbh, **kwargs):
@@ -42,7 +40,7 @@
         arguments ('smtp', 'pop3',  'imap', 'sieve') must be provided.
 
         Arguments:
-        `dbh` : pyPgSQL.PgSQL.Connection or psycopg2.extensions.connection
+        `dbh` : psycopg2.extensions.connection
           A database connection for the database access.
 
         Keyword arguments:
@@ -60,17 +58,13 @@
         self._dbh = dbh
         self._ssid = 0
         self._services = dict.fromkeys(SERVICES, True)
-        if cfg_dget('misc.dovecot_version') < 0x10200b02:
-            self._sieve_col = 'managesieve'
-        else:
-            self._sieve_col = 'sieve'
 
-        for key in kwargs.iterkeys():
+        for key in kwargs.keys():
             if key not in self.__class__._kwargs:
                 raise ValueError('unrecognized keyword: %r' % key)
             if key == 'ssid':
                 assert not isinstance(kwargs[key], bool) and \
-                       isinstance(kwargs[key], (int, long)) and kwargs[key] > 0
+                       isinstance(kwargs[key], int) and kwargs[key] > 0
                 self._load_by_ssid(kwargs[key])
                 break
             else:
@@ -101,15 +95,13 @@
 
     def __repr__(self):
         return '%s(%s, %s)' % (self.__class__.__name__, self._dbh,
-                  ', '.join('%s=%r' % s for s in self._services.iteritems()))
+                  ', '.join('%s=%r' % s for s in self._services.items()))
 
     def _load_by_services(self):
         """Try to load the service_set by it's service combination."""
         sql = ('SELECT ssid FROM service_set WHERE %s' %
                ' AND '.join('%s = %s' %
-               (k, str(v).upper()) for k, v in self._services.iteritems()))
-        if self._sieve_col == 'managesieve':
-            sql = sql.replace('sieve', self._sieve_col)
+               (k, str(v).upper()) for k, v in self._services.items()))
         dbc = self._dbh.cursor()
         dbc.execute(sql)
         result = dbc.fetchone()
@@ -122,24 +114,18 @@
     def _load_by_ssid(self, ssid):
         """Try to load the service_set by it's primary key."""
         dbc = self._dbh.cursor()
-        dbc.execute('SELECT ssid, smtp, pop3, imap, %s' % (self._sieve_col,) +
-                    ' FROM service_set WHERE ssid = %s', (ssid,))
+        dbc.execute('SELECT ssid, smtp, pop3, imap, sieve '
+                    'FROM service_set WHERE ssid = %s', (ssid,))
         result = dbc.fetchone()
         dbc.close()
         if not result:
             raise ValueError('Unknown service_set id specified: %r' % ssid)
         self._ssid = result[0]
-        #self._services.update(zip(SERVICES, result[1:]))
-        for key, value in zip(SERVICES, result[1:]):  # pyPgSQL compatible
-            if value:
-                self._services[key] = True
-            else:
-                self._services[key] = False
+        self._services.update(list(zip(SERVICES, result[1:])))
 
     def _save(self):
         """Store a new service_set in the database."""
-        sql = ('INSERT INTO service_set (ssid, smtp, pop3, imap, %s) ' %
-               (self._sieve_col,) +
+        sql = ('INSERT INTO service_set (ssid, smtp, pop3, imap, sieve) '
                'VALUES (%(ssid)s, %(smtp)s, %(pop3)s, %(imap)s, %(sieve)s)')
         self._set_ssid()
         values = {'ssid': self._ssid}
@@ -162,5 +148,3 @@
         """A dictionary: Keys: `smtp`, `pop3`, `imap` and `sieve` with
         boolean values."""
         return self._services.copy()
-
-del cfg_dget
--- a/VirtualMailManager/transport.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/VirtualMailManager/transport.py	Sun Mar 09 18:52:27 2014 +0000
@@ -9,8 +9,6 @@
     domains and accounts.
 """
 
-from VirtualMailManager.pycompat import any
-
 _ = lambda msg: msg
 
 
@@ -25,8 +23,8 @@
         are given, tid will be used.
 
         Keyword arguments:
-        dbh -- a pyPgSQL.PgSQL.connection
-        tid -- the id of a transport (int/long)
+        dbh -- a psycopg2._psycopg.connection
+        tid -- the id of a transport (int)
         transport -- the value of the transport (str)
 
         """
@@ -34,10 +32,10 @@
         self._tid = 0
         assert any((tid, transport))
         if tid:
-            assert not isinstance(tid, bool) and isinstance(tid, (int, long))
+            assert not isinstance(tid, bool) and isinstance(tid, int)
             self._load_by_id(tid)
         else:
-            assert isinstance(transport, basestring)
+            assert isinstance(transport, str)
             self._transport = transport
             self._load_by_name()
 
--- a/doc/web/source/_static/vmm.cfg	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/_static/vmm.cfg	Sun Mar 09 18:52:27 2014 +0000
@@ -1,162 +1,1 @@
-# This is the vmm (a virtual mail manager) configuration file.
-# default location: /usr/local/etc/vmm.cfg
-#
-
-#
-# Database settings
-#
-[database]
-; The Python PostgreSQL database adapter module to be used (String)
-; Supported modules are:
-;    * psycopg2
-;    * pyPgSQL
-module = psycopg2
-; Hostname or IP address of the database server (String)
-host = localhost
-; The TCP port, on which the database server is listening for connections (Int)
-port = 5432
-; SSL mode for the database connection (String)
-; Possible values are:
-;    * disabled
-;    * allow
-;    * prefer (default)
-;    * require
-;    * verify-ca (PostgreSQL >= 8.4)
-;    * verify-full (PostgreSQL >= 8.4)
-sslmode = prefer
-; Database user name (String)
-user = vmm
-; Database password (String)
-pass = dbpassword
-; Database name (String)
-name = mailsys
-
-#
-# mailbox settings
-#
-[mailbox]
-; The mailbox format to be used for user's mailboxes. (String)
-; Depending on the used Dovecot version there are up to 3 supported formats:
-;    * maildir - since Dovecot v1.0.0
-;    * mdbox   - since Dovecot v2.0.beta5
-;    * sdbox   - since Dovecot v2.0.rc3
-format = maildir
-; A colon separated list of mailbox names, that should be created (String)
-; e.g.: folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
-folders = Drafts:Sent:Templates:Trash
-; Name of the mailbox root directory in a user's home. (String)
-; Usually used names (format: name):
-;    * maildir: Maildir
-;    * mdbox:   mdbox
-;    * sdbox:   sdbox
-root = Maildir
-; Set to true if the mailboxes from the folders option should be listed in
-; the subscriptions file. (Boolean)
-subscribe = true
-
-#
-# Domain settings
-#
-[domain]
-; Should vmm create the postmaster account when a new domain is created?
-; (Boolean)
-auto_postmaster = true
-; Delete domain directory recursive when deleting a domain? (Boolean)
-delete_directory = false
-; Permissions for domain directories (Int)
-; octal 0770 -> decimal 504
-directory_mode = 504
-; Force deletion of accounts and aliases when deleting a domain (Boolean)
-force_deletion = false
-;
-; The service settings will be evaluated and applied when a domain is
-; created. The service settings of the domain will be applied when you
-; create a new account.
-; Use the subcommand domainservices to modify a domain's service settings.
-; Or userservices in order to update the service setting of an account.
-; Allow smtp by default? (Boolean)
-smtp = true
-; Allow pop3 by default? (Boolean)
-pop3 = true
-; Allow imap by default? (Boolean)
-imap = true
-; Allow managesieve by default? (Boolean)
-sieve = true
-;
-; The quota_* settings will be evaluated and applied when a domain is
-; created. The domain's quota_* settings will be applied when an account
-; is added to a domain.
-; Use the subcommand domainquota to modify a domain's quota limits.
-; Or userquota in order to update an account's quota limits.
-; Quota limit in bytes. 0 means unlimited (String)
-; The value can have one of the suffixes:
-;    * b: bytes
-;    * k: kilobytes
-;    * M: megabytes
-;    * G: gigabytes
-; 1024 is the same as 1024b or 1k
-quota_bytes = 0
-; Quota limit in number of messages. 0 means unlimited (Int)
-quota_messages = 0
-;
-; The transport setting will be evaluated and applied when a domain is
-; created. The domain's transport setting will be applied when an account
-; is added to a domain.
-; Use the subcommand domaintransport to modify the transport of a domain.
-; Or usertransport in order to update an account's transport setting.
-;
-; With Dovecot >= v2.0.0 it's strongly recommended that you use Dovecot's
-; lmtp instead of the dovecot-lda.
-;transport = lmtp:unix:private/dovecot-lmtp
-; default transport for domains and accounts (String)
-transport = dovecot:
-
-#
-# Account settings
-#
-[account]
-; Delete the user's home directory recursive when deleting an account? (Boolean)
-delete_directory = false
-; Permissions for the user's home directory and mail directories (Int)
-; octal 0700 -> decimal 448
-directory_mode = 448
-; Display disk usage in account info by default? (Boolean)
-disk_usage = false
-; Should vmm generate a random password when no password was given for the
-; useradd subcommand? (Boolean)
-random_password = false
-; How many characters to include in the generated passwords? (Int)
-password_length = 8
-
-#
-# external binaries
-#
-[bin]
-; location of dovecotpw (Dovecot v1) or doveadm (Dovecot v2) (String)
-dovecotpw = /usr/sbin/dovecotpw
-; location of disk usage (String)
-du = /usr/bin/du
-; location of postconf (String)
-postconf = /usr/sbin/postconf
-
-#
-# misc settings
-#
-[misc]
-; The base directory for all domains/accounts (String)
-base_directory = /srv/mail
-; Number of encryption rounds for the password_scheme BLF-CRYPT (Int)
-crypt_blowfish_rounds = 5
-; Number of encryption rounds for the password_scheme SHA256-CRYPT (Int)
-crypt_sha256_rounds = 5000
-; Number of encryption rounds for the password_scheme SHA512-CRYPT (Int)
-crypt_sha512_rounds = 5000
-; the version number from `dovecot --version` (String)
-; e.g. 1.2.17, 2.0.21, 2.1.9 or 2.2.beta1
-dovecot_version = 2.1.9
-; NOTE: When using other password schemes than PLAIN or CRAM-MD5, you
-;	have to remove `cram-md5' from the auth_mechanisms setting in
-;	dovecot/conf.d/10-auth.conf.
-; Password scheme to use (see also: ´vmm listpwschemes`) (String)
-password_scheme = CRAM-MD5
-
+../../../../vmm.cfg
\ No newline at end of file
--- a/doc/web/source/conf.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/conf.py	Sun Mar 09 18:52:27 2014 +0000
@@ -48,9 +48,9 @@
 # built documents.
 #
 # The short X.Y version.
-version = '0.6'
+version = '0.7'
 # The full version, including alpha/beta/rc tags.
-release = '0.6.2'
+release = '0.7.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
--- a/doc/web/source/ext_references.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/ext_references.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -8,6 +8,8 @@
 .. _GID: http://en.wikipedia.org/wiki/Group_identifier_%28Unix%29
 .. _GPG: http://en.wikipedia.org/wiki/GNU_Privacy_Guard
 .. _IDN: http://en.wikipedia.org/wiki/Internationalized_domain_name
+.. _IDN ccTLDs: \
+ http://en.wikipedia.org/wiki/Internationalized_country_code_top-level_domain
 .. _LDA: http://wiki.dovecot.org/LDA
 .. _LMTP: http://master.wiki2.dovecot.org/LMTP
 .. _Maildir: http://wiki2.dovecot.org/MailboxFormat/Maildir
@@ -17,7 +19,6 @@
 .. _Postfix: http://www.postfix.org/
 .. _PostgreSQL: http://www.postgresql.org/
 .. _Psycopg: http://initd.org/psycopg/
-.. _PyCrypto: http://www.pycrypto.org/
 .. _Python: http://www.python.org/
 .. _SourceForge: http://sourceforge.net/
 .. _UID: http://en.wikipedia.org/wiki/User_identifier_%28Unix%29
@@ -27,15 +28,12 @@
 .. _freenode: http://freenode.net/
 .. _integrity: http://en.wikipedia.org/wiki/Data_integrity
 .. _issue tracker: https://bitbucket.org/pvo/vmm/issues
-.. _mxDateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/
-.. _mxTools: http://www.egenix.com/products/python/mxBase/mxTools/
 .. _passdb: http://wiki.dovecot.org/PasswordDatabase
 .. _pg_dump: http://www.postgresql.org/docs/current/static/app-pgdump.html
 .. _pg_hba.conf: \
  http://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html
 .. _pgsql_table(5): http://www.postfix.org/pgsql_table.5.html
 .. _pipe(8): http://www.postfix.org/pipe.8.html
-.. _pyPgSQL: http://pypgsql.sourceforge.net/
 .. _relocated: http://www.postfix.org/relocated.5.html
 .. _setuid: http://en.wikipedia.org/wiki/Setuid
 .. _transport: http://www.postfix.org/transport.5.html
--- a/doc/web/source/features.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/features.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -5,7 +5,7 @@
 General features
 ----------------
  ‣ Unicode/UTF-8 capable (input/storage/output)
- ‣ supports IDN_ (also ccTLDs/ccIDNs and 'new' gTLDs)
+ ‣ supports IDN_ (also `IDN ccTLDs`_/ccIDNs and 'new' gTLDs)
  ‣ supports the mailbox format Maildir_ and Dovecot's own high-performance
    mailbox formats single- and multi-\ dbox_
  ‣ configurable basic mailbox structure, including sub-mailboxes
--- a/doc/web/source/howto.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/howto.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -6,7 +6,9 @@
 It allows you to simply and quickly administer your mail server.
 The general command syntax looks like::
 
- vmm subcommand [argument …]
+ vmm -h|-v|--help|--version
+ vmm subcommand -h|--help
+ vmm subcommand arguments [options]
 
 Each subcommand has both a long and a short form.
 Both forms are case sensitive.
@@ -26,43 +28,58 @@
 
 Most of the *subcommand*\ s take one or more *argument*\ s.
 
+Options
+-------
+The following options are recognized by :program:`vmm`.
+
+.. program:: vmm
+
+.. option:: -h, --help
+
+ show a list of available subcommands and exit.
+
+.. option:: -v, --version
+
+ show :command:`vmm`'s version and copyright information and exit.
+
+
 Arguments
 ---------
-address
+*address*
  The complete e-mail address (*local-part*\ @\ *fqdn*) of an user account,
  alias address or relocated user.
 
-destination
+*destination*
  Is either an e-mail address when used with
  :doc:`Alias subcommands <howto/manage_alias_addresses>`.
- Or a *fqnd* when used with
+ Or a *fqdn* when used with
  :doc:`Alias domain subcommands <howto/manage_alias_domains>`.
 
-fqdn
+*fqdn*
  The fully qualified domain name – without the trailing dot – of a domain
  or alias domain.
 
-messages
+*messages*
  An integer value which specifies a quota limit in number of messages.
  **0** (zero) means unlimited - no quota limit for the number of messages.
 
-option
+*option*
  Is the name of a configuration option, prefixed with the section name and
  a dot.
  For example: *misc*\ **.**\ *transport*
  All configuration options are described in :manpage:`vmm.cfg(5)`.
 
-service
+*service*
  The name of a service, commonly used with Dovecot.
  Supported services are: **imap**, **pop3**, **sieve** and **smtp**.
 
-storage
+*storage*
  Specifies a quota limit in bytes.
  One of the following prefixes can be appended to the integer value:
  **b** (bytes), **k** (kilobytes), **M** (megabytes) or **G** (gigabytes).
  **0** (zero) means unlimited - no quota limit in bytes.
 
-transport
+*transport*
  A transport for Postfix, written as: *transport*\ **:** or
  *transport*\ **:**\ *nexthop*.
  See :manpage:`transport(5)` for more details.
--- a/doc/web/source/howto/general_subcommands.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/howto/general_subcommands.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -48,8 +48,8 @@
 configure
 ---------
 Syntax:
- | **vmm configure** [*section*]
- | **vmm cf** [*section*]
+ | **vmm configure** [**-s** *section*]
+ | **vmm cf** [**-s** *section*]
 
 Starts the interactive configuration for all configuration sections.
 
@@ -84,7 +84,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm configure mailbox
+ root@host:~# vmm configure -s mailbox
  Using configuration file: /usr/local/etc/vmm.cfg
 
  * Configuration section: `mailbox'
@@ -113,21 +113,11 @@
          GID............: 70704
          Address........: a.user@example.com
 
-help
-----
-Syntax:
- | **vmm help** [*subcommand*]
- | **vmm h** [*subcommand*]
-
-Prints a list of available subcommands with a short description to stdout.
-When a *subcommand* was given, help for that *subcommand* will be displayed.
-After this :command:`vmm` exits.
-
 listaddresses
 -------------
 Syntax:
- | **vmm listaddresses** [*pattern*]
- | **vmm ll** [*pattern*]
+ | **vmm listaddresses** [**-p** *pattern*]
+ | **vmm ll** [**-p** *pattern*]
 
 This command lists all defined addresses. Addresses belonging to
 alias-domains are prefixed with a '-', addresses of regular domains with
@@ -143,7 +133,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm ll example.com
+ root@host:~# vmm ll -p example.com
  Matching addresses
  ------------------
          [u+] a.user@example.com
@@ -158,8 +148,8 @@
 listaliases
 -----------
 Syntax:
- | **vmm listaliases** [*pattern*]
- | **vmm la** [*pattern*]
+ | **vmm listaliases** [**-p** *pattern*]
+ | **vmm la** [**-p** *pattern*]
 
 This command lists all defined aliases. Aliases belonging to alias-domains
 are prefixed with a '-', addresses of regular domains with a '+'.
@@ -172,7 +162,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm listaliases example.com
+ root@host:~# vmm listaliases -p example.com
  Matching aliases
  ----------------
          [+] support@example.com
@@ -182,8 +172,8 @@
 listdomains
 -----------
 Syntax:
- | **vmm listdomains** [*pattern*]
- | **vmm ld** [*pattern*]
+ | **vmm listdomains** [**-p** *pattern*]
+ | **vmm ld** [**-p** *pattern*]
 
 This subcommand lists all available domains.
 All domain names will be prefixed either with '[+]', if the domain is
@@ -197,7 +187,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm listdomains %example%
+ root@host:~# vmm listdomains -p %example%
  Matching domains
  ----------------
          [+] example.com
@@ -217,8 +207,7 @@
 The output varies, depending on the used Dovecot version and the system's
 libc.
 
-When your Dovecot installation isn't too old, you will see additionally
-a few usable encoding suffixes.
+Additionally a few usable encoding suffixes will be displayed.
 One of them can be appended to the password scheme.
 
 Example:
@@ -228,10 +217,9 @@
  root@host:~# vmm listpwschemes
  Usable password schemes
  -----------------------
-         CRYPT SHA512-CRYPT LDAP-MD5 DIGEST-MD5 SHA256 SHA512 SSHA512
-         SKEY SSHA NTLM RPA MD5-CRYPT HMAC-MD5 SHA1 PLAIN SHA CRAM-MD5
-         SSHA256 MD5 LANMAN CLEARTEXT PLAIN-MD5 PLAIN-MD4 OTP SMD5
-         SHA256-CRYPT
+         CLEARTEXT CRAM-MD5 CRYPT DIGEST-MD5 HMAC-MD5 LANMAN LDAP-MD5 MD5
+         MD5-CRYPT NTLM OTP PLAIN PLAIN-MD4 PLAIN-MD5 RPA SHA SHA1 SHA256
+         SHA256-CRYPT SHA512 SHA512-CRYPT SKEY SMD5 SSHA SSHA256 SSHA512
 
  Usable encoding suffixes
  ------------------------
@@ -242,8 +230,8 @@
 listrelocated
 -------------
 Syntax:
- | **vmm listrelocated** [*pattern*]
- | **vmm lr** [*pattern*]
+ | **vmm listrelocated** [**-p** *pattern*]
+ | **vmm lr** [**-p** *pattern*]
 
 This command lists all defined relocated addresses.
 Relocated entries belonging to alias-domains are prefixed with a '-',
@@ -257,7 +245,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm listrelocated example.com
+ root@host:~# vmm listrelocated -p example.com
  Matching relocated users
  ------------------------
          [+] b.user@example.com
@@ -267,8 +255,8 @@
 listusers
 ---------
 Syntax:
- | **vmm listusers** [*pattern*]
- | **vmm lu** [*pattern*]
+ | **vmm listusers** [**-p** *pattern*]
+ | **vmm lu** [**-p** *pattern*]
 
 This command lists all user accounts.
 User accounts belonging to alias-domains are prefixed with a '-', addresses
@@ -282,7 +270,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm listusers example.com
+ root@host:~# vmm listusers -p example.com
  Matching user accounts
  ----------------------
          [+] a.user@example.com
@@ -291,12 +279,3 @@
          [+] postmaster@example.com
 
 .. versionadded:: 0.6.0
-
-version
--------
-Syntax:
- | **vmm version**
- | **vmm v**
-
-Prints :command:`vmm`'s version and copyright information to stdout.
-After this :command:`vmm` exits.
--- a/doc/web/source/howto/manage_accounts.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/howto/manage_accounts.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -3,9 +3,19 @@
 =================
 useradd
 -------
+.. program:: vmm useradd
+
 Syntax:
- | **vmm useradd** *address* [*password*]
- | **vmm ua** *address* [*password*]
+ | **vmm useradd** *address* [**-n** *note*] [**-p** *password*]
+ | **vmm ua** *address* [**-n** *note*] [**-p** *password*]
+
+.. option:: -n note
+
+ the note that should be set
+
+.. option:: -p password
+
+ the new user's password
 
 Use this subcommand to create a new e-mail account for the given *address*.
 
@@ -19,7 +29,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm ua d.user@example.com "A 5ecR3t P4s5\/\/0rd"
+ root@host:~# vmm ua d.user@example.com -p "A 5ecR3t P4s5\/\/0rd"
  root@host:~# vmm useradd e.user@example.com
  Enter new password:
  Retype new password:
@@ -27,20 +37,20 @@
 userdelete
 ----------
 Syntax:
- | **vmm userdelete** *address* [*force*]
- | **vmm ud** *address* [*force*]
+ | **vmm userdelete** *address* [**‒‒force**]
+ | **vmm ud** *address* [**‒‒force**]
 
 Use this subcommand to delete the account with the given *address*.
 
 If there are one or more aliases with an identical destination address,
 :command:`vmm` will abort the requested operation and show an error message.
-To prevent this, specify the optional keyword **force**.
+To prevent this, give the optional argument **‒‒force**.
 
 userinfo
 --------
 Syntax:
- | **vmm userinfo** *address* [*details*]
- | **vmm ui** *address* [*details*]
+ | **vmm userinfo** *address* [**-d** *details*]
+ | **vmm ui** *address* [**-d** *details*]
 
 This subcommand displays some information about the account specified by
 *address*.
@@ -83,67 +93,114 @@
 
 username
 --------
+.. program:: vmm username
+
 Syntax:
- | **vmm username** *address* [*name*]
- | **vmm un** *address* [*name*]
+ | **vmm username** *address* **-d** | **-n** *name*
+ | **vmm un** *address* **-d** | **-n** *name*
+
+.. option:: -d
+
+ delete the user's name
+
+.. option:: -n name
+
+ a user's real name
 
 The user's real *name* can be set/updated with this subcommand.
 
-If no *name* is given, the value stored for the account is erased.
+In order to delete the value stored for the account, pass the :option:`-d`
+option.
+
+Example:
+
+.. code-block:: console
+
+ root@host:~# vmm un d.user@example.com -n "John Doe"
+
+usernote
+--------
+.. program:: vmm usernote
+
+Syntax:
+ | **vmm usernote** *address* **-d** | **-n** *note*
+ | **vmm uo** *address* **-d** | **-n** *note*
+
+.. option:: -d
+
+ delete the user's note
+
+.. option:: -n note
+
+ the note that should be set
+
+With this subcommand, it is possible to attach a note to the specified
+account.
+In order to delete an existing note, pass the :option:`-d` option.
 
 Example:
 
 .. code-block:: console
 
- root@host:~# vmm un d.user@example.com "John Doe"
+ root@host:~# vmm uo d.user@example.com -n 'Only needed until end of May 2013'
+
+.. versionadded:: 0.6.0
 
-usernote
---------
+userpassword
+------------
+.. program:: vmm userpassword
+
 Syntax:
- | **vmm usernote** *address* [*note*]
- | **vmm uo** *address* [*note*]
+ | **vmm userpassword** *address* ([**-p** *password*] [**-s** *scheme*] | \
+  [**‒‒hash** *pwhash*])
+ | **vmm up** *address* ([**-p** *password*] [**-s** *scheme*] | \
+  [**‒‒hash** *pwhash*])
+
+.. option:: -p password
+
+ The user's new password.
+
+.. option:: -s scheme
+
+ When a *scheme* was specified, it overrides the *misc.password_scheme*
+ setting, configured in the :file:`vmm.cfg` file.
 
-With this subcommand, it is possible to attach a note to the specified
-account.
-Without an argument, an existing note is removed.
+.. option:: --hash pwhash
+
+ A hashed password, prefixed with **{**\ *SCHEME*\ **}**; as generated by
+ :command:`doveadm pw`.
+ You should enclose the hashed password in single quotes, if it contains
+ one ore more dollar signs (**$**).
+
+The password of an account can be updated with this subcommand.
+
+If no *password* or *pwhash* was provided, :command:`vmm` will prompt for a
+password interactively.
+
+.. note::
+  When passing a hashed password, :command:`vmm` checks only if the included
+  *SCHEME* is supported by your Dovecot installation.  No further checks are
+  done.
 
 Example:
 
 .. code-block:: console
 
- root@host:~# vmm uo d.user@example.com Only needed until end of May 2012
-
-.. versionadded:: 0.6.0
-
-userpassword
-------------
-Syntax:
- | **vmm userpassword** *address* [*password*]
- | **vmm up** *address* [*password*]
-
-The password of an account can be updated with this subcommand.
-
-If no *password* was provided, :command:`vmm` will prompt for it interactively.
-
-Example:
-
-.. code-block:: console
-
- root@host:~# vmm up d.user@example.com "A |\/|0r3 5ecur3 P4s5\/\/0rd?"
+ root@host:~# vmm up d.user@example.com -p "A |\/|0r3 5ecur3 P4s5\/\/0rd?"
 
 userquota
 ---------
 Syntax:
- | **vmm userquota** *address storage* [*messages*]
- | **vmm uq** *address storage* [*messages*]
+ | **vmm userquota** *address storage* [**-m** *messages*]
+ | **vmm uq** *address storage* [**-m** *messages*]
 
 This subcommand is used to set a new quota limit for the given account.
 
 When the argument *messages* was omitted the default number of messages
 **0** (zero) will be applied.
 
-Instead of *storage* pass **domain** to remove the account-specific
-override, causing the domain's value to be in effect.
+Instead of a *storage* limit pass the keyword **domain** to remove the
+account-specific override, causing the domain's value to be in effect.
 
 Example:
 
@@ -156,22 +213,22 @@
 userservices
 ------------
 Syntax:
- | **vmm userservices** *address* [*service ...*]
- | **vmm us** *address* [*service ...*]
+ | **vmm userservices** *address* [**-s** *service ...*]
+ | **vmm us** *address* [**-s** *service ...*]
 
 To grant a user access to the specified services, use this command.
 
 All omitted services will be deactivated/unusable for the user with the
 given *address*.
 
-Instead of *service* pass **domain** to remove the account-specific override,
-causing the domain's value to be in effect.
+Instead of any *service* pass the keyword **domain** to remove the
+account-specific override, causing the domain's value to be in effect.
 
 Example:
 
 .. code-block:: console
 
- root@host:~# userservices d.user@example.com SMTP IMAP
+ root@host:~# userservices d.user@example.com -s smtp imap
 
 .. _usertransport:
 
--- a/doc/web/source/howto/manage_catch-all_addresses.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/howto/manage_catch-all_addresses.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -61,7 +61,7 @@
  root@host:~# vmm catchallinfo example.com
  Catch-all information
  ---------------------
-   Mail to unknown localparts in domain example.com will be sent to:
+   Mail to unknown local-parts in domain example.com will be sent to:
           * user@example.org
 
 .. versionadded:: 0.6.0
--- a/doc/web/source/howto/manage_domains.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/howto/manage_domains.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -5,9 +5,19 @@
 
 domainadd
 ---------
+.. program:: vmm domainadd
+
 Syntax:
- | **vmm domainadd** *fqdn* [*transport*]
- | **vmm da** *fqdn* [*transport*]
+ | **vmm domainadd** *fqdn* [**-n** *note*] [**-t** *transport*]
+ | **vmm da** *fqdn* [**-n** *note*] [**-t** *transport*]
+
+.. option:: -n note
+
+ the note that should be set
+
+.. option:: -t transport
+
+ a Postfix transport (transport: or transport:nexthop)
  
 Adds the new domain into the database and creates the domain directory.
 
@@ -32,7 +42,7 @@
 
 .. code-block:: console
 
- root@host:~# vmm domainadd support.example.com smtp:[mx1.example.com]:2025
+ root@host:~# vmm domainadd support.example.com -t smtp:[mx1.example.com]:2025
  Creating account for postmaster@support.example.com
  Enter new password: 
  Retype new password: 
@@ -44,16 +54,16 @@
 domaindelete
 ------------
 Syntax:
- | **vmm domaindelete** *fqdn* [**force**]
- | **vmm dd** *fqdn* [**force**]
+ | **vmm domaindelete** *fqdn* [**‒‒force**]
+ | **vmm dd** *fqdn* [**‒‒force**]
 
 This subcommand deletes the domain specified by *fqdn*.
 
 If there are accounts, aliases and/or relocated users assigned to the given
 domain, :command:`vmm` will abort the requested operation and show an error
 message.
-If you know, what you are doing, you can specify the optional keyword
-**force**.
+If you know, what you are doing, you can specify the optional argument
+**‒‒force**.
 
 If you really always know what you are doing, edit your :file:`vmm.cfg` and
 set the option *domain.force_deletion* to **true**.
@@ -61,8 +71,8 @@
 domaininfo
 ----------
 Syntax:
- | **vmm domaininfo** *fqdn* [*details*]
- | **vmm di** *fqdn* [*details*]
+ | **vmm domaininfo** *fqdn* [**-d** *details*]
+ | **vmm di** *fqdn* [**-d** *details*]
 
 This subcommand shows some information about the given domain.
 
@@ -102,27 +112,37 @@
 
 domainnote
 ----------
+.. program:: vmm domainnote
+
 Syntax:
- | **vmm domainnote** *fqdn* [*note*]
- | **vmm do** *fqdn* [*note*]
+ | **vmm domainnote** *fqdn* **-d** | **-n** *note*
+ | **vmm do** *fqdn* **-d** | **-n** *note*
+
+.. option:: -d
+
+ delete the domain's note
+
+.. option:: -n note
+
+ the note that should be set
 
 With this subcommand, it is possible to attach a note to the specified
 domain.
-Without an argument, an existing note is removed.
+In order to delete an existing note, pass the :option:`-d` option.
 
 Example:
 
 .. code-block:: console
 
- root@host:~# vmm do example.com Belongs to Robert
+ root@host:~# vmm do example.com -n 'Belongs to Robert'
 
 .. versionadded:: 0.6.0
 
 domainquota
 -----------
 Syntax:
- | **vmm domainquota** *fqdn storage* [*messages*] [**force**]
- | **vmm dq** *fqdn storage* [*messages*] [**force**]
+ | **vmm domainquota** *fqdn storage* [**-m** *messages*] [**‒‒force**]
+ | **vmm dq** *fqdn storage* [**-m** *messages*] [**‒‒force**]
 
 This subcommand is used to configure a new quota limit for the accounts
 of the domain - not for the domain itself.
@@ -132,8 +152,8 @@
 
 The new quota limit will affect only those accounts for which the limit has
 not been overridden.
-If you want to restore the default to all accounts, you may pass the keyword
-**force**.
+If you want to restore the default to all accounts, you may pass the optional
+argument **‒‒force**.
 When the argument *messages* was omitted the default number of messages
 **0** (zero) will be applied.
 
@@ -141,15 +161,15 @@
 
 .. code-block:: console
 
- root@host:~# vmm domainquota example.com 1g force
+ root@host:~# vmm domainquota example.com 1g ‒‒force
 
 .. versionadded:: 0.6.0
 
 domainservices
 --------------
 Syntax:
- | **vmm domainservices** *fqdn* [*service ...*] [**force**]
- | **vmm ds** *fqdn* [*service ...*] [**force**]
+ | **vmm domainservices** *fqdn* [**-s** *service ...*] [**‒‒force**]
+ | **vmm ds** *fqdn* [**-s** *service ...*] [**‒‒force**]
 
 To define which services could be used by the users of the domain — with
 the given *fqdn* — use this subcommand.
@@ -160,7 +180,7 @@
 The new service set will affect only those accounts for which the set has
 not been overridden.
 If you want to restore the default to all accounts, you may pass the
-keyword **force**.
+option **‒‒force**.
 
 .. versionadded:: 0.6.0
 
@@ -169,15 +189,15 @@
 domaintransport
 ---------------
 Syntax:
- | **vmm domaintransport** *fqdn transport* [**force**]
- | **vmm dt** *fqdn transport* [**force**]
+ | **vmm domaintransport** *fqdn transport* [**‒‒force**]
+ | **vmm dt** *fqdn transport* [**‒‒force**]
 
 A new transport for the indicated domain can be set with this subcommand.
 
 The new transport will affect only those accounts for which the transport
 has not been overridden.
 If you want to restore the default to all accounts, you may pass the
-keyword **force**.
+option **‒‒force**.
 
 Example:
 
--- a/doc/web/source/installation/prerequisites.rst	Sun Mar 09 18:42:58 2014 +0000
+++ b/doc/web/source/installation/prerequisites.rst	Sun Mar 09 18:52:27 2014 +0000
@@ -1,9 +1,9 @@
 ==========================
 Installation Prerequisites
 ==========================
-You already should have installed and configured Postfix and Dovecot with
-PostgreSQL support. You also need access to a local or remote PostgreSQL
-server.
+You already should have installed and configured Postfix and Dovecot
+≥ 2.0.0 with PostgreSQL support. You also need access to a local or remote
+PostgreSQL server.
 
 Check for pgsql support in Dovecot and Postfix
 ----------------------------------------------
@@ -20,22 +20,7 @@
   root@host:~# dovecot --build-options | grep postgresql
   SQL drivers: mysql postgresql sqlite
 
-vmm depends on Python (≥ 2.4.0) and Psycopg_ (≥ 2.0) or pyPgSQL_ (≥ 2.5.1)
-[#]_. Psycopg and pyPgSQL are depending on parts of the *eGenix.com mx Base
-Distribution* (mxDateTime_ and mxTools_).
-
-If you are using Python ≤ 2.5.0:
-
- ‣ if you want to store your users' passwords as ``PLAIN-MD4`` digest in
-   the database, vmm will try to use ``Crypto.Hash.MD4`` from PyCrypto_
- ‣ if you are using Dovecot ≥ v1.1.0 and you want to store your users'
-   passwords as ``SHA256`` or ``SSHA256`` hashes, vmm will try to use
-   ``Crypto.Hash.SHA256`` from PyCrypto. For ``SHA256``/``SSHA256`` you
-   should have installed PyCrypto, at least in version 2.1.0alpha1.
-
- When the Crypto.Hash module couldn't be imported, vmm will use
- dovecotpw/doveadm, if  the *misc.password_scheme* setting in your
- :file:`vmm.cfg` is set to ``PLAIN-MD4``, ``SHA256`` or ``SSHA256``.
+vmm depends on Python (≥ 3.2) and Psycopg_ (≥ 2.0).
 
 If your Dovecot and/or Postfix installation shouldn't support PostgreSQL you
 could possibly fix this by installing the missing package (see below) or by
@@ -43,29 +28,19 @@
 
 Package names by OS/Distribution
 --------------------------------
-Debian GNU/Linux (Squeeze/Wheezy)
+Debian GNU/Linux (Wheezy)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ‣ `postfix <http://packages.debian.org/postfix>`_ and
    `postfix-pgsql <http://packages.debian.org/postfix-pgsql>`_
- ‣ Squeeze:
-
-   * `dovecot-common <http://packages.debian.org/dovecot-common>`_
-
- ‣ Wheezy (and Squeeze backports):
-
-   * `dovecot-core <http://packages.debian.org/dovecot-core>`_ and
-     `dovecot-lmtpd <http://packages.debian.org/dovecot-lmtpd>`_
-
+ ‣ `dovecot-core <http://packages.debian.org/dovecot-core>`_ and
+   `dovecot-lmtpd <http://packages.debian.org/dovecot-lmtpd>`_
  ‣ `dovecot-imapd <http://packages.debian.org/dovecot-imapd>`_ and/or
    `dovecot-pop3d <http://packages.debian.org/dovecot-pop3d>`_
  ‣ `postgresql-client <http://packages.debian.org/postgresql-client>`_
    (or `postgresql <http://packages.debian.org/postgresql>`_ , if you do not
    have a dedicated PostgreSQL server.)
- ‣ `python <http://packages.debian.org/python>`_,
-   `python-egenix-mxdatetime \
-   <http://packages.debian.org/python-egenix-mxdatetime>`_
-   and `python-psycopg2 <http://packages.debian.org/python-psycopg2>`_
-   optionally `python-crypto <http://packages.debian.org/python-crypto>`_
+ ‣ `python3 <http://packages.debian.org/python3>`_ and
+   `python3-psycopg2 <http://packages.debian.org/python3-psycopg2>`_
  ‣ `gettext <http://packages.debian.org/gettext>`_
 
 FreeBSD
@@ -92,13 +67,16 @@
  net-mail/dovecot postgres -pam pop3d sieve
  dev-db/postgresql-server -perl
 
-OpenBSD (5.x)
+OpenBSD (5.2)
 ^^^^^^^^^^^^^
 Packages
  ‣ postfix-2.x.y-pgsql
  ‣ dovecot-2.x.y and dovecot-postgresql-2.x.y
  ‣ postgresql-client
- ‣ python-2.7.x, py-psycopg2 and optionally py-crypto
+ ‣ python-3.2.x
+
+Build from source
+ ‣ Psycopg_
 
 Or build the above mentioned software from ports.
 
@@ -109,8 +87,4 @@
  ‣ dovecot
  ‣ python and pyPgSQL optionally python-crypto
 
-
-.. rubric:: Footnotes
-.. [#] Beginning with version 0.7.0 of vmm support for pyPgSQL will be dropped.
-
 .. include:: ../ext_references.rst
--- a/install.sh	Sun Mar 09 18:42:58 2014 +0000
+++ b/install.sh	Sun Mar 09 18:52:27 2014 +0000
@@ -52,19 +52,6 @@
 
 [ -d ${MANDIR}/man5 ] || mkdir -m 0755 -p ${MANDIR}/man5
 install -m 0644 ${INSTALL_OPTS} man5/vmm.cfg.5 ${MANDIR}/man5
-
-for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\?)
-do
-    for s in man1 man5; do
-        [ -d ${MANDIR}/${l}/${s} ] || mkdir -m 0755 -p ${MANDIR}/${l}/${s}
-    done
-    if [ -f ${l}/man1/vmm.1 ]; then
-        install -m 0644 ${INSTALL_OPTS} ${l}/man1/vmm.1 ${MANDIR}/${l}/man1
-    fi
-    if [ -f ${l}/man5/vmm.cfg.5 ]; then
-        install -m 0644 ${INSTALL_OPTS} ${l}/man5/vmm.cfg.5 ${MANDIR}/${l}/man5
-    fi
-done
 cd - >/dev/null
 
 [ -d ${DOC_DIR} ] || mkdir -m 0755 -p ${DOC_DIR}
--- a/man/de/man1/vmm.1	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1089 +0,0 @@
-.TH "VMM" "1" "2012-09-27" "vmm 0.6" "vmm"
-.SH NAME
-vmm \- Kommandozeilenprogramm zur Verwaltung von E\-Mail\-Domains/\-Konten
-und \-Aliase.
-.\" -----------------------------------------------------------------------
-.SH ÜBERSICHT
-.B vmm
-.IR Unterbefehl " [" "Argument ..." ]
-.\" -----------------------------------------------------------------------
-.SH BESCHREIBUNG
-.B vmm
-(a virtual mail manager) ist das einfach zu bedienende
-Kommandozeilenprogramm für Administratoren und Postmaster, zur Verwaltung
-von (Alias\-) Domains, Konten, Alias\-Adressen und sogenannten Relocated
-Users.
-Es ermöglicht die schnelle und einfache Verwaltung des Mailservers.
-.br
-Es wurde für Dovecot und Postfix mit einem PostgreSQL\-Backend entwickelt.
-.PP
-Von jedem
-.I Unterbefehl
-gibt es jeweils eine lange und kurze Variante.
-Die Kurzform ist in Klammern geschrieben.
-Bei beiden Formen ist die Groß\-/Kleinschreibung zu berücksichtigen.
-.PP
-Die meisten
-.IR Unterbefehl e
-erwarten ein oder mehrere
-.IR Argument e.
-.\" -----------------------------------------------------------------------
-.SH ARGUMENTE
-.TP 12
-.I address
-Die komplette E\-Mail\-Adresse
-.RI ( local\-part @ fqdn )
-eines Kontos, Aliases oder Relocated Users.
-.\" --------------------------
-.TP
-.I destination
-Ist entweder eine E\-Mail\-Adresse, wenn sie in Verbindung mit
-.I "ALIAS UNTERBEFEHLEN"
-verwendet wird, oder ein
-.I fqdn
-in Verbindung mit
-.IR "ALIASDOMAIN UNTERBEFEHLEN" .
-.\" --------------------------
-.TP
-.I fqdn
-Der voll qualifizierten Domain\-Namen \(em ohne den abschließenden Punkt
-\(em einer Domain oder Alias\-Domain.
-.\" --------------------------
-.TP
-.I messages
-Ein Integer\-Wert, der das maximal nutzbare Kontingent als Anzahl von
-Nachrichten festlegt.
-.br
-Der Wert
-.B 0
-(null) bedeutet unbegrenzt \(em kein Quota\-Limit als Anzahl von
-Nachrichten.
-.\" --------------------------
-.TP
-.I option
-ist der Name einer Konfigurationsoption mit vorangestellter
-Konfigurations\-Sektion, getrennt durch einen Punkt.
-Zum Beispiel:
-.IB misc . transport
-.br
-Alle Konfigurationsoptionen werden in
-.BR vmm.cfg (5)
-beschrieben.
-.\" --------------------------
-.TP
-.I service
-Der Name eines Services, der gewöhnlicherweise in Verbindung mit Dovecot
-genutzt wird.
-Folgende Services werden unterstützt:
-.BR imap ", " pop3 ", " sieve " und " smtp .
-.\" --------------------------
-.TP
-.I storage
-Bestimmt das maximal nutzbare Kontingent in Bytes.
-Eines der folgenden Präfixe kann dem dem ganzzahligen Wert angehängt
-werden:
-.BR b " (Bytes), " k " (Kilobytes), " M " (Megabytes) oder " G
-(Gigabytes).
-.br
-Der Wert
-.B 0
-(null) bedeutet unbegrenzt \(em kein Quota\-Limit in Bytes.
-.\" --------------------------
-.TP
-.I transport
-ein Transport für Postfix, angegeben in der Form:
-.IB transport :
-oder
-.IB transport :\c
-.IR nexthop .
-Siehe
-.BR transport (5)
-für weitere Details.
-.\" -----------------------------------------------------------------------
-.SH ALLGEMEINE UNTERBEFEHLE
-.SS configget (cg)
-.BI "vmm configget" " option"
-.PP
-Dieser Unterbefehl wird verwendet, um den aktuellen Wert der übergebenen
-.I option
-anzuzeigen.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm configget misc.crypt_sha512_rounds
-misc.crypt_sha512_rounds = 5000
-.fi
-.\" --------------------------
-.SS configset (cs)
-.B vmm configset
-.I option value
-.PP
-Verwenden Sie diesen Unterbefehl, um einer einzelnen Konfigurationsoption
-einen neuen Wert zuzuweisen.
-.I option
-ist der Name der Konfigurationsoption,
-.I value
-ist der Wert, der der Konfigurationsoption zugewiesen wird.
-.IP Hinweis:
-Diese Unterbefehl erstellt eine neue
-.IR vmm.cfg ,
-ohne Kommentare.
-Die aktuelle Konfigurationsdatei wird als
-.IR vmm.cfg.bak
-gesichert.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm configget domain.transport
-domain.transport = dovecot:
-.B vmm configset domain.transport lmtp:unix:private/dovecot\-lmtp
-.B vmm cg domain.transport
-domain.transport = lmtp:unix:private/dovecot\-lmtp
-.fi
-.\" ------------------------------------
-.SS configure (cf)
-.B vmm configure
-.RI [ section ]
-.PP
-Startet die interaktiven Konfiguration für alle Konfigurationssektionen.
-.PP
-Dabei wird der aktuell konfigurierte Wert einer jeden Option in eckigen
-Klammern ausgegeben.
-Sollte kein Wert konfiguriert sein, wird der Vorgabewert der jeweiligen
-Option in in eckigen Klammern angezeigt.
-Um den angezeigten Wert unverändert zu übernehmen, ist dieser mit der
-Eingabe\-Taste zu bestätigen.
-.PP
-Wurde das optionale Argument
-.I section
-angegeben, werden nur die Optionen der angegebenen Sektion angezeigt und
-können geändert werden.
-Folgende Sektionen sind vorhanden:
-.RS
-.TP 10
-.B account
-Konto Einstellungen
-.TP
-.B bin
-Pfade zu externen Binär\-Dateien
-.TP
-.B database
-Datenbank Einstellungen
-.TP
-.B domain
-Domain Einstellungen
-.TP
-.B mailbox
-Mailbox Einstellungen
-.TP
-.B misc
-Verschiedene Einstellungen
-.RE
-.PP
-Die Konfigurationsoptionen werden in
-.BR vmm.cfg (5)
-beschrieben.
-.IP Hinweis:
-Diese Unterbefehl erstellt eine neue
-.IR vmm.cfg ,
-ohne Kommentare.
-Die aktuelle Konfigurationsdatei wird als
-.IR vmm.cfg.bak
-gesichert.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm configure mailbox
-Konfigurationsdatei wird verwendet: /root/vmm.cfg
-
-* Konfigurationsabschnitt: »mailbox«
-Neuer Wert für Option folders [Drafts:Sent:Templates:Trash]:
-Neuer Wert für Option format [maildir]: mdbox
-Neuer Wert für Option subscribe [True]:
-Neuer Wert für Option root [Maildir]: mdbox
-.fi
-.\" ------------------------------------
-.SS getuser (gu)
-.BI "vmm getuser" " uid"
-.PP
-Wenn nur der
-.I uid
-eines Benutzers vorhanden ist, zum Beispiel aus der Prozessliste, kann mit
-dem Unterbefehl
-.B getuser
-die E\-Mail\-Adresse des Benutzers ermittelt werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm getuser 79876
-Konto Informationen
--------------------
-        UID............: 79876
-        GID............: 70704
-        Address........: a.user@example.com
-.fi
-.\" ------------------------------------
-.SS help (h)
-.B vmm help
-.RI [ subcommand ]
-.PP
-Gibt ein Liste aller vorhandenen Unterbefehle mit einer kurzen Beschreibung
-aus.
-Wurde ein
-.I subcommand
-angegeben, wird Hilfe zu diesem Unterbefehl ausgegeben.
-Danach wird
-.B vmm
-beendet.
-.\" ------------------------------------
-.SS listdomains (ld)
-.B vmm listdomains
-.RI [ pattern ]
-.PP
-Dieser Unterbefehl listet alle angelegten Domains auf.
-Allen Domains wird ein Präfix vorangestellt.
-Entweder ein `[+]', falls es sich um eine primäre Domain handelt, oder ein
-`[-]', falls es sich um eine Alias\-Domain handelt.
-Die Ausgabe kann reduziert werden, indem ein optionales Muster angegeben
-wird.
-.PP
-Um eine Wildcard\-Suche durchzuführen kann das %\-Zeichen am Anfang
-und/oder Ende des Musters verwendet werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listdomains %example%
-Übereinstimmende Domains
-------------------------
-        [+] example.com
-        [\-]     e.g.example.com
-        [\-]     example.name
-        [+] example.org
-        [+] sales.example.com
-.fi
-.\" ------------------------------------
-.SS listaddresses (ll)
-.B vmm listaddresses
-.RI [ pattern ]
-.PP
-Verwenden Sie diesen Unterbefehl, um alle Adressen anzuzeigen.
-Einträge regulärer Domains werden mit einem '+' gekennzeichnet, Einträge
-von Alias-Domains mit einem '-'. Zudem wird jedem Eintrag ein 'u', 'a',
-oder 'r' vorangestellt, welche den Eintrag als Benutzerkonto, Alias oder
-Relocated User identifizieren.
-.PP
-Mit dem optionalen Muster können Sie die Ausgabe einschränken. Akzeptiert
-wird entweder eine Domain oder ein SQL-Muster (% als Wildcard).
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listaddresses example.com
-.B vmm listaddresses %master@%
-.\" ------------------------------------
-.SS listaliases (la)
-.B vmm listaliases
-.RI [ pattern ]
-.PP
-Verwenden Sie diesen Unterbefehl, um alle Aliase anzuzeigen. Reguläre
-Aliase werden mit einem '+' gekennzeichnet, Aliase in Alias-Domains mit
-einem '-'.
-.PP
-Mit dem optionalen Muster können Sie die Ausgabe einschränken. Akzeptiert
-wird entweder eine Domain oder ein SQL-Muster (% als Wildcard).
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listaliases example.com
-.B vmm listaliases %master@%
-.\" ------------------------------------
-.SS listrelocated (lr)
-.B vmm listrelocated
-.RI [ pattern ]
-.PP
-Verwenden Sie diesen Unterbefehl, um alle Relocated Users anzuzeigen.
-Einträge regulärer Domains werden mit einem '+' gekennzeichnet, Einträge
-von Alias-Domains mit einem '-'.
-.PP
-Mit dem optionalen Muster können Sie die Ausgabe einschränken. Akzeptiert
-wird entweder eine Domain oder ein SQL-Muster (% als Wildcard).
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listrelocated example.com
-.B vmm listrelocated %master@%
-.\" ------------------------------------
-.SS listusers (lu)
-.B vmm listusers
-.RI [ pattern ]
-.PP
-Verwenden Sie diesen Unterbefehl, um alle Benutzerkonten anzuzeigen.
-Reguläre Benutzerkonten werden mit einem '+' gekennzeichnet, Benutzerkonten
-in Alias-Domains mit einem '-'.
-.PP
-Mit dem optionalen Muster können Sie die Ausgabe einschränken. Akzeptiert
-wird entweder eine Domain oder ein SQL-Muster (% als Wildcard).
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listusers example.com
-.B vmm listusers %master@%
-.\" ------------------------------------
-.SS listpwschemes (lp)
-.B vmm listpwschemes
-.PP
-Dieser Unterbefehl listet alle unterstützte Passwort\-Schemen, die als Wert
-für
-.I misc.password_scheme
-in der
-.I vmm.cfg
-verwendet werden können.
-Die Ausgabe variiert, je nach eingesetzter Dovecot Version und der libc des
-Systems.
-.br
-Sollte Ihre Dovecot\-Installation nicht zu alt sein, werden zusätzlich
-die verwendbaren Encoding\-Suffixe ausgegeben.
-Eines dieser Suffixe kann an das Passwort\-Schema angefügt werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm listpwschemes
-Verfügbare Passwort-Schemata
-----------------------------
-        CRYPT SHA512-CRYPT LDAP-MD5 DIGEST-MD5 SHA256 SHA512 SSHA512
-        SKEY SSHA NTLM RPA MD5-CRYPT HMAC-MD5 SHA1 PLAIN SHA CRAM-MD5
-        SSHA256 MD5 LANMAN CLEARTEXT PLAIN-MD5 PLAIN-MD4 OTP SMD5
-        SHA256-CRYPT
-
-Verwendbare Encoding-Suffixe
-----------------------------
-        .B64 .BASE64 .HEX
-.fi
-.\" ------------------------------------
-.SS version (v)
-.B vmm version
-.PP
-Gibt Versions\- und Copyright\-Informationen zu
-.B vmm
-aus.
-Danach wird
-.B vmm
-beendet.
-.\" -----------------------------------------------------------------------
-.SH DOMAIN UNTERBEFEHLE
-.SS domainadd (da)
-.B vmm domainadd
-.IR fqdn " [" transport ]
-.PP
-Fügt eine neue Domain in die Datenbank ein und erstellt das
-Domain\-Verzeichnis.
-.PP
-Wurde das optional Argument
-.I transport
-angegeben, ersetzt der angegebene Transport den konfigurierten Transport
-.RI ( misc.transport ") aus " vmm.cfg .
-Der angegebene
-.I transport
-ist der Vorgabe\-Transport für alle Konten, die dieser Domain zugeordnet
-werden.
-.PP
-Konfigurationsbezogenes Verhalten:
-.RS
-.TP
-.I domain.auto_postmaster
-Wenn diese Option den Wert
-.B true
-(Vorgabe) hat, wird
-.B vmm
-nach erfolgreichem Anlegen der Domain auch das Konto für
-.BI postmaster@ fqdn
-erstellen.
-.TP
-.I account.random_password
-Sollte dieser Option ebenfalls der Wert
-.B true
-zugewiesen sein, wird ein zufällig generiertes Passwort für den
-Postmaster\-Account gesetzt und auf stdout ausgegeben.
-.RE
-.PP
-Beispiele:
-.PP
-.nf
-.B vmm domainadd support.example.com smtp:[mx1.example.com]:2025
-Konto für postmaster@support.example.com wird angelegt
-Neues Passwort eingeben:
-Neues Passwort wiederholen:
-.B vmm cs account.random_password true
-.B vmm domainadd vertrieb.example.com
-Konto für postmaster@vertrieb.example.com wird angelegt
-Erzeugtes Passwort: YoG3Uw*5aH
-.fi
-.\" ------------------------------------
-.SS domaindelete (dd)
-.BI "vmm domaindelete " fqdn
-.RB [ force ]
-.PP
-Dieser Unterbefehl löscht die Domain mit dem angegebenen
-.IR fqdn .
-.PP
-Sollten der zu löschenden Domain Konten, Aliase und/oder Relocated User
-zugeordnet sein, wird
-.B vmm
-die Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden.
-Sollten Sie sich Ihres Vorhabens sicher sein, so kann optional das
-Schlüsselwort
-.B force
-angegeben werden.
-.PP
-Sollten Sie wirklich immer wissen was Sie tun, so editieren Sie Ihre
-.I vmm.cfg
-und setzen den Wert der Option
-.I domain.force_deletion
-auf
-.BR true .
-Dann werden Sie zukünftig beim Löschen von Domains nicht mehr wegen
-vorhanden Konten, Aliase und/oder Relocated User gewarnt.
-.\" ------------------------------------
-.SS domaininfo (di)
-.B vmm domaininfo
-.IR fqdn \ [ details ]
-.PP
-Dieser Unterbefehl zeigt Informationen zur Domain mit dem angegebenen
-.I fqdn
-an.
-.PP
-Um detaillierte Informationen über die Domain zu erhalten, kann das
-optionale Argument
-.I details
-angegeben werden.
-Ein möglicher Wert für
-.I details
-kann eines der folgenden sechs Schlüsselwörter sein:
-.RS
-.TP 13
-.B accounts
-um alle eingerichteten Konten aufzulisten
-.TP
-.B aliasdomains
-um alle zugeordneten Alias\-Domains aufzulisten
-.TP
-.B aliases
-um alle vorhandenen Alias\-Adressen aufzulisten
-.TP
-.B catchall
-um alle Catch\-all\-Ziele aufzulisten
-.TP
-.B relocated
-um alle Adressen der Relocated Users aufzulisten
-.TP
-.B full
-um alle oben genannten Informationen aufzulisten
-.RE
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm domaininfo sales.example.com
-Domain Informationen
---------------------
-        Domain Name......: sales.example.com
-        GID..............: 70708
-        Domain Directory.: /srv/mail/c/70708
-        Quota Limit/User.: Storage: 500,00 MiB; Messages: 10.000
-        Active Services..: IMAP SIEVE
-        Transport........: lmtp:unix:private/dovecot-lmtp
-        Alias Domains....: 0
-        Accounts.........: 1
-        Aliases..........: 0
-        Relocated........: 0
-        Catch-All Dests..: 1
-.fi
-.\" ------------------------------------
-.SS domainquota (dq)
-.B vmm domainquota
-.IR "fqdn storage" " [" messages ]
-.RB [ force ]
-.PP
-Dieser Unterbefehl wird verwendet, um für die Konten der Domain ein neues
-Quota\-Limit festzulegen.
-.PP
-Standardmäßig gilt für Konten das Quota\-Limit der
-.IR vmm.cfg " (" domain.quota_bytes " und " domain.quota_messages ).
-Das neue Quota\-Limit gilt für für alle bestehenden Konten, die nicht selbst
-ein Quota\-Limit definieren. Soll das neue Quota\-Limit auch für Konten mit
-eigenen Limiten angewendet werden, so ist das optionale Schlüsselwort
-.B force
-anzugeben.
-.br
-Wenn der Wert für das Argument
-.I messages
-ausgelassen wurde, wird der Vorgabewert
-.B 0
-(null) als Anzahl von Nachrichten angewendet werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm domainquota example.com 1g force
-.fi
-.\" ------------------------------------
-.SS domainservices (ds)
-.B vmm domainservices
-.IR fqdn " [" "service ..." ]
-.RB [ force ]
-.PP
-Um festzulegen, welche Services für die Anwender der Domain \(em mit dem
-angegebenen
-.I fqdn
-\(em nutzbar sein sollen, wird dieser Unterbefehl verwendet.
-.PP
-Der Zugriff auf alle genannten Services wird den Anwender gestattet.
-Der Zugriff auf nicht genannte Services wird verweigert werden.
-Verwendbare
-.IR service \-Namen
-sind:
-.BR  imap ", " pop3 ", " sieve " und " smtp .
-.br
-Wird das Schlüsselwort
-.B force
-angegeben, so werden alle kontospezifischen Einstellungen gelöscht und es
-gelten fortan die Service\-Einstellungen der Domain für alle Konten. Ohne
-dieses Schlüsselwort gelten die neuen Einstellungen nur für Konten, bei denen
-die Service\-Einstellungen nicht individuell geändert wurden.
-.\" ------------------------------------
-.SS domaintransport (dt)
-.BI "vmm domaintransport" " fqdn transport"
-.RB [ force ]
-.PP
-Ein neuer
-.I transport
-für die Domain mit dem angegebenen
-.I fqdn
-kann mit diesem Unterbefehl festgelegt werden.
-.PP
-Wird das Schlüsselwort
-.B force
-angegeben, so werden alle kontospezifischen Einstellungen gelöscht und es
-gelten fortan die Transport\-Einstellungen der Domain für alle Konten. Ohne
-dieses Schlüsselwort gelten die neuen Einstellungen nur für Konten, bei denen
-die Transport\-Einstellungen nicht individuell geändert wurden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm domaintransport support.example.com dovecot:
-.fi
-.\" ------------------------------------
-.SS domainnote (do)
-.BI "vmm domainnote" " fqdn"
-.RI [ note ]
-.PP
-Mit diesem Unterbefehl kann eine Domain mit einer Notiz versehen werden. Um
-die Notiz wieder zu löschen, läßt man sie einfach weg.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm do example.com Gehört Robert
-.fi
-.\" -----------------------------------------------------------------------
-.SH ALIAS\-DOMAIN UNTERBEFEHLE
-Eine Alias\-Domain ist ein Alias für eine Domain, die zuvor mit dem
-Unterbefehl
-.B domainadd
-erstellt wurde.
-Alle Konten, Aliase und Relocated Users der Domain sind ebenfalls unter der
-Alias\-Domain verfügbar.
-.br
-Im Folgenden wird angenommen, example.net sei ein Alias für example.com.
-.PP
-Postfix wird nicht erst fälschlicherweise E\-Mails für
-unbekannten.user@example.net annehmen und später an den \(em oftmals
-gefälschten \(em Absender bouncen.
-Postfix wird E\-Mails an unbekannte Empfänger sofort ablehnen.
-.br
-Dieses Verhalten ist sichergestellt, solange die empfohlenen
-Datenbankabfragen in
-.I $config_directory/pgsql\-*.cf
-konfiguriert sind.
-.\" ------------------------------------
-.SS aliasdomainadd (ada)
-.BI "vmm aliasdomainadd" " fqdn destination"
-.PP
-Dieser Unterbefehl legt die Alias\-Domain
-.RI ( fqdn )
-als Alias für eine bestehende Domain
-.RI ( destination ") an."
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm aliasdomainadd example.net example.com
-.fi
-.\" ------------------------------------
-.SS aliasdomaindelete (add)
-.BI "vmm aliasdomaindelete" " fqdn"
-.PP
-Verwenden Sie diesen Unterbefehl, um die Alias\-Domain
-.I fqdn
-zu löschen.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm aliasdomaindelete e.g.example.com
-.fi
-.\" ------------------------------------
-.SS aliasdomaininfo (adi)
-.BI "vmm aliasdomaininfo" " fqdn"
-.PP
-Dieser Unterbefehl gibt Informationen darüber aus, welcher Domain die
-Alias\-Domain
-.I fqdn
-aktuell zugeordnet ist.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm adi example.net
-Alias\-Domain Informationen
---------------------------
-        Die Alias\-Domain example.net gehört zu:
-            * example.com
-.fi
-.\" ------------------------------------
-.SS aliasdomainswitch (ads)
-.BI "vmm aliasdomainswitch" " fqdn destination"
-.PP
-Wenn Sie die bereits vorhandene Alias\-Domain
-.I fqdn
-einer anderen Ziel\-Domain zuordnen wollen, verwenden Sie diesen
-Unterbefehl.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm aliasdomainswitch example.net example.org
-.fi
-.\" -----------------------------------------------------------------------
-.SH KONTO UNTERBEFEHLE
-.SS useradd (ua)
-.B vmm useradd
-.IR address " [" password ]
-.PP
-Mit diesem Unterbefehl wird ein neues Konto für die angegebene Adresse
-angelegt.
-.PP
-Wurde kein Passwort angegeben wird
-.B vmm
-dieses im interaktiven Modus erfragen.
-Falls kein Passwort angegeben wurde und
-.I account.random_password
-den Wert
-.B true
-hat, wird
-.B vmm
-ein zufälliges Passwort generieren und auf stdout ausgeben, nachdem das
-Konto angelegt wurde.
-.PP
-Beispiele:
-.PP
-.nf
-.B vmm ua d.user@example.com \(dqA 5ecR3t P4s5\(rs/\(rs/0rd\(dq
-.B vmm useradd e.user@example.com
-Neues Passwort eingeben:
-Neues Passwort wiederholen:
-.fi
-.\" ------------------------------------
-.SS userdelete (ud)
-.BI "vmm userdelete" " address"
-.RB [ force ]
-.PP
-Verwenden Sie diesen Unterbefehl, um das Konto mit der angegebenen Adresse
-zu löschen.
-.PP
-Sollte es einen oder mehrere Aliase geben, deren Ziel\-Adresse mit der
-Adresse des zu löschenden Kontos identisch ist, wird
-.B vmm
-die Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden.
-Um dieses zu umgehen, kann das optionale Schlüsselwort
-.B force
-angegebenen werden.
-.\" ------------------------------------
-.SS userinfo (ui)
-.B "vmm userinfo"
-.IR address " [" details ]
-.PP
-Dieser Unterbefehl zeigt einige Informationen über das Konto mit der
-angegebenen Adresse an.
-.PP
-Wurde das optionale Argument
-.I details
-angegeben, werden weitere Informationen ausgegeben.
-Mögliche Werte für
-.I details
-sind:
-.RS
-.TP 8
-.B aliases
-um alle Alias\-Adressen, mit dem Ziel
-.IR address ,
-aufzulisten
-.TP
-.B du
-um zusätzlich die Festplattenbelegung des Mail\-Verzeichnisses eines Kontos
-anzuzeigen.
-Soll die Festplattenbelegung jedes Mal mit der
-.B userinfo
-ermittelt werden, ist in der
-.I vmm.cfg
-der Wert der Option
-.I account.disk_usage
-auf
-.B true
-zu setzen.
-.TP
-.B full
-um alle oben genannten Informationen anzuzeigen
-.RE
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm userinfo d.user@example.com
-Konto Informationen
--------------------
-        Address..........: d.user@example.com
-        Name.............: None
-        UID..............: 79881
-        GID..............: 70704
-        Home.............: /srv/mail/2/70704/79881
-        Mail_Location....: mdbox:~/mdbox
-        Quota Storage....: [  0,00%] 0/500,00 MiB
-        Quota Messages...: [  0,00%] 0/10.000
-        Transport........: lmtp:unix:private/dovecot-lmtp
-        SMTP.............: deaktiviert
-        POP3.............: deaktiviert
-        IMAP.............: aktiviert
-        SIEVE............: aktiviert
-.fi
-.\" ------------------------------------
-.SS username (un)
-.BI "vmm username" " address"
-.RI [ name ]
-.PP
-Der bürgerliche Name des Kontoinhabers mit der angegebenen Adresse kann mit
-diesem Unterbefehl gesetzt/aktualisiert werden.
-.PP
-Wird kein
-.I name
-übergeben, so wird der Wert in den Kontoinformationen gelöscht.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm username d.user@example.com \(dqJohn Doe\(dq
-.fi
-.\" ------------------------------------
-.SS userpassword (up)
-.BI "vmm userpassword" " address"
-.RI [ password ]
-.PP
-Das Passwort eines Kontos kann mit diesem Unterbefehl aktualisiert werden.
-.PP
-Wurde kein Passwort angegeben, wird
-.B vmm
-dieses im interaktiven Modus erfragen.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm up d.user@example.com \(dqA |\(rs/|0r3 5ecur3 P4s5\(rs/\(rs/0rd?\(dq
-.fi
-.\" ------------------------------------
-.SS usernote (uo)
-.BI "vmm usernote" " address"
-.RI [ note ]
-.PP
-Mit diesem Unterbefehl kann ein Konto mit einer Notiz versehen werden. Um die
-Notiz wieder zu löschen, läßt man sie einfach weg.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm uo d.user@example.com Wird nur bis Ende Mai 2012 gebraucht
-.fi
-.\" ------------------------------------
-.SS userquota (uq)
-.BI "vmm userquota" " address storage"
-.RI [ messages ]
-.PP
-Um ein neues Quota\-Limit für das Konto mit der angegebenen Adresse
-festzulegen, wird dieser Unterbefehl verwendet.
-.PP
-Wenn der Wert für das Argument
-.I messages
-ausgelassen wurde, wird der Vorgabewert
-.B 0
-(null) als Anzahl von Nachrichten angewendet werden.
-.PP
-Anstelle der Limits, bewirkt das Wort
-.BR domain ,
-dass die Limits des Kontos gelöscht wird und somit wieder der in der
-Domain gespeicherte Wert für das Konto gilt.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm userquota d.user@example.com 750m
-.B vmm userquote d.user@example.com domain
-.fi
-.\" ------------------------------------
-.SS userservices (us)
-.B vmm userservices
-.IR address " [" "service ..." ]
-.PP
-Verwenden Sie diesen Unterbefehl, um einem Anwender den Zugriff auf die
-genannten Services zu gestatten.
-.PP
-Der Zugriff auf alle nicht genannten Services wird dem Anwender, mit der
-angegebenen Adresse, verwehrt werden.
-.PP
-Anstelle einer Liste, bewirkt das Wort
-.BR domain ,
-dass die benutzerspezifische Liste gelöscht wird und somit wieder die in
-der Domain gespeicherte Liste für das Konto gilt.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm userservices d.user@example.com SMTP IMAP
-.B vmm userservices d.user@example.com domain
-.\" ------------------------------------
-.SS usertransport (ut)
-.BI "vmm usertransport" " address transport"
-.PP
-Mit diesem Unterbefehl kann ein abweichender
-.I transport
-für das Konto mit der angegebenen Adresse bestimmt werden.
-.PP
-Wird als
-.I transport
-das Wort 'domain' übergeben, so wird der explizite Transport des Kontos
-wieder gelöscht und der in der Domain gespeicherte Wert benutzt.
-.PP
-Beispiel:
-.br
-Angenommen, Sie wollen mit Dovecots
-.BR dsync (1)
-die E\-Mails vom Maildir\-Format ins mdbox\-Format konvertieren, dann
-können Sie Postfix, über den Transport, darüber informieren, es später
-nochmals zu versuchen.
-.PP
-.nf
-.B vmm ut d.user@example.com \(dqretry:4.0.0 Mailbox being migrated\(dq
-# Konvertieren der Mailbox …
-# … danach den Transport auf den Domainwert setzen
-.B vmm usertransport d.user@example.com domain
-.fi
-.\" -----------------------------------------------------------------------
-.SH ALIAS UNTERBEFEHLE
-.SS aliasadd (aa)
-.BI "vmm aliasadd" " address destination ..."
-.PP
-Mit diesem Unterbefehl werden neue Alias\-Adressen, mit einer oder mehren
-.IR destination (en),
-erstellt.
-.PP
-Innerhalb der Zieladresse werden die Zeichenketten
-.IR %n ,
-.IR %d
-und
-.IR %=
-durch den ursprünglichen lokalen Teil, die Domain bzw. die Emailadresse mit
-'=' anstelle von '@' ersetzt. Dies ermöglicht z.B. in Verbindung mit
-Alias\-Domains domain\-spezifische Empfänger.
-.PP
-Beispiele:
-.PP
-.nf
-.B vmm aliasadd john.doe@example.com d.user@example.com
-.B vmm aa support@example.com d.user@example.com e.user@example.com
-.B vmm aa postmaster@example.com postmaster+%d@example.org
-.fi
-.\" ------------------------------------
-.SS aliasdelete (ad)
-.BI "vmm aliasdelete" " address"
-.RI [ destination " ...]"
-.PP
-Verwenden Sie diesen Unterbefehl um den Alias mit der angegebenen Adresse
-zu löschen.
-.PP
-Wurden eine oder mehrere optionale
-.I destination
-Adressen angegeben, so werden nur diese
-.IR destination s
-vom angegebenen Alias entfernt.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm aliasdelete support@example.com d.user@example.com
-.fi
-.\" ------------------------------------
-.SS aliasinfo (ai)
-.BI "vmm aliasinfo" " address"
-.PP
-Informationen zum Alias mit der angegebenen Adresse können mit diesem
-Unterbefehl ausgegeben werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm aliasinfo support@example.com
-Alias Informationen
--------------------
-        E\-Mails für support@example.com werden weitergeleitet an:
-             * e.user@example.com
-.fi
-.\" -----------------------------------------------------------------------
-.SH RELOCATED UNTERBEFEHLE
-.SS relocatedadd (ra)
-.BI "vmm relocatedadd" " address newaddress"
-.PP
-Um einen neuen Relocated User anzulegen kann dieser Unterbefehl verwendet
-werden.
-.PP
-Dabei ist
-.I address
-die ehemalige Adresse des Benutzers, zum Beispiel b.nutzer@example.com, und
-.I newaddress
-die neue Adresse, unter der die/der Benutzer/in erreichbar ist.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm relocatedadd b.nutzer@example.com b\-nutzer@firma.tld
-.fi
-.\" ------------------------------------
-.SS relocatedinfo (ri)
-.BI "vmm relocatedinfo " address
-.PP
-Dieser Unterbefehl zeigt die neue Adresse des Relocated Users mit mit der
-angegebenen Adresse.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm relocatedinfo b.nutzer@example.com
-Verschiebe\-Informationen
-------------------------
-        Der Benutzer »b.nutzer@example.com« wurde nach »b\-nutzer@firma.tld« verschoben
-.fi
-.\" ------------------------------------
-.SS relocateddelete (rd)
-.BI "vmm relocateddelete " address
-.PP
-Mit diesem Unterbefehl kann der Relocated User mit der angegebenen Adresse
-gelöscht werden.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm relocateddelete b.nutzer@example.com
-.fi
-.\" -----------------------------------------------------------------------
-.SH CATCH\-ALL UNTERBEFEHLE
-.SS catchalladd (caa)
-.BI "vmm catchalladd" " fqdn destination ..."
-.PP
-Mit diesem Unterbefehl können für eine Domain Adressen definiert werden, an
-die E\-Mails geleitet werden, die an nicht\-existente Adressen innerhalb
-dieser Domains adressiert sind.
-Diese Adressen \(dqfangen alle\(dq diese E\-Mails auf, es sei denn es
-bestehen spezifischere Aliase, Mailboxen oder Relocated\-Einträge.
-.PP
-WARNUNG: Catch\-all Adressen können dazu führen, dass ein Mailserver von
-Spam überflutet wird, da Spammer zuweilen gerne alle möglichen Emailadressen
-ausprobieren und man auf einmal zig tausend Nachrichten gerichtet an
-Adressen von abba@example.org bis zztop@example.org weitergeleitet bekommt.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm catchalladd example.com b.nutzer@example.org
-.fi
-.\" ------------------------------------
-.SS catchallinfo (cai)
-.BI "vmm catchallinfo " fqdn
-.PP
-Dieser Unterbefehl zeigt die für eine Domain definierten Catch\-all Aliase
-an.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm catchallinfo example.com
-Catch-all Informationen
------------------------
-  Nachrichten an unbekannte Adressen innerhalb der example.com Domäne werden
-  weitergeleitet an:
-         * b.nutzer@example.org
-.fi
-.\" ------------------------------------
-.SS catchalldelete (cad)
-.BI "vmm catchalldelete " fqdn
-.RI [ destination " ...]"
-.PP
-Mit diesem Unterbefehl werden Catch\-all Aliase einer Domain wieder
-gelöscht,
-entweder nur das/die angegebene(n) Alias(e), oder alle, wenn keine
-.I destination
-Adresse angegeben wurde.
-.PP
-Beispiel:
-.PP
-.nf
-.B vmm catchalldelete example.com b.nutzer@example.org
-.fi
-.\" -----------------------------------------------------------------------
-.SH DATEIEN
-.TP
-.I /root/vmm.cfg
-Wird verwendet, falls vorhanden.
-.TP
-.I /usr/local/etc/vmm.cfg
-Wird verwendet, sollte obige Datei nicht gefunden werden.
-.TP
-.I /etc/vmm.cfg
-Wird verwendet, falls die oben genannten Dateien nicht existieren.
-.\" -----------------------------------------------------------------------
-.SH SIEHE AUCH
-.BR dsync (1),
-.BR transport (5),
-.BR vmm.cfg (5)
-.\" -----------------------------------------------------------------------
-.SH INTERNET RESSOURCEN
-.TP
-Homepage
-http://vmm.localdomain.org/
-.TP
-Projekt\-Seite
-http://sf.net/projects/vmm/
-.TP
-Bugtracker
-https://bitbucket.org/pvo/vmm/issues
-.\" -----------------------------------------------------------------------
-.SH COPYING
-vmm und die dazugehörigen Manualseiten wurden von Pascal Volk <user+vmm AT
-localhost.localdomain.org> geschrieben und sind unter den Bedingungen der
-BSD Lizenz lizenziert.
\ No newline at end of file
--- a/man/de/man5/vmm.cfg.5	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,625 +0,0 @@
-.TH "VMM.CFG" "5" "2012-08-12" "vmm 0.6" "vmm"
-.SH NAME
-vmm.cfg \- Konfigurationsdatei für vmm
-.\" -----------------------------------------------------------------------
-.SH ÜBERSICHT
-vmm.cfg
-.\" -----------------------------------------------------------------------
-.SH BESCHREIBUNG
-.BR vmm (1)
-liest seine Konfigurationsparameter aus der Datei
-.IR vmm.cfg .
-.PP
-Die Konfigurationsdatei ist in mehrere Sektionen unterteilt.
-Jede Sektion wird mit dem in eckigen Klammern
-.RB ' [ "' und '" ] '
-eingefassten Namen der Sektion eingeleitet, gefolgt von
-.RI ' Option " = " Wert '
-Einträgen.
-.PP
-Leerräume um das Gleichheitszeichen '=' und am Ende eines Wertes werden
-ignoriert.
-Leerzeilen und Zeilen, die mit einer '#' oder einem ';' anfangen, werden
-ignoriert.
-.PP
-Jeder Wert ist von einem der folgenden Datentypen:
-.PP
-.TP 8
-.I Boolean
-um festzulegen, ob etwas eingeschaltet/aktiviert (true) oder
-ausgeschaltet/deaktiviert (false) ist.
-.br
-Mögliche Werte für
-.I true
-sind:
-.BR 1 , " yes" , " true" " und " on .
-.br
-Mögliche Werte für
-.I false
-sind:
-.BR 0 , " no" , " false" " und " off .
-.TP
-.I Int
-eine Integer\-Zahl, geschrieben ohne eine gebrochene oder dezimale
-Komponente.
-.br
-Beispielsweise
-.BR 1 , " 50" " oder " 321
-sind Integer\-Zahlen.
-.TP
-.I String
-eine Folge von Buchstaben und Zahlen.
-.br
-Zum Beispiel:
-.RB ' Wort "', '" "Hallo Welt" "' oder '" /usr/bin/strings '
-.PP
-Die meisten Optionen haben einen Vorgabewert.
-Dieser ist nach dem Namen der Option in Klammern angegebenen.
-Um den Vorgabewert einer Option zu verwenden, wird die entsprechende Zeile
-entweder mit
-.BR # " oder " ;
-auskommentiert oder die Zeile wird einfach aus der
-.I vmm.cfg
-entfernt.
-.PP
-Eine minimale
-.I vmm.cfg
-könnte so aussehen:
-.PP
-.nf
-[database]
-user = ich
-pass = xxxxxxxx
-
-[misc]
-dovecot_version = 1.2.16
-.fi
-.\" -----------------------------------------------------------------------
-.SH SUCHREIHENFOLGE
-Standardmäßig sucht
-.BR vmm (1)
-die
-.I vmm.cfg
-in folgenden Verzeichnissen, in der angegebenen Reihenfolge:
-.RS
-.PD 0
-.TP
-.I
-/root
-.TP
-.I
-/usr/local/etc
-.TP
-.I
-/etc
-.PD
-.RE
-.PP
-Die zuerst gefundene Datei wird verwendet.
-.\" -----------------------------------------------------------------------
-.SH SEKTION ACCOUNT
-Die Optionen der Sektion
-.B account
-legen Konto\-spezifische Einstellungen fest.
-.SS account.delete_directory
-.BR delete_directory " (Vorgabe: false) :"
-.I Boolean
-.PP
-Bestimmt das Verhalten von
-.BR vmm (1)
-beim Löschen eines Kontos (userdelete).
-Wenn der Wert dieser Option
-.I true
-ist, wird das Home\-Verzeichnis des zu löschenden Anwenders rekursiv
-gelöscht.
-.\" ------------------------------------
-.SS account.directory_mode
-.BR directory_mode " (Vorgabe: 448) :"
-.I Int
-.PP
-Zugriffsbits des Home\-Verzeichnisses, sowie aller enthaltenen
-Verzeichnisse, in Dezimal\-Schreibweise (Basis 10).
-.br
-Beispiel: 'drwx\-\-\-\-\-\-' \(-> oktal 0700 \(-> dezimal 448
-.\" ------------------------------------
-.SS account.disk_usage
-.BR disk_usage " (Vorgabe: false) :"
-.I Boolean
-.PP
-Legt fest, ob die Festplattenbelegung des Maildirs eines Benutzers jedes
-Mal mit
-.BR du (1)
-ermittelt und mit den Konto\-Informationen ausgegeben werden soll.
-.PP
-Bei umfangreichen Maildirs kann das langsam sein.
-Falls Sie Quotas aktiviert haben, wird der
-.BR vmm\-Unterbefehl
-userinfo ebenfalls die aktuelle Quota\-Nutzung des Kontos mit ausgegeben.
-Sie können auch eines der optionalen Argumente
-.BR du " oder " full
-an userinfo übergeben, um sich die aktuelle Festplattenbelegung anzeigen zu
-lassen.
-.\" ------------------------------------
-.SS account.password_length
-.BR password_length " (Vorgabe: 8) :"
-.I Int
-.PP
-Diese Option legt die Anzahl der Zeichen für automatisch erzeugte
-Passwörter fest.
-Alle Werte kleiner als 8 werden auf 8 erhöht.
-.\" ------------------------------------
-.SS account.random_password
-.BR random_password " (Vorgabe: false) :"
-.I Boolean
-.PP
-Mit dieser Option wird bestimmt, ob
-.BR vmm (1)
-ein zufälliges Passwort generieren soll, wenn kein Passwort an den
-Unterbefehl useradd übergeben wurde.
-Ist der Wert dieser Option
-.IR false ,
-wird
-.B vmm
-Sie auffordern, ein Passwort für den neuen Account einzugeben.
-.PP
-Sie können die Länge für automatisch generierte Passwörter mit der Option
-.I account.password_length
-konfigurieren.
-.\" -----------------------------------------------------------------------
-.SH SEKTION BIN
-In der
-.BR bin \-Sektion
-werden die Pfade zu den von
-.BR vmm (1)
-benötigten Binaries angegeben.
-.SS bin.dovecotpw
-.BR dovecotpw " (Vorgabe: /usr/sbin/dovecotpw) :"
-.I String
-.PP
-Der absolute Pfad zum dovecotpw Binary.
-Geben Sie den absoluten Pfad zum
-.BR doveadm (1)
-Binary an, falls Sie Dovecot v2.0 verwenden.
-.PP
-Dieses Binary wird zur Hash\-Erzeugung verwendet, wenn
-.I misc.password_scheme
-einen der nachfolgenden Werte hat: 'CRAM\-MD5', 'HMAC\-MD5', 'LANMAN',
-\(aqOTP', 'RPA' oder 'SKEY'.
-Dieses Binary wird auch benötigt, wenn Ihre Python\-Installation einen der
-folgenden Hash\-Algorithmen nicht unterstützt:
-.IP \(bu 4
-md4: (hashlib + OpenSSL oder PyCrypto) verwendet für die Passwort\-Schemen:
-\(aqPLAIN\-MD4' und 'NTLM'
-.IP \(bu
-sha256: (hashlib oder PyCrypto \(>= 2.1.0alpha1) verwendet für die
-Passwort\-Schemen: 'SHA256' und 'SSHA256'
-.IP \(bu
-sha512: (hashlib) verwendet für die Passwort\-Schemen: 'SHA512' und
-\(aqSSHA512'
-.PP
-Das
-.BR doveadm (1)
-Binary wird auch gebraucht, um die INBOX und zusätzliche Mailboxen
-.RI ( mailbox.folders )
-für einen neuen Account zu erstellen, wenn die Option
-.I mailbox.format
-den Wert
-.BR mdbox " oder " sdbox
-hat.
-.\" ------------------------------------
-.SS bin.du
-.BR du " (Vorgabe: /usr/bin/du) :"
-.I String
-.PP
-Der absolute Pfad zu
-.BR du (1).
-Dieses Binary wird verwendet, wenn die Festplattenbelegung eines Kontos
-ermittelt wird.
-.\" ------------------------------------
-.SS bin.postconf
-.BR postconf " (Vorgabe: /usr/sbin/postconf) :"
-.I String
-.PP
-Der absolute Pfad zu Postfix'
-.BR postconf (1).
-Dieses Binary wird verwendet, wenn
-.BR vmm (1)
-diverse Postfix\-Einstellungen prüft, zum Beispiel das
-.IR virtual_alias_expansion_limit .
-.\" -----------------------------------------------------------------------
-.SH SEKTION DATABASE
-Die
-.BR database \-Sektion
-wird verwendet, um die für den Datenbankzugriff erforderlichen Optionen
-festzulegen.
-.SS database.host
-.BR host " (Vorgabe: localhost) :"
-.I String
-.PP
-Der Hostname oder die IP\-Adresse des Datenbankservers.
-.\" ------------------------------------
-.SS database.module
-.BR module " (Vorgabe: psycopg2) :"
-.I String
-.PP
-Das für den Datenbankzugriff zu verwendende Python PostgreSQL Adapter
-Modul.
-Unterstützte Module sind
-.BR psycopg2 " und " pyPgSQL .
-.\" ------------------------------------
-.SS database.name
-.BR name " (Vorgabe: mailsys) :"
-.I String
-.PP
-Der Name der zu verwendenden Datenbank.
-.\" ------------------------------------
-.SS database.pass
-.BR pass " (Vorgabe: " None ") :"
-.I String
-.PP
-Das Passwort des Datenbank\-Benutzers.
-.\" ------------------------------------
-.SS database.port
-.BR port " (Vorgabe: 5432) :"
-.I Int
-.PP
-Der TCP\-Port, auf dem der Datenbankserver Verbindungen annimmt.
-.\" ------------------------------------
-.SS database.sslmode
-.BR sslmode " (Vorgabe: prefer) :"
-.I String
-.PP
-Bestimmt, ob und mit welcher Priorität eine SSL\-Verbindung mit dem
-Datenbankserver ausgehandelt wird.
-Mögliche Werte sind:
-.BR disabled ", " allow ", " prefer ", " require ", " verify\-ca " und "
-.BR verify\-full .
-Die Modi
-.BR verify\-ca " und " verify\-full
-stehen seit PostgreSQL 8.4 zur Verfügung.
-.PP
-Diese Option wird ignoriert, wenn das
-.I database.module
-.B pyPgSQL
-verwendet wird.
-.\" ------------------------------------
-.SS database.user
-.BR user " (Vorgabe: " None ") :"
-.I String
-.PP
-Der Name des Datenbank\-Benutzers.
-.\" -----------------------------------------------------------------------
-.SH SEKTION DOMAIN
-In der
-.BR domain \-Sektion
-werden Domain\-spezifische Einstellungen hinterlegt.
-.PP
-Das Quota\-Limit (quota_bytes und quota_messages), Service\-Einstellungen
-(imap,  pop3, sieve und smtp) und der Transport werden angewendet, wenn
-eine Domain angelegt wird.
-Um die Einstellungen einer vorhandenen Domain zu ändern, verwenden Sie
-einen der folgenden
-.BR vmm (1)
-Unterbefehle:
-.PP
-.TP
-.B domainquota
-um das Quota\-Limit einer Domain zu ändern
-.TP
-.B domainservices
-um einer Domain einen abweichenden Satz von nutzbaren Services zuzuweisen
-.TP
-.B domaintransport
-um einen neuen Vorgabe\-Transport für eine Domain festzulegen
-.PP
-Wenn ein Account angelegt wird, erbt er alle Einstellungen von der Domain,
-zu der er hinzugefügt wird.
-Abweichende Einstellungen für einen vorhandenen Account nehmen Sie mit
-einem der Unterbefehle
-.BR userquota ", " userservices " und " usertransport
-vor.
-.\" ------------------------------------
-.SS domain.auto_postmaster
-.BR auto_postmaster " (Vorgabe: true) :"
-.I Boolean
-.PP
-Ist der Wert dieser Option
-.IR true ,
-wird
-.BR vmm (1)
-beim Anlegen einer Domain (domainadd) automatisch einen postmaster\-Account
-erstellen.
-.\" ------------------------------------
-.SS domain.delete_directory
-.BR delete_directory " (Vorgabe: false) :"
-.I Boolean
-.PP
-Legt fest, ob beim Löschen einer Domain (domaindelete) das Verzeichnis der
-zu löschenden Domain, inklusive aller Anwender\-Verzeichnisse, rekursiv
-gelöscht werden soll.
-.\" ------------------------------------
-.SS domain.directory_mode
-.BR directory_mode " (Vorgabe: 504) :"
-.I Int
-.PP
-Zugriffsbits des Domain\-Verzeichnisses in Dezimal\-Schreibweise
-(Basis 10).
-.br
-Beispiel: 'drwxrwx\-\-\-' \(-> oktal 0770 \(-> dezimal 504
-.\" ------------------------------------
-.SS domain.force_deletion
-.BR force_deletion " (Vorgabe: false) :"
-.I Boolean
-.PP
-Erzwingt das Löschen aller zugeordneten Konten und Aliase beim Löschen
-einer Domain (domaindelete).
-.\" ------------------------------------
-.SS domain.imap
-.BR imap " (Vorgabe: true) :"
-.I Boolean
-.PP
-Legt fest, ob sich neu angelegte Benutzer per IMAP anmelden können sollen.
-.\" ------------------------------------
-.SS domain.pop3
-.BR pop3 " (Vorgabe: true) :"
-.I Boolean
-.PP
-Legt fest, ob sich neu angelegte Benutzer per POP3 anmelden können sollen.
-.\" ------------------------------------
-.SS domain.quota_bytes
-.BR quota_bytes " (Vorgabe: 0) :"
-.I String
-.PP
-Quota Limit in Bytes.
-0 bedeutet unbegrenzt.
-Dieses Limit wird beim Anlegen von Domains angewendet.
-.PP
-Der Wert dieser Option kann als Integer\-Wert, zum Beispiel
-.B 20480
-geschrieben werden.
-Es ist auch möglich dem Wert eines der folgenden
-Suffixe anzuhängen:
-.BR b " (Bytes), " k " (Kilobytes), " M " (Megabytes) oder " G
-(Gigabytes).
-.br
-1024 entspricht 1024b oder 1k.
-.\" ------------------------------------
-.SS domain.quota_messages
-.BR quota_messages " (Vorgabe: 0) :"
-.I Int
-.PP
-Quota Limit als Anzahl von Nachrichten.
-0 bedeutet unbegrenzt.
-Dieses Limit wird beim Anlegen neuer Domains angewendet.
-.\" ------------------------------------
-.SS domain.sieve
-.BR sieve " (Vorgabe: true) :"
-.I Boolean
-.PP
-Legt fest, ob sich neu angelegte Benutzer per SIEVE (ManageSieve) anmelden
-können sollen.
-.\" ------------------------------------
-.SS domain.smtp
-.BR smtp " (Vorgabe: true) :"
-.I Boolean
-.PP
-Legt fest, ob sich neu angelegte Benutzer per SMTP (SMTP AUTH) anmelden
-können sollen.
-.\" ------------------------------------
-.SS domain.transport
-.BR transport " (Vorgabe: dovecot:) :"
-.I String
-.PP
-Der Standard\-Transport aller neuen Domains.
-Siehe auch:
-.BR transport (5).
-.\" -----------------------------------------------------------------------
-.SH SEKTION MAILBOX
-In der
-.BR mailbox \-Sektion
-werden die für die Erstellung von Mailboxen erforderlichen Optionen
-festgelegt.
-Die INBOX wird in jedem Fall erstellt.
-.SS mailbox.folders
-.BR folders " (Vorgabe: Drafts:Sent:Templates:Trash) :"
-.I String
-.PP
-Eine durch Doppelpunkte getrennte Liste mit Namen der zu erstellenden
-Mailboxen.
-Sollen keine zusätzlichen Mailboxen angelegt werden, ist dieser Option ein
-einzelner Doppelpunkt
-.RB (' : ')
-als Wert zuzuweisen.
-.PP
-Sollen Verzeichnisse mit Unterverzeichnissen angelegt werden, ist ein
-einzelner Punkt
-.RB (' . ')
-als Separator zu verwenden.
-.PP
-Sollen Mailboxen mit internationalisierten Namen erstellt werden (zum
-Beispiel: 'Wysłane' oder 'Gelöschte Objekte'), ist der Name UTF\-8 kodiert
-anzugeben.
-.BR vmm (1)
-wird die internationalisierten Mailboxnamen in eine modifizierten Variante
-des UTF\-7\-Zeichensatzes (siehe auch: RFC 3501, Sektion 5.1.3)
-konvertieren.
-.\" ------------------------------------
-.SS mailbox.format
-.BR format " (Vorgabe: maildir) :"
-.I String
-.PP
-Das zu verwendende Mailbox\-Format für die Mailboxen der Benutzer.
-Abhängig von der verwendeten Dovecot\-Version
-.RI ( misc.dovecot_version ),
-unterstützt
-.BR vmm (1)
-bis zu drei Formate:
-.TP 8
-.B maildir
-Dovecot \(>= v1.0.0
-.TP
-.B mdbox
-Dovecot \(>= v2.0.beta5
-.TP
-.B sdbox
-Dovecot \(>= v2.0.rc3
-.\" ------------------------------------
-.SS mailbox.root
-.BR root " (Vorgabe: Maildir) :"
-.I String
-.PP
-Name des Mailbox\-Wurzelverzeichnisses im Home\-Verzeichnis des jeweiligen
-Benutzers.
-Übliche Namen, je nach verwendetem
-.IR mailbox.format ,
-sind
-.BR Maildir ", " mdbox " or " sdbox .
-.\" ------------------------------------
-.SS mailbox.subscribe
-.BR subscribe " (Vorgabe: true) :"
-.I Boolean
-.PP
-Wenn dieser Option der Wert
-.B true
-zugewiesen wurde, werden die, gemäß
-.IR mailbox.folders ,
-erstellen Mailboxen in der subscriptions\-Datei des Benutzers gelistet.
-Sollen die erstellen Mailboxen nicht nicht in der subscriptions\-Datei
-gelistet werden, weisen Sie dieser Option den Wert
-.B false
-zu.
-.\" -----------------------------------------------------------------------
-.SH SEKTION MISC
-In der
-.BR misc \-Sektion
-werden Einstellungen für verschiedene Bereiche festgelegt.
-.SS misc.base_directory
-.BR base_directory " (Vorgabe: /srv/mail) :"
-.I String
-.PP
-Alle Domain\-Verzeichnisse werden innerhalb dieses Basis\-Verzeichnisses
-angelegt.
-.\" ------------------------------------
-.SS misc.crypt_blowfish_rounds
-.BR crypt_blowfish_rounds " (Vorgabe: 5) :"
-.I Int
-.PP
-Anzahl der Verschlüsselungsdurchgänge für das
-.I password_scheme
-.BR BLF\-CRYPT .
-.PP
-Der Wert muss im Bereich von
-.BR 4 " \- " 31
-liegen.
-.\" ------------------------------------
-.SS misc.crypt_sha256_rounds
-.BR crypt_sha256_rounds " (Vorgabe: 5000) :"
-.I Int
-.PP
-Anzahl der Verschlüsselungdurchgänge für das
-.I password_scheme
-.BR SHA256\-CRYPT .
-.PP
-Der Wert muss im Bereich von
-.BR 1000 " \- " 999999999
-liegen.
-.\" ------------------------------------
-.SS misc.crypt_sha512_rounds
-.BR crypt_sha512_rounds " (Vorgabe: 5000) :"
-.I Int
-.PP
-Anzahl der Verschlüsselungdurchgänge für das
-.I password_scheme
-.BR SHA512\-CRYPT .
-.PP
-Der Wert muss im Bereich von
-.BR 1000 " \- " 999999999
-liegen.
-.\" ------------------------------------
-.SS misc.dovecot_version
-.BR dovecot_version " (Vorgabe: " None ") :"
-.I String
-.PP
-Die aktuell eingesetzte Dovecot\-Version.
-(siehe:
-.BR "dovecot \-\-version" ).
-
-Wenn das Kommando
-.B dovecot \-\-version
-zum Beispiel
-.I 2.0.beta4 (8818db00d347)
-ausgibt, ist dieser Option der Wert
-.B 2.0.beta4
-zuzuweisen.
-.\" ------------------------------------
-.SS misc.password_scheme
-.BR password_scheme " (Vorgabe: CRAM\-MD5) :"
-.I String
-.PP
-Das zu verwendende Passwort\-Schema.
-Um eine Liste aller verwendbaren Passwort\-Schemen zu erhalten, führen Sie
-das Kommando
-.B vmm lp
-aus.
-.PP
-Seit Dovecot \(>= v1.1.alpha1 ist es möglich, dem
-.I password_scheme
-ein Encoding\-Suffix anzufügen.
-Unterstützte Encoding\-Suffixe:
-.BR .b64 ", " .base64 " und " .hex .
-Beispiel: PLAIN.BASE64
-.\" -----------------------------------------------------------------------
-.SH BEISPIEL
-Eine Beispiel\-Konfiguration.
-Alle Optionen, die nicht in der Konfigurationsdatei gelistet sind, haben
-ihren Vorgabewert.
-.PP
-.nf
-[account]
-password_length = 10
-random_password = true
-
-[bin]
-dovecotpw = /usr/bin/doveadm
-
-[database]
-host = dbsrv8.example.net
-pass = PY_SRJ}L/0p\-oOk
-port = 5433
-sslmode = require
-user = vmm
-
-[domain]
-quota_bytes = 500M
-quota_messages = 10000
-transport = lmtp:unix:private/dovecot\-lmtp
-
-[mailbox]
-folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
-
-[misc]
-crypt_sha512_rounds = 10000
-dovecot_version = 2.0.beta4
-password_scheme = SHA512\-CRYPT.hex
-.fi
-.\" -----------------------------------------------------------------------
-.SH SIEHE AUCH
-.BR postconf (1),
-.BR vmm (1),
-.BR transport (5)
-.\" -----------------------------------------------------------------------
-.SH INTERNET RESSOURCEN
-.TP
-Homepage
-http://vmm.localdomain.org/
-.TP
-Projekt\-Seite
-http://sf.net/projects/vmm/
-.TP
-Bugtracker
-https://bitbucket.org/pvo/vmm/issues
-.\" -----------------------------------------------------------------------
-.SH COPYING
-vmm und die dazugehörigen Manualseiten wurden von Pascal Volk <user+vmm AT
-localhost.localdomain.org> geschrieben und sind unter den Bedingungen der
-BSD Lizenz lizenziert.
\ No newline at end of file
--- a/man/man1/vmm.1	Sun Mar 09 18:42:58 2014 +0000
+++ b/man/man1/vmm.1	Sun Mar 09 18:52:27 2014 +0000
@@ -1,10 +1,18 @@
-.TH "VMM" "1" "2012-09-27" "vmm 0.6" "vmm"
+.TH "VMM" "1" "2014-02-23" "vmm 0.7" "vmm"
 .SH NAME
 vmm \- command line tool to manage email domains/accounts/aliases
 .\" -----------------------------------------------------------------------
 .SH SYNOPSIS
 .B vmm
-.IR subcommand " [" "argument ..." ]
+.BR   \-h | \-v | \-\-help | \-\-version
+.\" --------------------------
+.br
+.BI vmm " subcommand " \-h \c
+.RB | \-\-help
+.\" --------------------------
+.br
+.BI vmm " subcommand"
+.IR arguments " [" options ]
 .\" -----------------------------------------------------------------------
 .SH DESCRIPTION
 .B vmm
@@ -26,6 +34,19 @@
 take one or more
 .IR argument s.
 .\" -----------------------------------------------------------------------
+.SH OPTIONS
+The following options are recognized by
+.BR vmm .
+.TP
+.BR \-h ", " \-\-help
+show a list of available subcommands and exit.
+.\" --------------------------
+.TP
+.BR \-v ", " \-\-version
+show
+.BR vmm \(aqs
+version and copyright information and exit.
+.\" -----------------------------------------------------------------------
 .SH ARGUMENTS
 .TP 12
 .I address
@@ -136,7 +157,8 @@
 .\" ------------------------------------
 .SS configure (cf)
 .B vmm configure
-.RI [ section ]
+.RB [ \-s
+.IR section ]
 .PP
 Starts the interactive configuration for all configuration sections.
 .PP
@@ -184,7 +206,7 @@
 Example:
 .PP
 .nf
-.B vmm configure mailbox
+.B vmm configure \-s mailbox
 Using configuration file: /usr/local/etc/vmm.cfg
 
 * Configuration section: `mailbox'
@@ -214,23 +236,10 @@
         Address........: a.user@example.com
 .fi
 .\" ------------------------------------
-.SS help (h)
-.B vmm help
-.RI [ subcommand ]
-.PP
-Prints a list of available subcommands with a short description to stdout.
-When a
-.I subcommand
-was given, help for that
-.I subcommand
-will be displayed.
-After this
-.B vmm
-exits.
-.\" ------------------------------------
 .SS listdomains (ld)
 .B vmm listdomains
-.RI [ pattern ]
+.RB [ \-p
+.IR pattern ]
 .PP
 This subcommand lists all available domains.
 All domain names will be prefixed either with `[+]', if the domain is a
@@ -245,7 +254,7 @@
 Example:
 .PP
 .nf
-.B vmm listdomains %example%
+.B vmm listdomains \-p %example%
 Matching domains
 ----------------
         [+] example.com
@@ -257,7 +266,8 @@
 .\" ------------------------------------
 .SS listaddresses (ll)
 .B vmm listaddresses
-.RI [ pattern ]
+.RB [ \-p
+.IR pattern ]
 .PP
 This command lists all defined addresses. Addresses belonging to
 alias-domains are prefixed with a '-', addresses of regular domains with
@@ -273,12 +283,13 @@
 Example:
 .PP
 .nf
-.B vmm listaddresses example.com
-.B vmm listaddresses %master@%
+.B vmm listaddresses \-p example.com
+.B vmm listaddresses \-p %master@%
 .\" ------------------------------------
 .SS listaliases (la)
 .B vmm listaliases
-.RI [ pattern ]
+.RB [ \-p
+.IR pattern ]
 .PP
 This command lists all defined aliases. Aliases belonging to
 alias-domains are prefixed with a '-', addresses of regular domains with
@@ -293,12 +304,13 @@
 Example:
 .PP
 .nf
-.B vmm listaliases example.com
-.B vmm listaliases %master@%
+.B vmm listaliases \-p example.com
+.B vmm listaliases \-p %master@%
 .\" ------------------------------------
 .SS listrelocated (lr)
 .B vmm listrelocated
-.RI [ pattern ]
+.RB [ \-p
+.IR pattern ]
 .PP
 This command lists all defined relocated addresses. Relocated entries
 belonging to alias-domains are prefixed with a '-', addresses of regular
@@ -314,12 +326,13 @@
 Example:
 .PP
 .nf
-.B vmm listrelocated example.com
-.B vmm listrelocated %master@%
+.B vmm listrelocated \-p example.com
+.B vmm listrelocated \-p %master@%
 .\" ------------------------------------
 .SS listusers (lu)
 .B vmm listusers
-.RI [ pattern ]
+.RB [ \-p
+.IR pattern ]
 .PP
 This command lists all user accounts. User accounts belonging to
 alias-domains are prefixed with a '-', addresses of regular
@@ -335,8 +348,8 @@
 Example:
 .PP
 .nf
-.B vmm listusers example.com
-.B vmm listusers %master@%
+.B vmm listusers \-p example.com
+.B vmm listusers \-p %master@%
 .\" ------------------------------------
 .SS listpwschemes (lp)
 .B vmm listpwschemes
@@ -349,8 +362,7 @@
 The output varies, depending on the used Dovecot version and the system's
 libc.
 .br
-When your Dovecot installation isn't too old, you will see additionally a
-few usable encoding suffixes.
+Additionally a few usable encoding suffixes will be displayed.
 One of them can be appended to the password scheme.
 .PP
 Example:
@@ -359,30 +371,33 @@
 .B vmm listpwschemes
 Usable password schemes
 -----------------------
-        CRYPT SHA512-CRYPT LDAP-MD5 DIGEST-MD5 SHA256 SHA512 SSHA512
-        SKEY SSHA NTLM RPA MD5-CRYPT HMAC-MD5 SHA1 PLAIN SHA CRAM-MD5
-        SSHA256 MD5 LANMAN CLEARTEXT PLAIN-MD5 PLAIN-MD4 OTP SMD5
-        SHA256-CRYPT
+        CLEARTEXT CRAM-MD5 CRYPT DIGEST-MD5 HMAC-MD5 LANMAN LDAP-MD5 MD5
+        MD5-CRYPT NTLM OTP PLAIN PLAIN-MD4 PLAIN-MD5 RPA SHA SHA1 SHA256
+        SHA256-CRYPT SHA512 SHA512-CRYPT SKEY SMD5 SSHA SSHA256 SSHA512
 
 Usable encoding suffixes
 ------------------------
         .B64 .BASE64 .HEX
 .fi
-.\" ------------------------------------
-.SS version (v)
-.B vmm version
-.PP
-Prints
-.BR vmm 's
-version and copyright information to stdout.
-After this
-.B vmm
-exits.
 .\" -----------------------------------------------------------------------
 .SH DOMAIN SUBCOMMANDS
 .SS domainadd (da)
 .B vmm domainadd
-.IR fqdn " [" transport ]
+.I fqdn
+.RB [ \-n
+.IR note ]
+.RB [ \-t
+.IR transport ]
+.PP
+.\" ------------------------------------
+.TP
+.BI "\-n " note
+the note that should be set.
+.\" ------------------------------------
+.TP
+.BI "\-t " transport
+a Postfix transport (transport: or transport:nexthop).
+.\" ------------------------------------
 .PP
 Adds the new domain into the database and creates the domain directory.
 .PP
@@ -415,7 +430,7 @@
 Examples:
 .PP
 .nf
-.B vmm domainadd support.example.com smtp:[mx1.example.com]:2025
+.B vmm domainadd support.example.com \-t smtp:[mx1.example.com]:2025
 Creating account for postmaster@support.example.com
 Enter new password:
 Retype new password:
@@ -427,7 +442,7 @@
 .\" ------------------------------------
 .SS domaindelete (dd)
 .BI "vmm domaindelete " fqdn
-.RB [ force ]
+.RB [ \-\-force ]
 .PP
 This subcommand deletes the domain specified by
 .IR fqdn .
@@ -436,8 +451,8 @@
 domain,
 .B vmm
 will abort the requested operation and show an error message.
-If you know, what you are doing, you can specify the optional keyword
-.BR force .
+If you know, what you are doing, you can specify the optional argument
+.BR \-\-force .
 .PP
 If you really always know what you are doing, edit your
 .I vmm.cfg
@@ -448,7 +463,9 @@
 .\" ------------------------------------
 .SS domaininfo (di)
 .B vmm domaininfo
-.IR fqdn \ [ details ]
+.I fqdn
+.RB [ \-d
+.IR details ]
 .PP
 This subcommand shows some information about the given domain.
 .PP
@@ -500,8 +517,10 @@
 .\" ------------------------------------
 .SS domainquota (dq)
 .B vmm domainquota
-.IR "fqdn storage" " [" messages ]
-.RB [ force ]
+.I fqdn storage
+.RB [ \-m
+.IR messages ]
+.RB [ --force ]
 .PP
 This subcommand is used to configure a new quota limit for the accounts of
 the domain - not for the domain itself.
@@ -511,8 +530,8 @@
 .PP
 The new quota limit will affect only those accounts for which the limit
 has not been overridden. If you want to restore the default to all accounts,
-you may pass the keyword
-.BR force .
+you may pass the optional argument
+.BR \-\-force .
 .br
 When the argument
 .I messages
@@ -523,13 +542,15 @@
 Example:
 .PP
 .nf
-.B vmm domainquota example.com 1g force
+.B vmm domainquota example.com 1g \-\-force
 .fi
 .\" ------------------------------------
 .SS domainservices (ds)
 .B vmm domainservices
-.IR fqdn " [" "service ..." ]
-.RB [ force ]
+.I fqdn
+.RB [ \-s
+.IR "service ..." ]
+.RB [ \-\-force ]
 .PP
 To define which services could be used by the users of the domain \(em with
 the given
@@ -540,24 +561,24 @@
 .I service
 will be enabled/usable.
 All other services will be deactivated/unusable.
-Possible service names are: 
+Possible service names are:
 .BR  imap ", " pop3 ", " sieve " and " smtp .
 .br
 The new service set will affect only those accounts for which the set has not
 been overridden. If you want to restore the default to all accounts, you may
-pass the keyword
-.BR force .
+pass the option
+.BR \-\-force .
 .\" ------------------------------------
 .SS domaintransport (dt)
 .BI "vmm domaintransport" " fqdn transport"
-.RB [ force ]
+.RB [ --force ]
 .PP
 A new transport for the indicated domain can be set with this subcommand.
 .PP
 The new transport will affect only those accounts for which the transport has
 not been overridden. If you want to restore the default to all accounts, you
-may pass the keyword
-.BR force .
+may give the option
+.BR \-\-force .
 .PP
 Example:
 .PP
@@ -567,15 +588,28 @@
 .\" ------------------------------------
 .SS domainnote (do)
 .BI "vmm domainnote" " fqdn"
-.RI [ note ]
+.BR \-d | \-n
+.I note
+.PP
+.\" ------------------------------------
+.TP
+.B \-d
+delete the domain\(aqs note.
+.\" ------------------------------------
+.TP
+.BI "\-n " note
+the note that should be set.
+.\" ------------------------------------
 .PP
 With this subcommand, it is possible to attach a note to the specified
-domain. Without an argument, an existing note is removed.
+domain.
+In order to delete an existing note, pass the
+.BR \-d " option."
 .PP
 Example:
 .PP
 .nf
-.B vmm do example.com Belongs to Robert
+.B vmm do example.com \-n `Belongs to Robert'
 .fi
 .\" -----------------------------------------------------------------------
 .SH ALIAS DOMAIN SUBCOMMANDS
@@ -661,7 +695,21 @@
 .SH ACCOUNT SUBCOMMANDS
 .SS useradd (ua)
 .B vmm useradd
-.IR address " [" password ]
+.I address
+.RB [ \-n
+.IR note ]
+.RB [ \-p
+.IR password ]
+.PP
+.\" ------------------------------------
+.TP
+.BI "\-n " note
+the note that should be set.
+.\" ------------------------------------
+.TP
+.BI "\-p " password
+the new user\(aqs password.
+.\" ------------------------------------
 .PP
 Use this subcommand to create a new e\-mail account for the given
 .IR address .
@@ -683,7 +731,7 @@
 Examples:
 .PP
 .nf
-.B vmm ua d.user@example.com \(dqA 5ecR3t P4s5\(rs/\(rs/0rd\(dq
+.B vmm ua d.user@example.com \-p \(dqA 5ecR3t P4s5\(rs/\(rs/0rd\(dq
 .B vmm useradd e.user@example.com
 Enter new password:
 Retype new password:
@@ -691,7 +739,7 @@
 .\" ------------------------------------
 .SS userdelete (ud)
 .BI "vmm userdelete" " address"
-.RB [ force ]
+.RB [ \-\-force ]
 .PP
 Use this subcommand to delete the account with the given
 .IR address .
@@ -699,12 +747,14 @@
 If there are one or more aliases with an identical destination address,
 .B vmm
 will abort the requested operation and show an error message.
-To prevent this, specify the optional keyword
-.BR force .
+To prevent this, give the optional argument
+.BR \-\-force .
 .\" ------------------------------------
 .SS userinfo (ui)
 .B "vmm userinfo"
-.IR address " [" details ]
+.I address
+.RB [ \-d
+.IR details ]
 .PP
 This subcommand displays some information about the account specified by
 .IR address .
@@ -758,56 +808,116 @@
 .\" ------------------------------------
 .SS username (un)
 .BI "vmm username" " address"
-.RI [ name ]
+.BR \-d | \-n
+.IR name
+.PP
+.\" ------------------------------------
+.TP
+.B \-d
+delete the user\(aqs name.
+.\" ------------------------------------
+.TP
+.BI "\-n " name
+a user\(aqs real name.
+.\" ------------------------------------
 .PP
 The user's real
 .I name
 can be set/updated with this subcommand.
 .PP
-If no
-.I name
-is given, the value stored for the account is erased.
+In order to delete the value stored for the account, pass the
+.BR \-d " option."
 .PP
 Example:
 .PP
 .nf
-.B vmm un d.user@example.com \(dqJohn Doe\(dq
+.B vmm un d.user@example.com \-n \(dqJohn Doe\(dq
 .fi
 .\" ------------------------------------
 .SS userpassword (up)
 .BI "vmm userpassword" " address"
-.RI [ password ]
+.RB ([ \-p
+.IR password ]
+.RB [ \-s
+.IR scheme "] |"
+.B \-\-hash
+.IR pwhash ])
+.PP
+.TP
+.BI "\-p " password
+The user\(aqs new password.
+.\" ------------------------------------
+.TP
+.BI "\-s " scheme
+When a
+.I scheme
+was specified, it overrides the
+.I misc.password_scheme
+setting, configured in the
+.I vmm.cfg
+file.
+.\" ------------------------------------
+.TP
+.BI "\-\-hash " pwhash
+A hashed password, prefixed with
+.BI { SCHEME };
+as generated by
+.BR "doveadm pw" .
+You should enclose the hashed password in single quotes, if it contains
+one ore more dollar signs
+.RB ( $ ).
+.\" ------------------------------------
 .PP
 The password of an account can be updated with this subcommand.
 .PP
 If no
-.I password
+.IR password " or " pwhash
 was provided,
 .B vmm
-will prompt for it interactively.
+will prompt for a password interactively.
+.IP Note:
+When passing a hashed password,
+.B vmm
+checks only if the included
+.I SCHEME
+is supported by your Dovecot installation.  No further checks are done.
 .PP
 Example:
 .PP
 .nf
-.B vmm up d.user@example.com \(dqA |\(rs/|0r3 5ecur3 P4s5\(rs/\(rs/0rd?\(dq
+.B vmm up d.user@example.com \-p \(dqA |\(rs/|0r3 5ecur3 P4s5\(rs/\(rs/0rd?\(dq
 .fi
 .\" ------------------------------------
 .SS usernote (uo)
 .BI "vmm usernote" " address"
-.RI [ note ]
+.BR \-d | \-n
+.IR note
+.PP
+.\" ------------------------------------
+.TP
+.B \-d
+delete the user\(aqs note.
+.\" ------------------------------------
+.TP
+.BI "\-n " note
+the note that should be set.
+.\" ------------------------------------
 .PP
 With this subcommand, it is possible to attach a note to the specified
-account. Without an argument, an existing note is removed.
+account.
+In order to delete an existing note, pass the
+.BR \-d " option."
 .PP
 Example:
 .PP
 .nf
-.B vmm uo d.user@example.com Only needed until end of May 2012
+.B vmm uo d.user@example.com -n `Only needed until end of May 2013'
 .fi
 .\" ------------------------------------
 .SS userquota (uq)
 .BI "vmm userquota" " address storage"
-.RI [ messages ]
+.RB [ \-m
+.IR messages ]
 .PP
 This subcommand is used to set a new quota limit for the given account.
 .PP
@@ -819,7 +929,7 @@
 .PP
 Instead of
 .I storage
-pass the keyword
+limit pass the keyword
 .B domain
 to remove the account\-specific override, causing the domain's value to be
 in effect.
@@ -832,7 +942,9 @@
 .\" ------------------------------------
 .SS userservices (us)
 .B vmm userservices
-.IR address " [" "service ..." ]
+.I address
+.RB [ \-s
+.IR "service ..." ]
 .PP
 To grant a user access to the specified services, use this command.
 .PP
@@ -840,15 +952,17 @@
 given
 .IR address .
 .PP
-Instead of
+Instead of any
 .I service
-pass 'domain' to remove the account\-specific override, causing the
-domain's value to be in effect.
+pass the keyword
+.B domain
+to remove the account\-specific override, causing the domain's value to be
+in effect.
 .PP
 Example:
 .PP
 .nf
-.B vmm userservices d.user@example.com SMTP IMAP
+.B vmm userservices d.user@example.com \-s smtp imap
 .\" ------------------------------------
 .SS usertransport (ut)
 .BI "vmm usertransport" " address transport"
@@ -1013,7 +1127,7 @@
 .B vmm catchallinfo example.com
 Catch-all information
 ---------------------
-  Mail to unknown localparts in domain example.com will be sent to:
+  Mail to unknown local\-parts in domain example.com will be sent to:
          * user@example.org
 .fi
 .\" ------------------------------------
@@ -1044,6 +1158,7 @@
 will be used when none of the both above mentioned files exists.
 .\" -----------------------------------------------------------------------
 .SH SEE ALSO
+.BR doveadm\-pw (1),
 .BR dsync (1),
 .BR transport (5),
 .BR vmm.cfg (5)
--- a/man/man5/vmm.cfg.5	Sun Mar 09 18:42:58 2014 +0000
+++ b/man/man5/vmm.cfg.5	Sun Mar 09 18:52:27 2014 +0000
@@ -1,4 +1,4 @@
-.TH "VMM.CFG" "5" "2014-02-17" "vmm 0.6" "vmm"
+.TH "VMM.CFG" "5" "2014-02-17" "vmm 0.7" "vmm"
 .SH NAME
 vmm.cfg \- configuration file for vmm
 .\" -----------------------------------------------------------------------
@@ -68,7 +68,7 @@
 pass = xxxxxxxx
 
 [misc]
-dovecot_version = 1.2.16
+dovecot_version = 2.2.11
 .fi
 .\" -----------------------------------------------------------------------
 .SH SEARCH ORDER
@@ -165,32 +165,21 @@
 .B bin
 section is used to specify some paths to some binaries required by
 .BR vmm (1).
-.SS bin.dovecotpw
-.BR dovecotpw " (default: /usr/sbin/dovecotpw) :"
+.SS bin.doveadm
+.BR doveadm " (default: /usr/bin/doveadm) :"
 .I String
 .PP
 The absolute path to the
-.BR dovecotpw (1)
+.BR doveadm (1)
 binary.
-Use the absolute path to the
-.BR doveadm (1)
-binary, if you are using Dovecot v2.0.
 .PP
 This binary is used to generate a password hash, if
 .I misc.password_scheme
-is set to one of `CRAM\-MD5', `HMAC\-MD5', `LANMAN', `OTP', `RPA' or
-`SKEY'.
+is set to one of `CRAM\-MD5', `HMAC\-MD5', `LANMAN', `OTP', `RPA', 
+`SCRAM-SHA-1' or `SKEY'.
 This binary will be also required if your Python installation doesn't
-support the:
-.IP \(bu 4
-md4 hash algorithm (hashlib + OpenSSL or PyCrypto) used for the password
-schemes: `PLAIN\-MD4' and `NTLM'
-.IP \(bu
-sha256 hash algorithm (hashlib or PyCrypto \(>= 2.1.0alpha1) used for the
-password schemes: `SHA256' and `SSHA256'
-.IP \(bu
-sha512 hash algorithm (hashlib) used for the password schemes: `SHA512' and
-`SSHA512'
+support the md4 hash algorithm (hashlib + OpenSSL) used for the password
+schemes `PLAIN\-MD4' and `NTLM'.
 .PP
 The
 .BR doveadm (1)
@@ -231,14 +220,6 @@
 .PP
 Hostname or IP address of the database server.
 .\" ------------------------------------
-.SS database.module
-.BR module " (default: psycopg2) :"
-.I String
-.PP
-The Python PostgreSQL database adapter module to be used.
-Supported modules are
-.BR psycopg2 " and " pyPgSQL .
-.\" ------------------------------------
 .SS database.name
 .BR name " (default: mailsys) :"
 .I String
@@ -269,11 +250,6 @@
 The modes
 .BR verify\-ca " and " verify\-full
 are available since PostgreSQL 8.4
-.PP
-This setting will be ignored when the
-.I database.module
-is set to
-.BR pyPgSQL .
 .\" ------------------------------------
 .SS database.user
 .BR user " (default: " None ") :"
@@ -389,10 +365,10 @@
 Determines whether newly created users can log in via SMTP (SMTP AUTH).
 .\" ------------------------------------
 .SS domain.transport
-.BR transport " (default: dovecot:) :"
+.BR transport " (default: lmtp:unix:private/dovecot\-lmtp) :"
 .I String
 .PP
-Default transport for domains and accounts.
+Default transport for new domains.
 For details see
 .BR transport (5).
 .\" -----------------------------------------------------------------------
@@ -553,9 +529,6 @@
 password_length = 10
 random_password = true
 
-[bin]
-dovecotpw = /usr/bin/doveadm
-
 [database]
 host = dbsrv8.example.net
 pass = PY_SRJ}L/0p\-oOk
@@ -566,14 +539,13 @@
 [domain]
 quota_bytes = 500M
 quota_messages = 10000
-transport = lmtp:unix:private/dovecot\-lmtp
 
 [mailbox]
 folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
 
 [misc]
 crypt_sha512_rounds = 10000
-dovecot_version = 2.0.beta4
+dovecot_version = 2.2.11
 password_scheme = SHA512\-CRYPT.hex
 .fi
 .\" -----------------------------------------------------------------------
--- a/pgsql/create_tables-dovecot-1.2.x.pgsql	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,770 +0,0 @@
-SET client_encoding = 'UTF8';
-SET client_min_messages = warning;
-
-
-CREATE SEQUENCE transport_id;
-
-CREATE SEQUENCE mailboxformat_id;
-
-CREATE SEQUENCE maillocation_id;
-
-CREATE SEQUENCE quotalimit_id;
-
-CREATE SEQUENCE service_set_id;
-
-CREATE SEQUENCE domain_gid
-    START WITH 70000
-    INCREMENT BY 1
-    MINVALUE 70000
-    MAXVALUE 4294967294
-    NO CYCLE;
-
-CREATE SEQUENCE users_uid
-    START WITH 70000
-    INCREMENT BY 1
-    MINVALUE 70000
-    MAXVALUE 4294967294
-    NO CYCLE;
-
-
-CREATE TABLE transport (
-    tid         bigint NOT NULL DEFAULT nextval('transport_id'),
-    transport   varchar(270) NOT NULL, -- smtps:[255-char.host.name:50025]
-    CONSTRAINT  pkey_transport PRIMARY KEY (tid),
-    CONSTRAINT  ukey_transport UNIQUE (transport)
-);
--- Insert default transport
-INSERT INTO transport(transport) VALUES ('dovecot:');
-
-CREATE TABLE mailboxformat (
-    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
-    format      varchar(20) NOT NULL,
-    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
-    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
-);
--- Insert supported mailbox formats
-INSERT INTO mailboxformat(format) VALUES ('maildir');
-INSERT INTO mailboxformat(format) VALUES ('mdbox');
-INSERT INTO mailboxformat(format) VALUES ('sdbox');
-
-CREATE TABLE maillocation (
-    mid         bigint NOT NULL DEFAULT nextval('maillocation_id'),
-    fid         bigint NOT NULL DEFAULT 1,
-    directory   varchar(20) NOT NULL,
-    extra       varchar(1024),
-    CONSTRAINT  pkey_maillocation PRIMARY KEY (mid),
-    CONSTRAINT  fkey_maillocation_fid_mailboxformat FOREIGN KEY (fid)
-        REFERENCES mailboxformat (fid)
-);
--- Insert default Maildir-folder name
-INSERT INTO maillocation(directory) VALUES ('Maildir');
-
-CREATE TABLE quotalimit (
-    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
-    bytes       bigint NOT NULL,
-    messages    integer NOT NULL DEFAULT 0,
-    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
-    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
-);
--- Insert default (non) quota limit
-INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
-
-CREATE TABLE service_set (
-    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
-    smtp        boolean NOT NULL DEFAULT TRUE,
-    pop3        boolean NOT NULL DEFAULT TRUE,
-    imap        boolean NOT NULL DEFAULT TRUE,
-    sieve       boolean NOT NULL DEFAULT TRUE,
-    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
-    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, sieve)
-);
--- Insert all possible service combinations
-COPY service_set (smtp, pop3, imap, sieve) FROM stdin;
-TRUE	TRUE	TRUE	TRUE
-FALSE	TRUE	TRUE	TRUE
-TRUE	FALSE	TRUE	TRUE
-FALSE	FALSE	TRUE	TRUE
-TRUE	TRUE	FALSE	TRUE
-FALSE	TRUE	FALSE	TRUE
-TRUE	FALSE	FALSE	TRUE
-FALSE	FALSE	FALSE	TRUE
-TRUE	TRUE	TRUE	FALSE
-FALSE	TRUE	TRUE	FALSE
-TRUE	FALSE	TRUE	FALSE
-FALSE	FALSE	TRUE	FALSE
-TRUE	TRUE	FALSE	FALSE
-FALSE	TRUE	FALSE	FALSE
-TRUE	FALSE	FALSE	FALSE
-FALSE	FALSE	FALSE	FALSE
-\.
-
-CREATE TABLE domain_data (
-    gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
-    qid         bigint NOT NULL DEFAULT 1, -- default quota limit
-    ssid        bigint NOT NULL DEFAULT 1, -- default service set
-    tid         bigint NOT NULL DEFAULT 1, -- default transport
-    domaindir   varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
-    note        text NULL DEFAULT NULL,
-    CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
-    CONSTRAINT  fkey_domain_data_qid_quotalimit FOREIGN KEY (qid)
-        REFERENCES quotalimit (qid),
-    CONSTRAINT  fkey_domain_data_ssid_service_set FOREIGN KEY (ssid)
-        REFERENCES service_set (ssid),
-    CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
-);
-
-CREATE TABLE domain_name (
-    domainname  varchar(255) NOT NULL,
-    gid         bigint NOT NULL,
-    is_primary  boolean NOT NULL,
-    CONSTRAINT  pkey_domain_name PRIMARY KEY (domainname),
-    CONSTRAINT  fkey_domain_name_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
-CREATE TABLE users (
-    local_part  varchar(64) NOT NULL,-- only localpart w/o '@'
-    passwd      varchar(270) NOT NULL,
-    name        varchar(128) NULL,
-    uid         bigint NOT NULL DEFAULT nextval('users_uid'),
-    gid         bigint NOT NULL,
-    mid         bigint NOT NULL DEFAULT 1,
-    qid         bigint NULL DEFAULT NULL,
-    ssid        bigint NULL DEFAULT NULL,
-    tid         bigint NULL DEFAULT NULL,
-    note        text NULL DEFAULT NULL,
-    CONSTRAINT  pkey_users PRIMARY KEY (local_part, gid),
-    CONSTRAINT  ukey_users_uid UNIQUE (uid),
-    CONSTRAINT  fkey_users_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid),
-    CONSTRAINT  fkey_users_mid_maillocation FOREIGN KEY (mid)
-        REFERENCES maillocation (mid),
-    CONSTRAINT  fkey_users_qid_quotalimit FOREIGN KEY (qid)
-        REFERENCES quotalimit (qid),
-    CONSTRAINT fkey_users_ssid_service_set FOREIGN KEY (ssid)
-        REFERENCES service_set (ssid),
-    CONSTRAINT  fkey_users_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
-);
-
-CREATE TABLE userquota (
-    uid         bigint NOT NULL,
-    bytes       bigint NOT NULL DEFAULT 0,
-    messages    integer NOT NULL DEFAULT 0,
-    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
-    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
-        REFERENCES users (uid) ON DELETE CASCADE
-);
-
-CREATE TABLE alias (
-    gid         bigint NOT NULL,
-    address     varchar(64) NOT NULL,-- only localpart w/o '@'
-    destination varchar(320) NOT NULL,
-    CONSTRAINT  pkey_alias PRIMARY KEY (gid, address, destination),
-    CONSTRAINT  fkey_alias_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
-CREATE TABLE relocated (
-    gid         bigint NOT NULL,
-    address     varchar(64) NOT NULL,
-    destination varchar(320) NOT NULL,
-    CONSTRAINT  pkey_relocated PRIMARY KEY (gid, address),
-    CONSTRAINT  fkey_relocated_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
-CREATE TABLE catchall (
-    gid         bigint NOT NULL,
-    destination varchar(320) NOT NULL,
-    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
-    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
-CREATE OR REPLACE VIEW postfix_gid AS
-    SELECT gid, domainname
-      FROM domain_name;
-
-CREATE OR REPLACE VIEW vmm_domain_info AS
-    SELECT gid, count(uid) AS accounts,
-           (SELECT count(DISTINCT address)
-              FROM alias
-             WHERE alias.gid = domain_data.gid) AS aliases,
-           (SELECT count(gid)
-              FROM relocated
-             WHERE relocated.gid = domain_data.gid) AS relocated,
-           (SELECT count(gid)
-              FROM domain_name
-             WHERE domain_name.gid = domain_data.gid
-               AND NOT domain_name.is_primary) AS aliasdomains,
-           (SELECT count(gid)
-              FROM catchall
-             WHERE catchall.gid = domain_data.gid) AS catchall
-      FROM domain_data
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN users USING (gid)
-     WHERE domain_name.is_primary
-  GROUP BY gid;
-
--- ########################################################################## --
-
-CREATE LANGUAGE plpgsql;
-
--- ######################## TYPEs ########################################### --
-
--- ---
--- Data type for function postfix_virtual_mailbox(varchar, varchar)
--- ---
-CREATE TYPE address_maildir AS (
-    address varchar(320),
-    maildir text
-);
--- ---
--- Data type for function dovecotpassword(varchar, varchar)
--- ---
-CREATE TYPE dovecotpassword AS (
-    userid    varchar(320),
-    password  varchar(270),
-    smtp      boolean,
-    pop3      boolean,
-    imap      boolean,
-    sieve     boolean
-);
--- ---
--- Data type for function dovecotquotauser(varchar, varchar)
--- ---
-CREATE TYPE dovecotquotauser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text,
-    quota_rule  text
-);
--- ---
--- Data type for function dovecotuser(varchar, varchar)
--- ---
-CREATE TYPE dovecotuser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text
-);
--- ---
--- Data type for functions: postfix_relocated_map(varchar, varchar)
---                          postfix_virtual_alias_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_destination AS (
-    recipient   varchar(320),
-    destination text
-);
--- ---
--- Data type for function postfix_transport_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_transport AS (
-    recipient   varchar(320),
-    transport   text
-);
--- ---
--- Data type for function postfix_virtual_uid_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_uid AS (
-    recipient   varchar(320),
-    uid         bigint
-);
--- ---
--- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
--- ---
-CREATE TYPE sender_login AS (
-    sender  varchar(320),
-    login   text
-);
-
--- ######################## TRIGGERs ######################################## --
-
-CREATE OR REPLACE FUNCTION domain_primary_trigger() RETURNS TRIGGER AS $$
-DECLARE
-    primary_count bigint;
-BEGIN
-    SELECT INTO primary_count count(gid) + NEW.is_primary::integer
-      FROM domain_name
-     WHERE domain_name.gid = NEW.gid
-       AND is_primary;
-
-    IF (primary_count > 1) THEN
-        RAISE EXCEPTION 'There can only be one domain marked as primary.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql STABLE;
-
-
-CREATE TRIGGER primary_count_ins BEFORE INSERT ON domain_name
-    FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
-
-CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
-    FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
-
-
-CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
-BEGIN
-    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
-        IF NEW.messages IS NULL THEN
-            NEW.messages = 0;
-        ELSE
-            NEW.messages = -NEW.messages;
-        END IF;
-        RETURN NEW;
-    END IF;
-    LOOP
-        UPDATE userquota
-           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
-         WHERE uid = NEW.uid;
-        IF found THEN
-            RETURN NULL;
-        END IF;
-        BEGIN
-            IF NEW.messages = 0 THEN
-              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
-            ELSE
-              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
-            END IF;
-            RETURN NULL;
-        EXCEPTION
-            WHEN unique_violation THEN
-                -- do nothing, and loop to try the UPDATE again
-            WHEN foreign_key_violation THEN
-                -- break the loop: a non matching uid means no such user
-                RETURN NULL;
-        END;
-    END LOOP;
-END;
-$$ LANGUAGE plpgsql;
-
-
-CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
-    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
-
--- ######################## FUNCTIONs ####################################### --
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotpassword records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users, domain_name, service_set TO dovecot;
---
--- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
--- ---
-CREATE OR REPLACE FUNCTION dovecotpassword(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
-AS $$
-    DECLARE
-        record dovecotpassword;
-        userid varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, sieve
-              FROM users, service_set, domain_data
-             WHERE users.gid = (SELECT gid
-                                  FROM domain_name
-                                 WHERE domainname = the_domain)
-               AND local_part = localpart
-               AND users.gid = domain_data.gid
-               AND CASE WHEN
-                     users.ssid IS NOT NULL
-                     THEN
-                       service_set.ssid = users.ssid
-                     ELSE
-                       service_set.ssid = domain_data.ssid
-                     END
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Nearly the same as function dovecotuser below. It returns additionally the
--- field quota_rule.
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT
---          ON users, domain_data, domain_name, maillocation, mailboxformat,
---             quotalimit
---          TO dovecot;
--- ---
-CREATE OR REPLACE FUNCTION dovecotquotauser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
-AS $$
-    DECLARE
-        record dovecotquotauser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir || '/' || uid AS home,
-                   format || ':~/' || directory AS mail, '*:bytes=' ||
-                   bytes || ':messages=' || messages AS quota_rule
-              FROM users, domain_data, mailboxformat, maillocation, quotalimit
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND mailboxformat.fid = maillocation.fid
-               AND domain_data.gid = did
-               AND CASE WHEN
-                     users.qid IS NOT NULL
-                   THEN
-                     quotalimit.qid = users.qid
-                   ELSE
-                     quotalimit.qid = domain_data.qid
-                   END
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotuser records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT
---          ON users, domain_data, domain_name, maillocation, mailboxformat
---          TO dovecot;
---
--- For more details see http://wiki.dovecot.org/UserDatabase
--- ---
-CREATE OR REPLACE FUNCTION dovecotuser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
-AS $$
-    DECLARE
-        record dovecotuser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir || '/' || uid AS home,
-                   format || ':~/' || directory AS mail
-              FROM users, domain_data, mailboxformat, maillocation
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND mailboxformat.fid = maillocation.fid
-               AND domain_data.gid = did
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, relocated TO postfix;
---
--- For more details see postconf(5) section relocated_maps and relocated(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_relocated_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM relocated
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
---      set of sender_login records.
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, users, alias TO postfix;
---
--- For more details see postconf(5) section smtpd_sender_login_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
-AS $$
-    DECLARE
-        rec sender_login;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        sender varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        -- Get all addresses for 'localpart' in the primary and aliased domains
-        FOR rec IN
-            SELECT sender, local_part || '@' || domainname
-              FROM domain_name, users
-             WHERE domain_name.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        IF NOT FOUND THEN
-            -- Loop over the alias addresses for localpart@the_domain
-            FOR rec IN
-                SELECT DISTINCT sender, destination
-                  FROM alias
-                 WHERE alias.gid = did
-                   AND alias.address = localpart
-                LOOP
-                    RETURN NEXT rec;
-                END LOOP;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_transport records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, transport, domain_name TO postfix;
---
--- For more details see postconf(5) section transport_maps and transport(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_transport_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_transport
-AS $$
-    DECLARE
-        record recipient_transport;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
-        transport_id bigint;
-    BEGIN
-        IF did IS NULL THEN
-            RETURN;
-        END IF;
-
-        SELECT tid INTO transport_id
-          FROM users
-         WHERE gid = did AND local_part = localpart;
-
-        IF transport_id IS NULL THEN
-            SELECT tid INTO STRICT transport_id
-              FROM domain_data
-             WHERE gid = did;
-        END IF;
-
-        FOR record IN
-            SELECT recipient, transport
-              FROM transport
-             WHERE tid = transport_id
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON alias, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_alias_maps and virtual(5)
--- ---
-CREATE OR REPLACE FUNCTION _interpolate_destination(
-    IN destination varchar, localpart varchar, IN the_domain varchar)
-    RETURNS varchar
-AS $$
-    DECLARE
-        result varchar(320);
-    BEGIN
-        IF position('%' in destination) = 0 THEN
-            RETURN destination;
-        END IF;
-        result := replace(destination, '%n', localpart);
-        result := replace(result, '%d', the_domain);
-        result := replace(result, '%=', localpart || '=' || the_domain);
-        RETURN result;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
-CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        recordc recipient_destination;
-        record recipient_destination;
-        catchall_cursor refcursor;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient,
-                _interpolate_destination(destination, localpart, the_domain)
-              FROM alias
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-
-        IF NOT FOUND THEN
-            -- There is no matching virtual_alias. If there are no catchall
-            -- records for this domain, we can just return NULL since Postfix
-            -- will then later consult mailboxes/relocated itself. But if
-            -- there is a catchall destination, then it would take precedence
-            -- over mailboxes/relocated, which is not what we want. Therefore,
-            -- we must first find out if the query is for an existing mailbox
-            -- or relocated entry and return the identity mapping if that is
-            -- the case
-            OPEN catchall_cursor FOR
-                SELECT recipient,
-                    _interpolate_destination(destination, localpart, the_domain)
-                  FROM catchall
-                 WHERE gid = did;
-            FETCH NEXT FROM catchall_cursor INTO recordc;
-
-            IF recordc IS NOT NULL THEN
-                -- Since there are catchall records for this domain
-                -- check the mailbox and relocated records and return identity
-                -- if a matching record exists.
-                FOR record IN
-                    SELECT recipient, recipient as destination
-                      FROM users
-                    WHERE gid = did
-                      AND local_part = localpart
-                    UNION SELECT recipient, recipient as destination
-                      FROM relocated
-                    WHERE gid = did
-                      AND address = localpart
-                    LOOP
-                        RETURN NEXT record;
-                    END LOOP;
-
-                IF NOT FOUND THEN
-                    -- There were no records found for mailboxes/relocated,
-                    -- so now we can actually iterate the cursor and populate
-                    -- the return set
-                    LOOP
-                        RETURN NEXT recordc;
-                        FETCH NEXT FROM catchall_cursor INTO recordc;
-                        EXIT WHEN recordc IS NULL;
-                    END LOOP;
-                END IF;
-            END IF;
-            CLOSE catchall_cursor;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: address_maildir records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_data,domain_name,maillocation,users TO postfix;
---
--- For more details see postconf(5) section virtual_mailbox_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
-   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
-AS $$
-    DECLARE
-        rec address_maildir;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        address varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR rec IN
-            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
-              FROM domain_data, users, maillocation
-             WHERE domain_data.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_uid records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_uid_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
-AS $$
-    DECLARE
-        record recipient_uid;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, uid
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- a/pgsql/create_tables.pgsql	Sun Mar 09 18:42:58 2014 +0000
+++ b/pgsql/create_tables.pgsql	Sun Mar 09 18:52:27 2014 +0000
@@ -34,7 +34,7 @@
     CONSTRAINT  ukey_transport UNIQUE (transport)
 );
 -- Insert default transport
-INSERT INTO transport(transport) VALUES ('dovecot:');
+INSERT INTO transport(transport) VALUES ('lmtp:unix:private/dovecot-lmtp');
 
 CREATE TABLE mailboxformat (
     fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
@@ -74,12 +74,12 @@
     smtp        boolean NOT NULL DEFAULT TRUE,
     pop3        boolean NOT NULL DEFAULT TRUE,
     imap        boolean NOT NULL DEFAULT TRUE,
-    managesieve boolean NOT NULL DEFAULT TRUE,
+    sieve       boolean NOT NULL DEFAULT TRUE,
     CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
-    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, managesieve)
+    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, sieve)
 );
 -- Insert all possible service combinations
-COPY service_set (smtp, pop3, imap, managesieve) FROM stdin;
+COPY service_set (smtp, pop3, imap, sieve) FROM stdin;
 TRUE	TRUE	TRUE	TRUE
 FALSE	TRUE	TRUE	TRUE
 TRUE	FALSE	TRUE	TRUE
@@ -148,12 +148,12 @@
         REFERENCES transport (tid)
 );
 
-CREATE TABLE userquota_11 (
+CREATE TABLE userquota (
     uid         bigint NOT NULL,
-    path        varchar(16) NOT NULL,
-    current     bigint NOT NULL DEFAULT 0,
-    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
-    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
+    bytes       bigint NOT NULL DEFAULT 0,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
+    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
         REFERENCES users (uid) ON DELETE CASCADE
 );
 
@@ -225,12 +225,12 @@
 -- Data type for function dovecotpassword(varchar, varchar)
 -- ---
 CREATE TYPE dovecotpassword AS (
-    userid      varchar(320),
-    password    varchar(270),
-    smtp        boolean,
-    pop3        boolean,
-    imap        boolean,
-    managesieve boolean
+    userid    varchar(320),
+    password  varchar(270),
+    smtp      boolean,
+    pop3      boolean,
+    imap      boolean,
+    sieve     boolean
 );
 -- ---
 -- Data type for function dovecotquotauser(varchar, varchar)
@@ -310,21 +310,44 @@
     FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
 
 
-CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
+CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
 BEGIN
-    UPDATE userquota_11
-       SET current = current + NEW.current
-     WHERE uid = NEW.uid AND path = NEW.path;
-    IF found THEN
-        RETURN NULL;
-    ELSE
+    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+        IF NEW.messages IS NULL THEN
+            NEW.messages = 0;
+        ELSE
+            NEW.messages = -NEW.messages;
+        END IF;
         RETURN NEW;
     END IF;
+    LOOP
+        UPDATE userquota
+           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
+         WHERE uid = NEW.uid;
+        IF found THEN
+            RETURN NULL;
+        END IF;
+        BEGIN
+            IF NEW.messages = 0 THEN
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
+            ELSE
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
+            END IF;
+            RETURN NULL;
+        EXCEPTION
+            WHEN unique_violation THEN
+                -- do nothing, and loop to try the UPDATE again
+            WHEN foreign_key_violation THEN
+                -- break the loop: a non matching uid means no such user
+                RETURN NULL;
+        END;
+    END LOOP;
 END;
 $$ LANGUAGE plpgsql;
 
-CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
-    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
+
+CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
 
 -- ######################## FUNCTIONs ####################################### --
 
@@ -347,7 +370,7 @@
         userid varchar(320) := localpart || '@' || the_domain;
     BEGIN
         FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, managesieve
+            SELECT userid, passwd, smtp, pop3, imap, sieve
               FROM users, service_set, domain_data
              WHERE users.gid = (SELECT gid
                                   FROM domain_name
@@ -355,12 +378,12 @@
                AND local_part = localpart
                AND users.gid = domain_data.gid
                AND CASE WHEN
-                  users.ssid IS NOT NULL
-                  THEN
-                    service_set.ssid = users.ssid
-                  ELSE
-                    service_set.ssid = domain_data.ssid
-                  END
+                     users.ssid IS NOT NULL
+                     THEN
+                       service_set.ssid = users.ssid
+                     ELSE
+                       service_set.ssid = domain_data.ssid
+                     END
             LOOP
                 RETURN NEXT record;
             END LOOP;
--- a/pgsql/set-permissions.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/pgsql/set-permissions.py	Sun Mar 09 18:52:27 2014 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # coding: utf-8
 # Copyright 2012 - 2014, Pascal Volk
 # See COPYING for distribution information.
@@ -13,75 +13,53 @@
 import getpass
 import sys
 
-from optparse import OptionParser
+from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
 
-has_psycopg2 = False
-try:
-    import psycopg2
-    has_psycopg2 = True
-except ImportError:
-    try:
-        from pyPgSQL import PgSQL
-    except ImportError:
-        sys.stderr.write('error: no suitable database module found\n')
-        raise SystemExit(1)
-
-if has_psycopg2:
-    DBErr = psycopg2.DatabaseError
-else:
-    DBErr = PgSQL.libpq.DatabaseError
+import psycopg2
 
 
-def check_opts(opts, err_hdlr):
-    if not opts.postfix:
+def check_args(args, err_hdlr):
+    if not args.postfix:
         err_hdlr('missing Postfix database user name')
-    if not opts.dovecot:
+    if not args.dovecot:
         err_hdlr('missing Dovecot database user name')
-    if opts.askp:
-        opts.dbpass = getpass.getpass()
+    if args.askp:
+        args.dbpass = getpass.getpass()
 
 
 def get_dbh(database, user, password, host, port):
-    if has_psycopg2:
-        return psycopg2.connect(database=database, user=user,
-                                password=password, host=host, port=port)
-    return PgSQL.connect(user=user, password=password, host=host,
-                         database=database, port=port)
+    return psycopg2.connect(database=database, user=user,
+                            password=password, host=host, port=port)
 
 
-def get_optparser():
+def get_argparser():
     descr = 'Set permissions for Dovecot and Postfix in the vmm database.'
-    usage = 'usage: %prog OPTIONS'
-    parser = OptionParser(description=descr, usage=usage)
-    parser.add_option('-a', '--askpass', dest='askp', default=False,
+    parser = ArgumentParser(description=descr, usage='%(prog)s OPTIONS',
+                            formatter_class=ArgumentDefaultsHelpFormatter)
+    parser.add_argument('-a', '--askpass', dest='askp',
             action='store_true', help='Prompt for the database password.')
-    parser.add_option('-H', '--host', dest='host', metavar='HOST',
-            default=None,
+    parser.add_argument('-H', '--host', dest='host', metavar='HOST',
             help='Hostname or IP address of the database server. Leave ' +
                  'blank in order to use the default Unix-domain socket.')
-    parser.add_option('-n', '--name', dest='name', metavar='NAME',
+    parser.add_argument('-n', '--name', dest='name', metavar='NAME',
             default='mailsys',
-            help='Specifies the name of the database to connect to. ' +
-                 'Default: %default')
-    parser.add_option('-p', '--pass', dest="dbpass", metavar='PASS',
-            default=None, help='Password for the database connection.')
-    parser.add_option('-P', '--port', dest='port', metavar='PORT', type='int',
-            default=5432,
+            help='Specifies the name of the database to connect to.')
+    parser.add_argument('-p', '--pass', dest="dbpass", metavar='PASS',
+            help='Password for the database connection.')
+    parser.add_argument('-P', '--port', dest='port', metavar='PORT',
+            type=int, default=5432,
             help='Specifies the TCP port or the local Unix-domain socket ' +
                  'file extension on which the server is listening for ' +
-                 'connections. Default: %default')
-    parser.add_option('-U', '--user', dest='user', metavar='USER',
+                 'connections.')
+    parser.add_argument('-U', '--user', dest='user', metavar='USER',
             default=getpass.getuser(),
-            help='Connect to the database as the user USER instead of the ' +
-                 'default: %default')
-    parser.add_option('-D', '--dovecot', dest='dovecot', metavar='USER',
+            help='Connect to the database as the user USER instead of the ')
+    parser.add_argument('-D', '--dovecot', dest='dovecot', metavar='USER',
             default='dovecot',
-            help='Database user name of the Dovecot database user. Default: ' +
-                 '%default')
-    parser.add_option('-M', '--postfix', dest='postfix', metavar='USER',
+            help='Database user name of the Dovecot database user.')
+    parser.add_argument('-M', '--postfix', dest='postfix', metavar='USER',
             default='postfix',
-            help='Database user name of the Postfix (MTA)  database user. ' +
-                 'Default: %default')
+            help='Database user name of the Postfix (MTA)  database user.')
     return parser
 
 
@@ -119,10 +97,10 @@
                 (dc_rw_tbls, db['dovecot']))
     dbc.execute('GRANT SELECT ON %s TO %s' % (dc_ro_tbls, db['dovecot']))
     dbc.execute('GRANT SELECT ON %s TO %s' % (pf_ro_tbls, db['postfix']))
-    for table, columns in db['dovecot_tbls'].iteritems():
+    for table, columns in db['dovecot_tbls'].items():
         dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
                                                        db['dovecot']))
-    for table, columns in db['postfix_tbls'].iteritems():
+    for table, columns in db['postfix_tbls'].items():
         dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
                                                        db['postfix']))
     dbc.close()
@@ -136,7 +114,7 @@
         try:
             dbc.execute("SELECT current_setting('server_version_num')")
             versions['pgsql'] = int(dbc.fetchone()[0])
-        except DBErr:
+        except psycopg2.DatabaseError:
             versions['pgsql'] = 80199
     dbc.execute("SELECT relname FROM pg_stat_user_tables WHERE relname LIKE "
                 "'userquota%'")
@@ -155,15 +133,15 @@
 
 
 if __name__ == '__main__':
-    optparser = get_optparser()
-    opts, args = optparser.parse_args()
-    check_opts(opts, optparser.error)
-    dbh = get_dbh(opts.name, opts.user, opts.dbpass, opts.host, opts.port)
+    argparser = get_argparser()
+    args = argparser.parse_args()
+    check_args(args, argparser.error)
+    dbh = get_dbh(args.name, args.user, args.dbpass, args.host, args.port)
     versions = {}
     set_versions(dbh, versions)
     if versions['pgsql'] < 80400:
-        set_permissions(dbh, versions['dovecot'], opts.dovecot, opts.postfix)
+        set_permissions(dbh, versions['dovecot'], args.dovecot, args.postfix)
     else:
-        set_permissions84(dbh, versions['dovecot'], opts.dovecot, opts.postfix)
+        set_permissions84(dbh, versions['dovecot'], args.dovecot, args.postfix)
     dbh.commit()
     dbh.close()
--- a/pgsql/update_tables_0.5.x-0.6.pgsql	Sun Mar 09 18:42:58 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,658 +0,0 @@
-SET client_encoding = 'UTF8';
-SET client_min_messages = warning;
-
--- ---
--- Create the new service_set table and insert all possible combinations
--- --
-CREATE SEQUENCE service_set_id;
-
-CREATE TABLE service_set (
-    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
-    smtp        boolean NOT NULL DEFAULT TRUE,
-    pop3        boolean NOT NULL DEFAULT TRUE,
-    imap        boolean NOT NULL DEFAULT TRUE,
-    managesieve boolean NOT NULL DEFAULT TRUE,
-    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
-    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, managesieve)
-);
-
-COPY service_set (smtp, pop3, imap, managesieve) FROM stdin;
-TRUE	TRUE	TRUE	TRUE
-FALSE	TRUE	TRUE	TRUE
-TRUE	FALSE	TRUE	TRUE
-FALSE	FALSE	TRUE	TRUE
-TRUE	TRUE	FALSE	TRUE
-FALSE	TRUE	FALSE	TRUE
-TRUE	FALSE	FALSE	TRUE
-FALSE	FALSE	FALSE	TRUE
-TRUE	TRUE	TRUE	FALSE
-FALSE	TRUE	TRUE	FALSE
-TRUE	FALSE	TRUE	FALSE
-FALSE	FALSE	TRUE	FALSE
-TRUE	TRUE	FALSE	FALSE
-FALSE	TRUE	FALSE	FALSE
-TRUE	FALSE	FALSE	FALSE
-FALSE	FALSE	FALSE	FALSE
-\.
-
--- ---
--- Drop the obsolete VIEWs, we've functions now.
--- ---
-DROP VIEW dovecot_user;
-DROP VIEW dovecot_password;
-DROP VIEW postfix_alias;
-DROP VIEW postfix_maildir;
-DROP VIEW postfix_relocated;
-DROP VIEW postfix_transport;
-DROP VIEW postfix_uid;
--- the vmm_domain_info view will be restored later
-DROP VIEW vmm_domain_info;
-
-CREATE SEQUENCE mailboxformat_id;
-CREATE SEQUENCE quotalimit_id;
-
-CREATE TABLE mailboxformat (
-    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
-    format      varchar(20) NOT NULL,
-    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
-    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
-);
--- Insert supported mailbox formats
-INSERT INTO mailboxformat(format) VALUES ('maildir');
-INSERT INTO mailboxformat(format) VALUES ('mdbox');
-INSERT INTO mailboxformat(format) VALUES ('sdbox');
-
--- Adjust maillocation table
-ALTER TABLE maillocation DROP CONSTRAINT ukey_maillocation;
-ALTER TABLE maillocation RENAME COLUMN maillocation TO directory;
-ALTER TABLE maillocation
-    ADD COLUMN fid bigint NOT NULL DEFAULT 1,
-    ADD COLUMN extra varchar(1024);
-ALTER TABLE maillocation ADD CONSTRAINT fkey_maillocation_fid_mailboxformat
-    FOREIGN KEY (fid) REFERENCES mailboxformat (fid);
-
-ALTER TABLE users ALTER COLUMN passwd TYPE varchar(270);
-
--- ---
--- Add quota stuff
--- ---
-CREATE TABLE quotalimit (
-    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
-    bytes       bigint NOT NULL,
-    messages    integer NOT NULL DEFAULT 0,
-    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
-    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
-);
--- Insert default (non) quota limit
-INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
-
--- Adjust tables (quota)
-ALTER TABLE domain_data ADD COLUMN qid bigint NOT NULL DEFAULT 1;
-ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_qid_quotalimit
-    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
-
-ALTER TABLE users ADD COLUMN qid bigint NULL DEFAULT NULL;
-ALTER TABLE users ADD CONSTRAINT fkey_users_qid_quotalimit
-    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
-
-CREATE TABLE userquota_11 (
-    uid         bigint NOT NULL,
-    path        varchar(16) NOT NULL,
-    current     bigint NOT NULL DEFAULT 0,
-    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
-    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
-        REFERENCES users (uid) ON DELETE CASCADE
-);
-
-CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
-BEGIN
-    UPDATE userquota_11
-       SET current = current + NEW.current
-     WHERE uid = NEW.uid AND path = NEW.path;
-    IF found THEN
-        RETURN NULL;
-    ELSE
-        RETURN NEW;
-    END IF;
-END;
-$$ LANGUAGE plpgsql;
-
-CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
-    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
-
--- Adjust tables (services)
-ALTER TABLE domain_data ADD COLUMN ssid bigint NOT NULL DEFAULT 1;
-ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_ssid_service_set
-    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
-
-ALTER TABLE users ADD COLUMN ssid bigint NULL DEFAULT NULL;
--- save current service sets
-UPDATE users u
-   SET ssid = ss.ssid
-  FROM service_set ss
- WHERE ss.smtp = u.smtp
-   AND ss.pop3 = u.pop3
-   AND ss.imap = u.imap
-   AND ss.managesieve = u.managesieve;
-
-ALTER TABLE users DROP COLUMN smtp;
-ALTER TABLE users DROP COLUMN pop3;
-ALTER TABLE users DROP COLUMN imap;
-ALTER TABLE users DROP COLUMN managesieve;
-ALTER TABLE users ADD CONSTRAINT fkey_users_ssid_service_set
-    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
-
--- ---
--- Catchall
--- ---
--- Quota/Service/Transport inheritance
--- ---
-ALTER TABLE users ALTER COLUMN tid DROP NOT NULL;
-ALTER TABLE users ALTER COLUMN tid SET DEFAULT NULL;
--- The qid and ssid columns have already been defined accordingly above.
--- The rest of the logic will take place in the functions.
-
--- While qid and ssid are new and it's perfectly okay for existing users to
--- get NULL values (i.e. inherit from the domain's default), tid existed in
--- vmm 0.5.x. A sensible way forward seems thus to NULL all user records' tid
--- fields where the tid duplicates the value stored in the domain's record.
-UPDATE users
-   SET tid = NULL
- WHERE tid = (SELECT tid
-                FROM domain_data
-               WHERE domain_data.gid = users.gid);
-
--- ---
-
-CREATE TABLE catchall (
-    gid         bigint NOT NULL,
-    destination varchar(320) NOT NULL,
-    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
-    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
--- ---
--- Account/domain notes
--- ---
-
-ALTER TABLE users ADD COLUMN note text NULL DEFAULT NULL;
-ALTER TABLE domain_data ADD COLUMN note text NULL DEFAULT NULL;
-
--- ---
--- Restore view
--- ---
-CREATE VIEW vmm_domain_info AS
-    SELECT gid, count(uid) AS accounts,
-           (SELECT count(DISTINCT address)
-              FROM alias
-             WHERE alias.gid = domain_data.gid) AS aliases,
-           (SELECT count(gid)
-              FROM relocated
-             WHERE relocated.gid = domain_data.gid) AS relocated,
-           (SELECT count(gid)
-              FROM domain_name
-             WHERE domain_name.gid = domain_data.gid
-               AND NOT domain_name.is_primary) AS aliasdomains,
-           (SELECT count(gid)
-              FROM catchall
-             WHERE catchall.gid = domain_data.gid) AS catchall
-      FROM domain_data
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN users USING (gid)
-     WHERE domain_name.is_primary
-  GROUP BY gid;
-
--- ---
--- Drop all known v0.5 types (the dirty way)
--- ---
-DROP TYPE address_maildir CASCADE;
-DROP TYPE dovecotpassword CASCADE;
-DROP TYPE dovecotuser CASCADE;
-DROP TYPE recipient_destination CASCADE;
-DROP TYPE recipient_transport CASCADE;
-DROP TYPE recipient_uid CASCADE;
-DROP TYPE sender_login CASCADE;
-
--- ######################## TYPEs ########################################### --
-
--- ---
--- Data type for function postfix_virtual_mailbox(varchar, varchar)
--- ---
-CREATE TYPE address_maildir AS (
-    address varchar(320),
-    maildir text
-);
--- ---
--- Data type for function dovecotpassword(varchar, varchar)
--- ---
-CREATE TYPE dovecotpassword AS (
-    userid      varchar(320),
-    password    varchar(270),
-    smtp        boolean,
-    pop3        boolean,
-    imap        boolean,
-    managesieve boolean
-);
--- ---
--- Data type for function dovecotquotauser(varchar, varchar)
--- ---
-CREATE TYPE dovecotquotauser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text,
-    quota_rule  text
-);
--- ---
--- Data type for function dovecotuser(varchar, varchar)
--- ---
-CREATE TYPE dovecotuser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text
-);
--- ---
--- Data type for functions: postfix_relocated_map(varchar, varchar)
---                          postfix_virtual_alias_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_destination AS (
-    recipient   varchar(320),
-    destination text
-);
--- ---
--- Data type for function postfix_transport_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_transport AS (
-    recipient   varchar(320),
-    transport   text
-);
--- ---
--- Data type for function postfix_virtual_uid_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_uid AS (
-    recipient   varchar(320),
-    uid         bigint
-);
--- ---
--- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
--- ---
-CREATE TYPE sender_login AS (
-    sender  varchar(320),
-    login   text
-);
-
--- ######################## FUNCTIONs ####################################### --
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotpassword records
--- ---
-CREATE OR REPLACE FUNCTION dovecotpassword(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
-AS $$
-    DECLARE
-        record dovecotpassword;
-        userid varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, managesieve
-              FROM users, service_set, domain_data
-             WHERE users.gid = (SELECT gid
-                                  FROM domain_name
-                                 WHERE domainname = the_domain)
-               AND local_part = localpart
-               AND users.gid = domain_data.gid
-               AND CASE WHEN
-                  users.ssid IS NOT NULL
-                  THEN
-                    service_set.ssid = users.ssid
-                  ELSE
-                    service_set.ssid = domain_data.ssid
-                  END
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Nearly the same as function dovecotuser below. It returns additionally the
--- field quota_rule.
--- ---
-CREATE OR REPLACE FUNCTION dovecotquotauser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
-AS $$
-    DECLARE
-        record dovecotquotauser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir || '/' || uid AS home,
-                   format || ':~/' || directory AS mail, '*:bytes=' ||
-                   bytes || ':messages=' || messages AS quota_rule
-              FROM users, domain_data, mailboxformat, maillocation, quotalimit
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND mailboxformat.fid = maillocation.fid
-               AND domain_data.gid = did
-               AND CASE WHEN
-                     users.qid IS NOT NULL
-                   THEN
-                     quotalimit.qid = users.qid
-                   ELSE
-                     quotalimit.qid = domain_data.qid
-                   END
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotuser records
--- ---
-CREATE OR REPLACE FUNCTION dovecotuser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
-AS $$
-    DECLARE
-        record dovecotuser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir || '/' || uid AS home,
-                   format || ':~/' || directory AS mail
-              FROM users, domain_data, mailboxformat, maillocation
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND mailboxformat.fid = maillocation.fid
-               AND domain_data.gid = did
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
--- ---
-CREATE OR REPLACE FUNCTION postfix_relocated_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM relocated
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
---      set of sender_login records.
--- ---
-CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
-AS $$
-    DECLARE
-        rec sender_login;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        sender varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        -- Get all addresses for 'localpart' in the primary and aliased domains
-        FOR rec IN
-            SELECT sender, local_part || '@' || domainname
-              FROM domain_name, users
-             WHERE domain_name.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        IF NOT FOUND THEN
-            -- Loop over the alias addresses for localpart@the_domain
-            FOR rec IN
-                SELECT DISTINCT sender, destination
-                  FROM alias
-                 WHERE alias.gid = did
-                   AND alias.address = localpart
-                LOOP
-                    RETURN NEXT rec;
-                END LOOP;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_transport records
--- ---
-CREATE OR REPLACE FUNCTION postfix_transport_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_transport
-AS $$
-    DECLARE
-        record recipient_transport;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
-        transport_id bigint;
-    BEGIN
-        IF did IS NULL THEN
-            RETURN;
-        END IF;
-
-        SELECT tid INTO transport_id
-          FROM users
-         WHERE gid = did AND local_part = localpart;
-
-        IF transport_id IS NULL THEN
-            SELECT tid INTO STRICT transport_id
-              FROM domain_data
-             WHERE gid = did;
-        END IF;
-
-        FOR record IN
-            SELECT recipient, transport
-              FROM transport
-             WHERE tid = transport_id
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
--- ---
-CREATE OR REPLACE FUNCTION _interpolate_destination(
-    IN destination varchar, localpart varchar, IN the_domain varchar)
-    RETURNS varchar
-AS $$
-    DECLARE
-        result varchar(320);
-    BEGIN
-        IF position('%' in destination) = 0 THEN
-            RETURN destination;
-        END IF;
-        result := replace(destination, '%n', localpart);
-        result := replace(result, '%d', the_domain);
-        result := replace(result, '%=', localpart || '=' || the_domain);
-        RETURN result;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
-CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        recordc recipient_destination;
-        record recipient_destination;
-        catchall_cursor refcursor;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient,
-                _interpolate_destination(destination, localpart, the_domain)
-              FROM alias
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-
-        IF NOT FOUND THEN
-            -- There is no matching virtual_alias. If there are no catchall
-            -- records for this domain, we can just return NULL since Postfix
-            -- will then later consult mailboxes/relocated itself. But if
-            -- there is a catchall destination, then it would take precedence
-            -- over mailboxes/relocated, which is not what we want. Therefore,
-            -- we must first find out if the query is for an existing mailbox
-            -- or relocated entry and return the identity mapping if that is
-            -- the case
-            OPEN catchall_cursor FOR
-                SELECT recipient,
-                    _interpolate_destination(destination, localpart, the_domain)
-                  FROM catchall
-                 WHERE gid = did;
-            FETCH NEXT FROM catchall_cursor INTO recordc;
-
-            IF recordc IS NOT NULL THEN
-                -- Since there are catchall records for this domain
-                -- check the mailbox and relocated records and return identity
-                -- if a matching record exists.
-                FOR record IN
-                    SELECT recipient, recipient as destination
-                      FROM users
-                    WHERE gid = did
-                      AND local_part = localpart
-                    UNION SELECT recipient, recipient as destination
-                      FROM relocated
-                    WHERE gid = did
-                      AND address = localpart
-                    LOOP
-                        RETURN NEXT record;
-                    END LOOP;
-
-                IF NOT FOUND THEN
-                    -- There were no records found for mailboxes/relocated,
-                    -- so now we can actually iterate the cursor and populate
-                    -- the return set
-                    LOOP
-                        RETURN NEXT recordc;
-                        FETCH NEXT FROM catchall_cursor INTO recordc;
-                        EXIT WHEN recordc IS NULL;
-                    END LOOP;
-                END IF;
-            END IF;
-            CLOSE catchall_cursor;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: address_maildir records
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
-   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
-AS $$
-    DECLARE
-        rec address_maildir;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        address varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR rec IN
-            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
-              FROM domain_data, users, maillocation
-             WHERE domain_data.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_uid records
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
-AS $$
-    DECLARE
-        record recipient_uid;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, uid
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- a/setup.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/setup.py	Sun Mar 09 18:52:27 2014 +0000
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 # Copyright 2007 - 2014, Pascal Volk
 # See COPYING for distribution information.
 
 import os
 from distutils.core import setup
-from distutils.dist import DistributionMetadata
 
 VERSION = '0.6.2'
 
@@ -20,7 +19,6 @@
     'VirtualMailManager',
     'VirtualMailManager.cli',
     'VirtualMailManager.ext',
-    'VirtualMailManager.pycompat',
 ]
 # http://pypi.python.org/pypi?%3Aaction=list_classifiers
 classifiers = ['Development Status :: 5 - Production/Stable',
@@ -44,7 +42,7 @@
                'Topic :: Utilities']
 
 # sucessfuly tested on:
-platforms = ['freebsd7', 'linux2', 'openbsd4']
+platforms = ['freebsd7', 'linux2', 'openbsd5']
 
 # remove existing MANIFEST
 if os.path.exists('MANIFEST'):
@@ -58,12 +56,10 @@
               'author': 'Pascal Volk',
               'author_email': 'user+vmm@localhost.localdomain.org',
               'license': 'BSD License',
+              'requires': ['psycopg2 (>=2.0)'],
               'url': 'http://vmm.localdomain.org/',
-              'download_url':'http://sf.net/projects/vmm/files/',
+              'download_url': 'http://sf.net/projects/vmm/files/',
               'platforms': platforms,
               'classifiers': classifiers}
 
-if 'requires' in DistributionMetadata._METHOD_BASENAMES:
-    setup_args['requires'] = ['psycopg2 (>=2.0)', 'pyPgSQL (>=2.5.1)']
-
 setup(**setup_args)
--- a/update_config.py	Sun Mar 09 18:42:58 2014 +0000
+++ b/update_config.py	Sun Mar 09 18:52:27 2014 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2008 - 2014, Pascal Volk
 # See COPYING for distribution information.
@@ -6,7 +6,7 @@
 import os
 os.sys.path.remove(os.sys.path[0])
 from time import time
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 from shutil import copy2
 
 pre_060 = False
@@ -161,18 +161,20 @@
     if len(sect_opt):
         update_cfg_file(cp, cf)
         sect_opt.sort()
-        print 'Please have a look at your configuration: %s' % cf
-        print 'This are your Modified/Renamed/New settings:'
+        print('Please have a look at your configuration: %s' % cf)
+        print('This are your Modified/Renamed/New settings:')
         for s_o, st in sect_opt:
-            print '%s   %s = %s' % (st, s_o, get_option(cp, s_o))
+            print('%s   %s = %s' % (st, s_o, get_option(cp, s_o)))
         if had_config:
-            print '\nRemoved section "config" with option "done" (obsolte)'
+            print('\nRemoved section "config" with option "done" (obsolte)')
         if had_gid_mail:
-            print '\nRemoved option "gid_mail" from section "misc" (obsolte)\n'
+            print('\nRemoved option "gid_mail" from section "misc"',
+                  '(obsolte)\n')
         os.sys.exit(0)
     if had_config or had_gid_mail:
         update_cfg_file(cp, cf)
         if had_config:
-            print '\nRemoved section "config" with option "done" (obsolte)'
+            print('\nRemoved section "config" with option "done" (obsolte)')
         if had_gid_mail:
-            print '\nRemoved option "gid_mail" from section "misc" (obsolte)\n'
+            print('\nRemoved option "gid_mail" from section "misc"',
+                  '(obsolte)\n')
--- a/upgrade.sh	Sun Mar 09 18:42:58 2014 +0000
+++ b/upgrade.sh	Sun Mar 09 18:52:27 2014 +0000
@@ -67,19 +67,6 @@
 
 [ -d ${MANDIR}/man5 ] || mkdir -m 0755 -p ${MANDIR}/man5
 install -m 0644 ${INSTALL_OPTS} man5/vmm.cfg.5 ${MANDIR}/man5
-
-for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\?)
-do
-    for s in man1 man5; do
-        [ -d ${MANDIR}/${l}/${s} ] || mkdir -m 0755 -p ${MANDIR}/${l}/${s}
-    done
-    if [ -f ${l}/man1/vmm.1 ]; then
-        install -m 0644 ${INSTALL_OPTS} ${l}/man1/vmm.1 ${MANDIR}/${l}/man1
-    fi
-    if [ -f ${l}/man5/vmm.cfg.5 ]; then
-        install -m 0644 ${INSTALL_OPTS} ${l}/man5/vmm.cfg.5 ${MANDIR}/${l}/man5
-    fi
-done
 cd - >/dev/null
 
 [ -d ${DOC_DIR} ] || mkdir -m 0755 -p ${DOC_DIR}
--- a/vmm	Sun Mar 09 18:42:58 2014 +0000
+++ b/vmm	Sun Mar 09 18:52:27 2014 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: UTF-8 -*-
 # Copyright 2007 - 2014, Pascal Volk
 # See COPYING for distribution information.
--- a/vmm.cfg	Sun Mar 09 18:42:58 2014 +0000
+++ b/vmm.cfg	Sun Mar 09 18:52:27 2014 +0000
@@ -6,11 +6,6 @@
 # Database settings
 #
 [database]
-; The Python PostgreSQL database adapter module to be used (String)
-; Supported modules are:
-;    * psycopg2
-;    * pyPgSQL
-module = psycopg2
 ; Hostname or IP address of the database server (String)
 host = localhost
 ; The TCP port, on which the database server is listening for connections (Int)
@@ -107,9 +102,8 @@
 ;
 ; With Dovecot >= v2.0.0 it's strongly recommended that you use Dovecot's
 ; lmtp instead of the dovecot-lda.
-;transport = lmtp:unix:private/dovecot-lmtp
-; default transport for domains and accounts (String)
-transport = dovecot:
+; default transport for new created domains (String)
+transport = lmtp:unix:private/dovecot-lmtp
 
 #
 # Account settings
@@ -132,8 +126,8 @@
 # external binaries
 #
 [bin]
-; location of dovecotpw (Dovecot v1) or doveadm (Dovecot v2) (String)
-dovecotpw = /usr/sbin/dovecotpw
+; location of doveadm (String)
+doveadm = /usr/bin/doveadm
 ; location of disk usage (String)
 du = /usr/bin/du
 ; location of postconf (String)