merged changes from default(d24f094d1cb5) v0.7.x
authorPascal Volk <user@localhost.localdomain.org>
Sun, 06 Jan 2013 00:09:47 +0000
branchv0.7.x
changeset 676 2bc11dada296
parent 674 995203a101e1 (diff)
parent 675 d24f094d1cb5 (current diff)
child 677 6287cf6c6447
merged changes from default(d24f094d1cb5)
VirtualMailManager/__init__.py
VirtualMailManager/account.py
VirtualMailManager/alias.py
VirtualMailManager/aliasdomain.py
VirtualMailManager/cli/__init__.py
VirtualMailManager/cli/config.py
VirtualMailManager/cli/handler.py
VirtualMailManager/cli/main.py
VirtualMailManager/cli/subcommands.py
VirtualMailManager/common.py
VirtualMailManager/config.py
VirtualMailManager/constants.py
VirtualMailManager/domain.py
VirtualMailManager/emailaddress.py
VirtualMailManager/ext/postconf.py
VirtualMailManager/handler.py
VirtualMailManager/mailbox.py
VirtualMailManager/maillocation.py
VirtualMailManager/network.py
VirtualMailManager/password.py
VirtualMailManager/quotalimit.py
VirtualMailManager/relocated.py
VirtualMailManager/serviceset.py
VirtualMailManager/transport.py
setup.py
update_config.py
vmm
--- a/VirtualMailManager/__init__.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/__init__.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/account.py	Sun Jan 06 00:09:47 2013 +0000
@@ -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()
@@ -282,7 +271,7 @@
           The new value of the attribute.
         """
         if field not in ('name', 'password', 'note'):
-            raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
+            raise AErr(_("Unknown field: '%s'") % field, INVALID_ARGUMENT)
         self._chk_state()
         dbc = self._dbh.cursor()
         if field == 'password':
@@ -304,8 +293,8 @@
           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)
+            raise VMMError(_('PostgreSQL-based dictionary quota requires '
+                             'Dovecot >= v1.1.2.'), VMM_ERROR)
         self._chk_state()
         if quotalimit == self._qlimit:
             return
@@ -364,7 +353,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 +376,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 +395,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 +439,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 +460,17 @@
 
     Argument:
 
-    `uid` : long
+    `uid` : int
       The Account unique ID.
     `dbh` : pyPgSQL.PgSQL.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 +479,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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/alias.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/aliasdomain.py	Sun Jan 06 00:09:47 2013 +0000
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/catchall.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/__init__.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2012 - 2013, 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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/config.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/handler.py	Sun Jan 06 00:09:47 2013 +0000
@@ -63,7 +63,7 @@
         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):
@@ -74,7 +74,7 @@
         """
         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')
@@ -90,9 +90,9 @@
         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 not isinstance(password, str) or not password:
             password = read_pass()
         acc.modify('password', password)
 
--- a/VirtualMailManager/cli/main.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/main.py	Sun Jan 06 00:09:47 2013 +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 DATABASE_ERROR, EX_SUCCESS, \
+     EX_USER_INTERRUPT, INVALID_ARGUMENT
+from VirtualMailManager.cli.subcommands import RunContext, setup_parser
 
 
 _ = lambda msg: msg
@@ -28,55 +27,41 @@
     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()
-    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)
-
+def run():
+    parser = setup_parser()
+    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:
+        w_err(EX_USER_INTERRUPT, '', _('Ouch!'), '')
+    except errors.VMMError as 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(0, _('Warnings:'), *handler.get_warnings())
+            w_err(err.code, _('Error: %s') % err.msg)
+        w_err(err.code, str(err.msg, ENCODING, 'replace'))
+    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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/subcommands.py	Sun Jan 06 00:09:47 2013 +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,162 @@
 
 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)
     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
         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)
+    force = 'force' if ctx.args.force else None
+    ctx.hdlr.domain_quotalimit(ctx.args.fqdn.lower(), ctx.args.storage,
+                               ctx.args.messages, 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)
+    force = 'force' if ctx.args.force else None
+    services = ctx.args.services if ctx.args.services else []
+    ctx.hdlr.domain_services(ctx.args.fqdn.lower(), 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)
+    force = 'force' if ctx.args.force else None
+    ctx.hdlr.domain_transport(ctx.args.fqdn.lower(),
+                              ctx.args.transport.lower(), 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 +287,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 +310,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 +342,43 @@
 
 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)
+    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 +386,650 @@
                 _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
-    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)
 
 
 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.\nWhen "
+               "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.")),
+           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('-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. Without an argument, an existing '
+                    'note is removed.'))
+    do.add_argument('fqdn', help=_('a fully qualified domain name'))
+    do.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 --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 pass --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('-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\nIf no name is given, the value stored for the "
+               "account is erased.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    un.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    un.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. Without the note argument, an '
+               'existing note is removed.'))
+    uo.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    uo.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 no password was provided, vmm will prompt "
+               "for it interactively.")),
+           formatter_class=RawDescriptionHelpFormatter)
+    up.add_argument('address',
+                    help=_("an account's e-mail address (local-part@fqdn)"))
+    up.add_argument('-p', metavar='PASSWORD', dest='password',
+                    help=_("the user's 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 +1042,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 +1087,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 +1151,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 +1167,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 +1195,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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/common.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/config.py	Sun Jan 06 00:09:47 2013 +0000
@@ -8,10 +8,12 @@
     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
@@ -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
@@ -337,9 +336,9 @@
             },
             'mailbox': {
                 'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
-                               self.unicode),
+                               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': {
@@ -360,12 +359,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 +372,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 +383,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 +407,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 +419,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)
@@ -436,30 +434,30 @@
             value = self.get('misc', 'dovecot_version')
             if not VERSION_RE.match(value):
                 self._missing['misc'] = ['version: ' +
-                        _(u"Not a valid Dovecot version: '%s'") % value]
+                        _("Not a valid Dovecot version: '%s'") % value]
         # 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)
+                          _("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)
+                              _("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,14 +468,14 @@
     path = expand_path(path)
     if lisdir(path):
         return path
-    raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path))
+    raise ConfigValueError(_("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'") %
+    raise ConfigValueError(_("Unsupported database module: '%s'") %
                            get_unicode(module))
 
 
@@ -485,7 +483,7 @@
     """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 +496,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,8 +506,8 @@
     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
 
@@ -520,7 +518,7 @@
     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))
     return version_string
 
@@ -531,7 +529,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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/constants.py	Sun Jan 06 00:09:47 2013 +0000
@@ -32,8 +32,6 @@
 # exit codes
 
 EX_SUCCESS = 0
-EX_MISSING_ARGS = 1
-EX_UNKNOWN_COMMAND = 2
 EX_USER_INTERRUPT = 3
 
 
--- a/VirtualMailManager/domain.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/domain.py	Sun Jan 06 00:09:47 2013 +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
@@ -79,7 +78,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])
@@ -114,9 +113,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):
@@ -126,10 +125,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):
@@ -150,7 +149,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`
@@ -264,7 +263,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):
@@ -326,8 +325,8 @@
           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)
+            raise VMMError(_('PostgreSQL-based dictionary quota requires '
+                             'Dovecot >= v1.1.2.'), VMM_ERROR)
         self._chk_state()
         assert isinstance(quotalimit, QuotaLimit)
         if not force and quotalimit == self._qlimit:
@@ -389,7 +388,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)
@@ -406,7 +405,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
@@ -433,7 +432,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
@@ -448,7 +447,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
@@ -463,7 +462,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
@@ -500,11 +499,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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/emailaddress.py	Sun Jan 06 00:09:47 2013 +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])
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/ext/postconf.py	Sun Jan 06 00:09:47 2013 +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,7 +53,7 @@
         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.
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/handler.py	Sun Jan 06 00:09:47 2013 +0000
@@ -16,6 +16,7 @@
 import re
 
 from shutil import rmtree
+from stat import S_IRGRP, S_IROTH, S_IWGRP, S_IWOTH
 from subprocess import Popen, PIPE
 
 from VirtualMailManager.account import Account
@@ -37,7 +38,6 @@
 from VirtualMailManager.errors import \
      DomainError, NotRootError, PermissionError, VMMError
 from VirtualMailManager.mailbox import new as new_mailbox
-from VirtualMailManager.pycompat import all, any
 from VirtualMailManager.quotalimit import QuotaLimit
 from VirtualMailManager.relocated import Relocated
 from VirtualMailManager.serviceset import ServiceSet, SERVICES
@@ -51,9 +51,9 @@
 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),
 }
 
 
@@ -79,7 +79,7 @@
         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)
@@ -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,23 +127,23 @@
         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:
@@ -154,14 +156,14 @@
             try:
                 _db_mod = __import__('psycopg2')
             except ImportError:
-                raise VMMError(_(u"Unable to import database module '%s'.") %
+                raise VMMError(_("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'.") %
+                raise VMMError(_("Unable to import database module '%s'.") %
                                'pyPgSQL', VMM_ERROR)
             _db_mod = tmp.PgSQL
             self._db_connect = self._pypgsql_connect
@@ -181,7 +183,7 @@
                 dbc = self._dbh.cursor()
                 dbc.execute("SET NAMES 'UTF8'")
                 dbc.close()
-            except _db_mod.libpq.DatabaseError, err:
+            except _db_mod.libpq.DatabaseError as err:
                 raise VMMError(str(err), DATABASE_ERROR)
 
     def _psycopg2_connect(self):
@@ -198,11 +200,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 _db_mod.DatabaseError as err:
                 raise VMMError(str(err), DATABASE_ERROR)
 
     def _chk_other_address_types(self, address, exclude):
@@ -240,7 +241,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 +283,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 +294,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 +316,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 +333,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 +345,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 +382,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,9 +427,9 @@
     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):
         """Wrapper around Domain's set_quotalimit, set_transport and save."""
@@ -439,7 +440,7 @@
         else:
             dom.set_transport(Transport(self._dbh, transport=transport))
         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'),
@@ -452,11 +453,11 @@
 
     def domain_quotalimit(self, domainname, bytes_, messages=0, force=None):
         """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,
+            raise DomainError(_("Invalid argument: '%s'") % force,
                               INVALID_ARGUMENT)
         dom = self._get_domain(domainname)
         quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
@@ -469,11 +470,11 @@
         """Wrapper around Domain.update_serviceset()."""
         kwargs = dict.fromkeys(SERVICES, False)
         if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
+            raise DomainError(_("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
 
@@ -484,7 +485,7 @@
     def domain_transport(self, domainname, transport, force=None):
         """Wrapper around Domain.update_transport()"""
         if force is not None and force != 'force':
-            raise DomainError(_(u"Invalid argument: '%s'") % force,
+            raise DomainError(_("Invalid argument: '%s'") % force,
                               INVALID_ARGUMENT)
         dom = self._get_domain(domainname)
         trsp = Transport(self._dbh, transport=transport)
@@ -518,13 +519,13 @@
         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--'):
             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':
@@ -597,8 +598,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)
 
@@ -616,13 +617,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
@@ -630,13 +628,10 @@
                 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,
@@ -647,7 +642,7 @@
         """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)
@@ -670,8 +665,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(...)"""
@@ -679,7 +674,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
@@ -689,10 +684,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'
@@ -708,7 +703,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):
@@ -725,7 +720,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:'))
@@ -747,8 +742,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`
@@ -769,7 +764,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:'))
@@ -780,12 +775,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'):
@@ -806,12 +801,12 @@
 
     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,
+        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)
 
@@ -819,7 +814,7 @@
         """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)
 
@@ -827,7 +822,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)
 
@@ -835,12 +830,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_,
@@ -849,24 +844,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
@@ -874,7 +867,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)
@@ -891,8 +884,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
@@ -901,7 +894,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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/mailbox.py	Sun Jan 06 00:09:47 2013 +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[:]
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/maillocation.py	Sun Jan 06 00:09:47 2013 +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')
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/network.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/password.py	Sun Jan 06 00:09:47 2013 +0000
@@ -15,22 +15,20 @@
         schemes, encodings = list_schemes()
 """
 
+import hashlib
+
+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,7 +53,7 @@
 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):
@@ -71,8 +69,8 @@
     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)
@@ -80,33 +78,13 @@
 
 
 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,12 +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', '')
+            encrypted = b64encode(encrypted.encode()).decode()
     if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
        cfg_dget('misc.dovecot_version') < 0x20000b06:
         scheme = 'CRYPT'
@@ -194,7 +173,7 @@
         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)
 
@@ -210,15 +189,17 @@
         #       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))
+            md5.update(user.localpart.encode() + b':' +
+                       user.domainname.encode() + b':')
         else:
-            md5.update('%s::' % user)
+            raise VMMError('You will need Dovecot >= v1.2.0 for proper '
+                           'functioning digest-md5 authentication.', VMM_ERROR)
     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,12 +207,13 @@
     """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)
 
@@ -240,7 +222,7 @@
     """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,80 +230,72 @@
 
 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),
     'CRYPT': (_crypt_hash, 0x10000f00),
@@ -359,11 +333,8 @@
     be empty.
     """
     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') if dcv >= 0x10100a01 else ()
     return schemes, encodings
 
 
@@ -382,23 +353,23 @@
       * 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)
+            raise VMMError(_('Encoding suffixes for password schemes require '
+                             '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 +384,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:
--- a/VirtualMailManager/pycompat/__init__.py	Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2013, 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	Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2013, 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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/quotalimit.py	Sun Jan 06 00:09:47 2013 +0000
@@ -9,8 +9,6 @@
     for domains and accounts.
 """
 
-from VirtualMailManager.pycompat import all
-
 _ = lambda msg: msg
 
 
@@ -34,7 +32,7 @@
 
         `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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/relocated.py	Sun Jan 06 00:09:47 2013 +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	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/serviceset.py	Sun Jan 06 00:09:47 2013 +0000
@@ -19,7 +19,7 @@
 
     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
@@ -65,12 +65,12 @@
         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,13 +101,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()))
+               (k, str(v).upper()) for k, v in self._services.items()))
         if self._sieve_col == 'managesieve':
             sql = sql.replace('sieve', self._sieve_col)
         dbc = self._dbh.cursor()
@@ -131,10 +131,7 @@
         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[key] = True if value else False
 
     def _save(self):
         """Store a new service_set in the database."""
--- a/VirtualMailManager/transport.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/transport.py	Sun Jan 06 00:09:47 2013 +0000
@@ -9,8 +9,6 @@
     domains and accounts.
 """
 
-from VirtualMailManager.pycompat import any
-
 _ = lambda msg: msg
 
 
@@ -26,7 +24,7 @@
 
         Keyword arguments:
         dbh -- a pyPgSQL.PgSQL.connection
-        tid -- the id of a transport (int/long)
+        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/pgsql/set-permissions.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/pgsql/set-permissions.py	Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # coding: utf-8
 # Copyright 2012, Pascal Volk
 # See COPYING for distribution information.
@@ -119,10 +119,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()
--- a/setup.py	Sat Jan 05 23:49:42 2013 +0000
+++ b/setup.py	Sun Jan 06 00:09:47 2013 +0000
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 # Copyright 2007 - 2013, Pascal Volk
 # See COPYING for distribution information.
 
 import os
 from distutils.core import setup
-from distutils.dist import DistributionMetadata
 
 VERSION = '0.6.1'
 
@@ -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	Sat Jan 05 23:49:42 2013 +0000
+++ b/update_config.py	Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: UTF-8 -*-
 # Copyright (c) 2008 - 2013, 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/vmm	Sat Jan 05 23:49:42 2013 +0000
+++ b/vmm	Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: UTF-8 -*-
 # Copyright 2007 - 2013, Pascal Volk
 # See COPYING for distribution information.
@@ -15,4 +15,4 @@
     # Otherwise just remove /usr/local/sbin from sys.path
     sys.path.remove(sys.path[0])
     from VirtualMailManager.cli.main import run
-    sys.exit(run(sys.argv))
+    sys.exit(run())