Merged changes from v0.6.x(28230a8230bf).
authorPascal Volk <user@localhost.localdomain.org>
Thu, 28 Jun 2012 19:26:50 +0000
changeset 571 a4aead244f75
parent 465 c0e1fb1b0145 (current diff)
parent 570 28230a8230bf (diff)
child 572 3238c58d01ae
Merged changes from v0.6.x(28230a8230bf).
VirtualMailManager/Account.py
VirtualMailManager/Alias.py
VirtualMailManager/AliasDomain.py
VirtualMailManager/Config.py
VirtualMailManager/Domain.py
VirtualMailManager/EmailAddress.py
VirtualMailManager/Exceptions.py
VirtualMailManager/MailLocation.py
VirtualMailManager/Relocated.py
VirtualMailManager/Transport.py
VirtualMailManager/VirtualMailManager.py
VirtualMailManager/constants/ERROR.py
VirtualMailManager/constants/EXIT.py
VirtualMailManager/constants/VERSION.py
VirtualMailManager/constants/__init__.py
VirtualMailManager/ext/Postconf.py
pgsql/create_optional_types_and_functions-dovecot-1.2.x.pgsql
pgsql/create_optional_types_and_functions.pgsql
pgsql/update_tables_0.4.x-0.5.pgsql
pgsql/update_tables_0.5.x_for_dovecot-1.2.x.pgsql
pgsql/update_types_and_functions_0.5.x_for_dovecot-1.2.x.pgsql
postfix/pgsql-transport.cf
update_config_0.4.x-0.5.py
--- a/.hgignore	Mon Nov 07 03:22:15 2011 +0000
+++ b/.hgignore	Thu Jun 28 19:26:50 2012 +0000
@@ -5,3 +5,12 @@
 *.py?
 .*.swp
 .swp
+doc/build
+build
+debian/vmm
+debian/*.log
+debian/*.debhelper
+debian/files
+debian/*substvars
+.pc
+*.egg-info
--- a/COPYING	Mon Nov 07 03:22:15 2011 +0000
+++ b/COPYING	Thu Jun 28 19:26:50 2012 +0000
@@ -1,4 +1,4 @@
-Copyright (c) 2007 - 2010, Pascal Volk
+Copyright (c) 2007 - 2012, Pascal Volk
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Configure.Dovecot_2	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,205 @@
+# This document contains a minimal configuration for a vmm setup with
+# Dovecot v2.x.
+#
+# You could save this file as local.conf in the dovecot configuration directory
+# (commonly /etc/dovecot or /usr/local/etc/dovecot).
+# When you want to use this file as your configuration file for Dovecot, make
+# sure you have commented out the line "!include conf.d/*.conf". The last line
+# "!include_try local.conf" is sufficient.
+#
+# Otherwise you have to apply the following settings to the configuration files
+# in the conf.d directory.
+
+### 
+#  dovecot.conf
+###
+protocols = imap lmtp
+# uncomment if your users should be able to manage their sieve scripts
+#protocols = imap lmtp sieve
+
+# uncomment if you want to use the quota plugin
+#dict {
+#  quota = pgsql:/usr/local/etc/dovecot/dovecot-dict-sql.conf.ext
+#}
+
+###
+# conf.d/10-auth.conf
+###
+auth_mechanisms = plain login cram-md5
+passdb {
+  driver = sql
+  args = /usr/local/etc/dovecot/dovecot-sql.conf.ext
+}
+userdb {
+  driver = sql
+  args = /usr/local/etc/dovecot/dovecot-sql.conf.ext
+}
+#!include auth-system.conf.ext
+
+###
+# conf.d/10-mail.conf
+###
+first_valid_gid = 70000
+first_valid_uid = 70000
+mail_access_groups = dovemail
+mail_location = maildir:~/Maildir
+
+# uncomment if you want to use the quota plugin
+#mail_plugins = quota
+
+###
+# conf.d/10-master.conf
+###
+
+# if you don't want to use secure imap, you have to disable the imaps listener
+##service imap-login {
+##  inet_listener imaps {
+##    port = 0
+##  }
+##}
+
+service lmtp {
+  unix_listener /var/spool/postfix/private/dovecot-lmtp {
+    user = postfix
+    group = postfix
+    mode = 0600
+  }
+}
+
+service auth {
+  user = doveauth
+  unix_listener auth-userdb {
+  }
+  unix_listener /var/spool/postfix/private/dovecot-auth {
+    user = postfix
+    group = postfix
+    mode = 0600
+  }
+}
+
+service auth-worker {
+  unix_listener auth-worker {
+    user = doveauth
+    group = $default_internal_user
+    mode = 0660
+  }
+  user = doveauth
+}
+
+service dict {
+  unix_listener dict {
+    group = dovemail
+    mode = 0660
+  }
+}
+
+###
+# conf.d/10-ssl.conf
+###
+# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
+#ssl = yes
+
+ssl_cert = </etc/ssl/certs/dovecot.pem
+ssl_key = </etc/ssl/private/dovecot.pem
+
+# if you want to disable SSL/TLS, you have set 'ssl = no' and disable the
+# imaps listener in conf.d/10-master.conf
+
+###
+# conf.d/15-lda.conf
+###
+postmaster_address = postmaster@YOUR-DOMAIN.TLD
+recipient_delimiter = +
+protocol lda {
+  # uncomment if you want to use the quota plugin
+  #mail_plugins = $mail_plugins
+  # uncomment if you want to use the quota and sieve plugins
+  #mail_plugins = $mail_plugins sieve
+}
+
+###
+# conf.d/20-imap.conf
+###
+protocol imap {
+  # uncomment if you want to use the quota plugin
+  #mail_plugins = $mail_plugins imap_quota
+}
+
+###
+# conf.d/20-lmtp.conf
+###
+protocol lmtp {
+  # uncomment if you want to use the quota plugin
+  #mail_plugins = $mail_plugins
+  # uncomment if you want to use the quota and sieve plugins
+  #mail_plugins = $mail_plugins sieve
+}
+
+###
+# conf.d/90-quota.conf
+###
+# uncomment if you want to use the quota plugin
+#plugin {
+#  quota = dict:user:%{uid}::proxy::quota
+#  quota_rule = *:storage=0:messages=0
+#  quota_rule2 = Trash:storage=+100M
+#}
+
+###
+# conf.d/90-sieve.conf
+###
+# uncomment if you want to use sieve (and maybe managesieve)
+#plugin {
+#  recipient_delimiter = +
+#  sieve = ~/.dovecot.sieve
+#  sieve_dir = ~/sieve
+#}
+
+###############################################################################
+#			    end of local configuration			      #
+###############################################################################
+
+
+###
+# etc/dovecot/dovecot-sql.conf.ext
+###
+# apply this settings to your dovecot-sql.conf.ext
+
+#driver = pgsql
+#connect = host=localhost dbname=mailsys user=dovecot password=$Dovecot_PASS
+#
+#password_query = \
+# SELECT userid AS "user", password FROM dovecotpassword('%Ln', '%Ld') WHERE %Ls
+#
+## uncomment this user_query if you want to use the quota plugin
+#user_query = \
+# SELECT home, uid, gid, mail, quota_rule FROM dovecotquotauser('%Ln', '%Ld')
+## otherwise uncomment the following user_query
+#user_query = SELECT home, uid, gid, mail FROM dovecotuser('%Ln', '%Ld')
+#
+#iterate_query = \
+# SELECT local_part AS username, domain_name.domainname AS domain \
+#   FROM users \
+#        LEFT JOIN domain_data USING (gid) \
+#        LEFT JOIN domain_name USING (gid)
+
+
+###
+# etc/dovecot/dovecot-dict-sql.conf.ext
+###
+# if you want to use the quota plugin add this lines to your
+#  dovecot-dict-sql.conf.ext
+
+#connect = host=localhost dbname=mailsys user=dovecot password=$Dovecot_PASS
+#map {
+#  pattern = priv/quota/storage
+#  table = userquota
+#  username_field = uid
+#  value_field = bytes
+#}
+#map {
+#  pattern = priv/quota/messages
+#  table = userquota
+#  username_field = uid
+#  value_field = messages
+#}
--- a/INSTALL	Mon Nov 07 03:22:15 2011 +0000
+++ b/INSTALL	Thu Jun 28 19:26:50 2012 +0000
@@ -1,69 +1,104 @@
 Installation Prerequisites
 You should already have installed and configured Postfix, Dovecot and
 PostgreSQL.
-You have to install Python and pyPgSQL* to use the Virtual Mail Manager.
-If you want to store the passwords as PLAIN-MD4 digest you have also to install
-python-crypto <http://www.amk.ca/python/code/crypto.html>.
+
+The Virtual Mail Manager depends on:
+    - Python (>= 2.4.0)
+    - Psycopg 2¹ or pyPgSQL²
+
+If you are using Python <= 2.5.0:
+    - if you want to store your users' passwords as PLAIN-MD4 digest in
+      the database, vmm will try to use Crypto.Hash.MD4 from PyCrypto³.
+    - if you are using Dovecot >= v1.1.0 and you want to store your users'
+      passwords as SHA256 or SSHA256 hashes, vmm will try to use
+      Crypto.Hash.SHA256 from PyCrypto². For SHA256/SSHA256 you should have
+      at least use PyCrypto in version 2.1.0alpha1.
+
+    When the Crypto.Hash module couldn't be imported, vmm will use
+    dovecotpw/doveadm, if the misc.password_scheme setting in the vmm.cfg
+    is set to PLAIN-MD4, SHA256 or SSHA256
 
-* = http://pypgsql.sourceforge.net/ (Debian: python-pgsql)
+[1] Psycopg: <http://initd.org/psycopg/> (Debian: python-psycopg2)
+[2] pyPgSQL: <http://pypgsql.sourceforge.net/> (Debian: python-pgsql)
+[3] PyCrypto: <http://www.pycrypto.org/> (Debian: python-crypto)
+
+
+Create additionally a user and groups for improved security
+  We will create the system user `doveauth'. This user is used in the
+  authentication process. On a Debian GNU/Linux System use this command:
+
+	adduser --system --home /nonexistent --no-create-home --group \
+	--disabled-login --gecos "Dovecot IMAP/POP3 authentication user" \
+	doveauth
+
+  This will create the doveauth user and group.
+  For Dovecot >= 2.0 we create also the group `dovemail'. Dovecot will assign
+  this group to all Dovecot processes.
+  On a Debian GNU/Linux bases system run:
+
+	addgroup --system dovemail
 
 
 Configuring PostgreSQL
+(for more details see: http://vmm.localdomain.org/PreparingPostgreSQL)
 
-* /etc/postgresql/8.2/main/pg_hba.conf
+* /etc/postgresql/8.4/main/pg_hba.conf
+  [ if you prefer to connect via TCP/IP ]
     # IPv4 local connections:
     host    mailsys     +mailsys    127.0.0.1/32          md5
+  [ if you want to connect through a local Unix-domain socket ]
+    # "local" is for Unix domain socket connections only
+    local   mailsys     +mailsys                          md5
 
     # reload configuration
-    /etc/init.d/postgresql-8.2 force-reload
+    /etc/init.d/postgresql-8.4 force-reload
 
-* Create a DB user if necessary:
-    DB Superuser:
+* Create a database superuser if necessary:
+    # as root run: su - postgres
+    # if you have sudo privileges run: sudo su - postgres
+    # create your superuser, which will be able to create users and databases
     createuser -s -d -r -E -e -P $USERNAME
-    DB User:
-    createuser -d -E -e -P $USERNAME
 
-* Create Database and db users for Postfix and Dovecot
+* As superuser create the database and db users for vmm, Postfix and Dovecot
     connecting to PostgreSQL:
     psql template1
 
-    # create database
-    CREATE DATABASE mailsys ENCODING 'UTF8';
-    # connect to the new database
-    \c mailsys
-    # either import the database structure for Dovecot v1.0.x/v1.1.x
-    \i /path/to/create_tables.pgsql
-    # or import the database structure for Dovecot v1.2.x
-    \i /path/to/create_tables-dovecot-1.2.x.pgsql
+    # create users, group and the database
+    CREATE ROLE vmm LOGIN ENCRYPTED PASSWORD 'DB PASSWORD for vmm';
+    CREATE ROLE dovecot LOGIN ENCRYPTED password 'DB PASSWORD for Dovecot';
+    CREATE ROLE postfix LOGIN ENCRYPTED password 'DB PASSWORD for Postfix';
+    CREATE ROLE mailsys WITH USER postfix, dovecot, vmm;
+    CREATE DATABASE mailsys WITH OWNER vmm ENCODING 'UTF8';
+    \q
 
-    # create users and group
-    CREATE USER postfix ENCRYPTED password 'DB PASSWORD for Postfix';
-    CREATE USER dovecot ENCRYPTED password 'DB PASSWORD for Dovecot';
-    CREATE ROLE mailsys WITH USER postfix, dovecot;
-
-    # set permissions
-    GRANT SELECT ON dovecot_password, dovecot_user TO dovecot;
-    GRANT SELECT ON postfix_alias, postfix_gid, postfix_maildir,
-    postfix_relocated, postfix_transport, postfix_uid TO postfix;
-
+    # connect to the new database
+    psql mailsys vmm -W -h 127.0.0.1
+    # either import the database structure for Dovecot v1.0.x/v1.1.x
+    \i vmm-y.x.z/pgsql/create_tables.pgsql
+    # or import the database structure for Dovecot v1.2.x/v2.x
+    \i vmm-x.y.z/pgsql/create_tables-dovecot-1.2.x.pgsql
     # leave psql
     \q
 
+    # set permissions for your Dovecot and Postfix users
+    # see python set-permissions.py -h for details
+    python vmm-x.y.z/pgsql/set-permissions.py -a -H 127.0.0.1 -U vmm
+
 Create directory for your mails
   mkdir /srv/mail
   cd /srv/mail/
   mkdir 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z
   chmod 771 /srv/mail
-  chgrp -R mail /srv/mail
   chmod 751 /srv/mail/*
 
-Configuring Dovecot
+
+For Dovecot >= 2.0 read the file Configure.Dovecot_2
+Configuring Dovecot v1.x
 
 * /etc/dovecot/dovecot.conf
     # all your other settings
     #disable_plaintext_auth = no
     mail_location = maildir:~/Maildir
-    mail_privileged_group = mail
     first_valid_uid = 70000
     first_valid_gid = 70000
     protocol lda {
@@ -77,14 +112,14 @@
       userdb sql {
         args = /etc/dovecot/dovecot-sql.conf
       }
-      user = nobody
+      user = doveauth
       socket listen {
         master {
           path = /var/run/dovecot/auth-master
           mode = 0600
         }
         client {
-          path = /var/spool/postfix/private/auth
+          path = /var/spool/postfix/private/dovecot-auth
           mode = 0660
           user = postfix
           group = postfix
@@ -95,12 +130,15 @@
 * /etc/dovecot/dovecot-sql.conf
     driver = pgsql
     connect = host=localhost dbname=mailsys user=dovecot password=$Dovecot_PASS
-    default_pass_scheme = PLAIN
-    password_query = SELECT "user", password FROM dovecot_password WHERE "user"='%Lu' AND %Ls
-    user_query = SELECT home, uid, gid, 'maildir:'||mail AS mail FROM dovecot_user WHERE userid = '%Lu'
+    default_pass_scheme = CRAM-MD5
+    password_query = SELECT userid AS "user", password FROM dovecotpassword('%Ln', '%Ld') WHERE %Ls
+    user_query = SELECT home, uid, gid, mail FROM dovecotuser('%Ln', '%Ld')
 
 Provide a root SETUID copy of Dovecot's deliver agent for Postfix
 
+/!\ Only required with Dovecot v.1.x.
+    With Dovecot >= v2.0 use Dovecot's lmtp!
+
     mkdir -p /usr/local/lib/dovecot
     chmod 700 /usr/local/lib/dovecot
     chown nobody /usr/local/lib/dovecot
@@ -114,10 +152,11 @@
 
 
 Configuring Postfix's master.cf
-
+    
+/!\ Only required with Dovecot v.1.x.
     # Add Dovecot's deliver agent
     dovecot   unix  -       n       n       -       -       pipe
-      flags=DRhu user=nobody argv=/usr/local/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+      flags=DORhu user=nobody argv=/usr/local/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
 
 
 
@@ -125,23 +164,28 @@
     # relocated users from the database
     #relocated_maps = pgsql:/etc/postfix/pgsql-relocated_maps.cf
 
+    # transport settings from our database
+    transport_maps = pgsql:/etc/postfix/pgsql-transport_maps.cf
+
     # virtual domains
     virtual_mailbox_domains = pgsql:/etc/postfix/pgsql-virtual_mailbox_domains.cf
     virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual_alias_maps.cf
-    transport_maps = pgsql:/etc/postfix/pgsql-transport.cf
     virtual_minimum_uid = 70000
     virtual_uid_maps = pgsql:/etc/postfix/pgsql-virtual_uid_maps.cf
     virtual_gid_maps = pgsql:/etc/postfix/pgsql-virtual_gid_maps.cf
     virtual_mailbox_base = /
     virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-virtual_mailbox_maps.cf
 
-    # dovecot LDA
-    dovecot_destination_recipient_limit = 1
-    virtual_transport = dovecot:
+    # dovecot LDA (only recommended with Dovecot v1.x)
+    #dovecot_destination_recipient_limit = 1
+    #virtual_transport = dovecot:
+
+    # dovecot lmtp
+    virtual_transport = lmtp:unix:private/dovecot-lmtp
 
     # dovecot SASL
     smtpd_sasl_type = dovecot
-    smtpd_sasl_path = private/auth
+    smtpd_sasl_path = private/dovecot-auth
     smtpd_sasl_auth_enable = yes
     # Keep smtpd_sasl_local_domain identical to Dovecot's auth_default_realm:
     # empty. Both are empty by default. Let it commented out.
@@ -159,17 +203,23 @@
 
 Installing the Virtual Mail Manager and configure the rest
 
-    Installing from SVN or vmm-x.y.z.tar.bz2
-    after checking out from svn or extracting the archive change into the new
-    directory and type:
+    Installing from Mercurial or vmm-x.y.z.tar.gz
+    after cloning from the hg repo or extracting the archive change into the
+    new directory and type:
         ./install.sh
     edit all the pgsql-*.cf files in /etc/postfix
 
     reload postfix
 
     # configure the Virtual Mail Manager
+    # vmm.cfg(5) - configuration file for vmm
+    #
+    # For Dovecot v1.x use 'dovecot:' as domain.transport
+    # When using Dovecot v2.x use 'lmtp:unix:private/dovecot-lmtp' as
+    # domain.transport
     vmm configure
 
     # for help type
+    # vmm(1) - command line tool to manage email domains/accounts/aliases
     vmm help
 
--- a/README	Mon Nov 07 03:22:15 2011 +0000
+++ b/README	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,151 @@
+.. -*- restructuredtext -*-
+
+============================
+vmm - a virtual mail manager
+============================
+
+Welcome to vmm!
+
+**vmm** is the easy to use and configurable command line tool for
+administrators and postmasters, to manage domains, alias-domains, accounts and
+relocated mail users. It allows the fast and easy management of mail servers.
+
+vmm is written in Python_. It's designed for installations using Dovecot_ and
+Postfix_ with a PostgreSQL_ backend.
+
+Features
+========
+• General features
+
+  ‣ Unicode/UTF-8 capable (input/storage/output)
+  ‣ supports IDN_
+  ‣ supports the mailbox format Maildir_ and Dovecot's own high-performance
+    mailbox formats single- and multi-\ dbox_
+  ‣ configurable basic mailbox structure, including sub-mailboxes
+  ‣ multilingual — currently:
+
+     * Dutch
+     * English
+     * Finnish
+     * French
+     * German
+     * Vietnamese
+
+• Domain features
+
+  ‣ configurable transport_ setting per domain
+  ‣ unique group identifier (GID) per domain
+  ‣ each domain may have one or more alias domain names
+  ‣ activate or deactivate services (SMTP, POP, IMAP and ManageSieve) for new
+    or all accounts of a domain
+  ‣ configurable quota limits (size and/or number of messages) for the
+    domain's accounts
+  ‣ supports relocated_ users
+  ‣ the postmaster account can be created automatically when a new domain is
+    created
+  ‣ supports per-domain catch-all aliases
+
+• Alias domain features
+
+  ‣ alias domain names can be switched between domains
+
+• Account features
+
+  ‣ configurable transport per account
+  ‣ activate or deactivate one/more/all services (SMTP, POP, IMAP and
+    ManageSieve) per account
+  ‣ configurable quota limit (size and/or number of messages) per user
+  ‣ per-account configuration overrides defaults defined by the domain,
+    otherwise the setting is inherited
+  ‣ unique user identifier (UID) per user
+
+• Alias features
+
+  ‣ supports multiple destinations per e-mail alias
+  ‣ destinations can be deleted separately
+  ‣ destinations can be interpolated using the original address' localpart
+    and domain, allowing aliases to have different meaning in aliasdomains,
+    e.g. with the following defined in example.org::
+
+      postmaster@example.org  →  postmaster+%d@admin.example.org
+
+    If example.com is an aliasdomain of example.org, the alias will become::
+
+      postmaster@example.org  →  postmaster+example.org@admin.example.org
+      postmaster@example.com  →  postmaster+example.com@admin.example.org
+
+Installation Prerequisites
+==========================
+You already should have installed and configured Postfix and Dovecot with
+PostgreSQL support. You also need access to a local or remote PostgreSQL
+server.
+
+To verify that your Dovecot and Postfix installation has support for
+PostgreSQL use the ``postconf`` and ``dovecot`` commands as shown below::
+
+        hostname ~ # postconf -m | grep pgsql
+        pgsql
+        hostname ~ # postconf -a | grep dovecot
+        dovecot
+        hostname ~ # dovecot --build-options | grep postgresql
+        SQL drivers: mysql postgresql sqlite
+
+vmm depends on Python (≥ 2.4.0) and Psycopg_ (≥ 2.0) or pyPgSQL_ (≥ 2.5.1).
+Psycopg and pyPgSQL are depending on parts of the *eGenix.com mx Base
+Distribution* (mxDateTime_ and mxTools_).
+
+If you are using Python ≤ 2.5.0:
+
+  • if you want to store your users' passwords as ``PLAIN-MD4`` digest in the
+    database, vmm will try to use ``Crypto.Hash.MD4`` from PyCrypto_
+  • if you are using Dovecot ≥ v1.1.0 and you want to store your users'
+    passwords as ``SHA256`` or ``SSHA256`` hashes, vmm will try to use
+    ``Crypto.Hash.SHA256`` from PyCrypto. For ``SHA256``/``SSHA256`` you
+    should have installed PyCrypto, at least in version 2.1.0alpha1.
+
+  When the Crypto.Hash module couldn't be imported, vmm will use
+  dovecotpw/doveadm, if  the *misc.password_scheme* setting in your *vmm.cfg*
+  is set to ``PLAIN-MD4``, ``SHA256`` or ``SSHA256``.
+
+Source code
+===========
+vmm's source code is available from the Mercurial_ repositories:
+
+• main repository
+
+  ‣ ``http://hg.localdomain.org/vmm``
+
+• mirror repositories
+
+  ‣ ``https://bitbucket.org/pvo/vmm``
+  ‣ ``http://vmm.hg.sourceforge.net/hgweb/vmm/vmm``
+
+Released versions are also available as gzip compressed tar archives at:
+https://sourceforge.net/projects/vmm/files/vmm/
+
+Installation/Upgrade
+====================
+For installation or upgrading instructions read the `INSTALL` or `UPGRADE`
+file.
+
+License
+=======
+In short: "**New BSD License**" aka "3-clause license". For a few more
+details see the `COPYING` file.
+
+.. External references
+.. _dbox: http://wiki2.dovecot.org/MailboxFormat/dbox
+.. _Dovecot: http://dovecot.org/
+.. _IDN: http://en.wikipedia.org/wiki/Internationalized_domain_name
+.. _Maildir: http://wiki2.dovecot.org/MailboxFormat/Maildir
+.. _Mercurial: http://mercurial.selenic.com/
+.. _mxDateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/
+.. _mxTools: http://www.egenix.com/products/python/mxBase/mxTools/
+.. _Postfix: http://www.postfix.org/
+.. _PostgreSQL: http://www.postgresql.org/
+.. _Psycopg: http://initd.org/psycopg/
+.. _PyCrypto: http://www.pycrypto.org/
+.. _pyPgSQL: http://pypgsql.sourceforge.net/
+.. _Python: http://www.python.org/
+.. _relocated: http://www.postfix.org/relocated.5.html
+.. _transport: http://www.postfix.org/transport.5.html
--- a/TODO	Mon Nov 07 03:22:15 2011 +0000
+++ b/TODO	Thu Jun 28 19:26:50 2012 +0000
@@ -1,4 +1,3 @@
-
 - Aliases
     - avoid looping aliases
 
@@ -8,3 +7,33 @@
         + aliases
         + destinations/alias
         + alias domains
+
+Database:
+   public.users.digestmd5: add "character varying(48)"
+	Outlook will love it. (`doveadm pw -s DIGEST-MD5.hex -p 1 -u 0`)
+
+- Non-root usage [madduck@madduck.net]:
+    - Provide /usr/share/vmm/vmm-{mkdirs,rmdirs,du} setuid wrappers that do
+      precisely what they have to and no more. The should probably even call
+      /usr/share/vmm/vmm-wrapper-helper as unprivileged user to parse the
+      arguments, match them with the database and obtain the actual data to
+      process, e.g. email@add.ress → home directory mapping from the DB, or
+      obtaining the set of precreatable maildirs from the config.
+
+    - configset and configure need root, it is questionable whether these
+      can/should be wrapped, as they will be disabled anyway for Debian, and
+      they are root-like activities (unlike day-to-day postmaster work).
+
+    - Pascal suggested to use hooks:
+      07 23:36 <Faxe> zwei config settings handle_dir in domain und account
+      07 23:37 <Faxe> und dann post-{domain,user}add scripte, die den wrapper aufrufen
+
+- relay_domains management [madduck@madduck.net]:
+    - should be in a separate table
+
+- default aliases [madduck@madduck.net]
+    - it should be possible to define a set of default aliases and their
+      destinations for a domain, e.g. postmaster@ and abuse@. For most
+      flexibility, there ought to be a m:n table connecting domains to sets of
+      default aliases. These aliases then get treated like normal aliases
+      unless they are overridden by a real alias.
--- a/UPGRADE	Mon Nov 07 03:22:15 2011 +0000
+++ b/UPGRADE	Thu Jun 28 19:26:50 2012 +0000
@@ -1,59 +1,83 @@
-If you still have installed vmm 0.3.x you have to proceed this step first:
+If you still have installed vmm 0.4.x you have to proceed this step first:
 
-    * upgrade your vmm installation to version 0.4-r41
+    * upgrade your vmm installation to version 0.5.2
 
 
-If you have installed vmm 0.4/0.4-r41 you have to proceed this steps:
+If you have installed vmm 0.5.2 you have to proceed this steps:
 
     * stop Postfix and Dovecot
     * backup/dump your database.
     * backup/dump your database!
 
     * start psql and connect to the appropriate database
-      (ex. psql mailsys mailsys vmm -W -h localhost)
-    * update the database: \i update_tables_0.4.x-0.5.pgsql
-    * GRANT SELECT ON postfix_alias TO postfix;
-                                       ^^^^^^^^ <- your Postfix db user
+      (ex. psql mailsys vmm -W -h 127.0.0.1)
+    * update the database,
+      - Dovecot < 1.2.0
+	\i vmm-x.y.z/pgsql/update_tables_0.5.x-0.6.pgsql
+      - Dovecot >= 1.2.0, 2.0.0 and 2.1.0
+	\i vmm-x.y.z/pgsql/update_tables_0.5.x-0.6-dovecot-1.2.x.pgsql
+    * Set database permissions.
+      (see python set-permissions.py -h for details)
+      python vmm-x.y.z/pgsql/set-permissions.py -a -H 127.0.0.1 -U vmm
+
+
+    /!\ Important note /!\
+
+    All the views (dovecot_* and postfix_*) have been replaced by database
+    functions. So you have to adjust all your postfix/pgsql-*.cf files and
+    also your /etc/dovecot/dovecot-sql.conf or
+    /etc/dovecot/dovecot-sql.conf.ext.
+    (See the vmm-x.y.z postfix/pgsql-*.cf files and INSTALL/Configure.Dovecot_2
+    files for the new query.)
 
 
-  Dovecot v1.2.x
-    -> Are you already using Dovecot v1.2.x?
-       * update the database for Dovecot v1.2.x:
-         \i update_tables_0.5.x_for_dovecot-1.2.x.pgsql
-       * GRANT SELECT ON dovecot_password TO dovecot;
-                                             ^^^^^^^^ <- your Dovecot db user
+    * execute upgrade.sh
+      This will also upgrade your vmm.cfg and apply the following modifications:
 
-       * When you are using the SQL function »dovecotpassword()« in your
-         dovecot-sql.conf, update it also:
-         \i update_types_and_functions_0.5.x_for_dovecot-1.2.x.pgsql
+	     old			     new
+	------------------------------------------------------------
+	domdir.mode		->	domain.directory_mode
+	domdir.delete		->	domain.delete_directory
+	domdir.base		->	misc.base_directory
+	domdir			->	_section domdir deleted_
+
+	maildir.mode		->	account.directory_mode
+	maildir.diskusage	->	account.disk_usage
+	maildir.delete		->	account.delete_directory
+	maildir.folders		->	mailbox.folders
+	maildir.name		->	mailbox.root
+	maildir			->	_section maildir deleted_
 
-       * You have also to adjust the permissions of the set-uid deliver copy:
-         on BSD:
-           chgrp nobody /usr/local/lib/dovecot/deliver
-         on Linux:
-           chgrp nogroup /usr/local/lib/dovecot/deliver
-         chmod u+s,o-rwx /usr/local/lib/dovecot/deliver
+	misc.forcedel		->	domain.force_deletion
+	misc.transport		->	domain.transport
+	misc.passwdscheme	->	misc.password_scheme
+	misc.dovecotvers	->	misc.dovecot_version (12 -> 1.2.11)
+	misc.gid_mail		->	/dev/null
+
+	services.smtp		->	domain.smtp
+	services.pop3		->	domain.pop3
+	services.imap		->	domain.imap
+	services.sieve		->	domain.sieve
+	services		->	_section services deleted_
 
-       * Check the master.cf from Postfix.
-         In prior setups the service dovecot was configured slightly different.
-         If you have set the command attribute 'user' to 'nobody:mail', so
-         either remove the group mail or replace it with the group nobody
-         or nogroup.
+	_NEW_.random_password	->	account.random_password
+	_NEW_.password_length	->	account.password_length
+	_NEW_.auto_postmaster	->	domain.auto_postmaster
+	_NEW_.quota_bytes	->	domain.quota_bytes
+	_NEW_.quota_messages	->	domain.quota_messages
+	_NEW_.module		->	database.module
+	_NEW_.port		->	database.port
+	_NEW_.sslmode		->	database.sslmode
+	_NEW_.format		->	mailbox.format
+	_NEW_.crypt_blowfish_rounds ->	misc.crypt_blowfish_rounds
+	_NEW_.crypt_sha256_rounds   ->	misc.crypt_sha256_rounds
+	_NEW_.crypt_sha512_rounds   ->	misc.crypt_sha512_rounds
 
-         e.g.: flags=DRhu user=nobody argv=/usr/local/lib/dovecot/deliver …
-
-    * execute upgrade.sh
-
-    * start Dovecot and Postfix
+	config.done             ->	/dev/null
+	config                  ->	_section config deleted_
 
 
-
-If you have installed vmm 0.5.x:
-
-    * execute upgrade.sh
-
-    -> Are you already using Dovecot v1.2.x?
-       See a few lines above.
+    * start Dovecot and Postfix again
 
 
 else
--- a/VirtualMailManager/Account.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,267 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's Account class to manage e-mail accounts."""
-
-from __main__ import ERR
-from Exceptions import VMMAccountException as AccE
-from Domain import Domain
-from Transport import Transport
-from MailLocation import MailLocation
-from EmailAddress import EmailAddress
-import VirtualMailManager as VMM
-
-class Account(object):
-    """Class to manage e-mail accounts."""
-    __slots__ = ('_addr','_base','_gid','_mid','_passwd','_tid','_uid','_dbh')
-    def __init__(self, dbh, address, password=None):
-        self._dbh = dbh
-        self._base = None
-        if isinstance(address, EmailAddress):
-            self._addr = address
-        else:
-            raise TypeError("Argument 'address' is not an EmailAddress")
-        self._uid = 0
-        self._gid = 0
-        self._mid = 0
-        self._tid = 0
-        self._passwd = password
-        self._setAddr()
-        self._exists()
-        if self._uid < 1 and VMM.VirtualMailManager.aliasExists(self._dbh,
-                self._addr):
-            # 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 AccE(_(u"There is already an alias with the address “%s”.") %\
-                    self._addr, ERR.ALIAS_EXISTS)
-        if self._uid < 1 and VMM.VirtualMailManager.relocatedExists(self._dbh,
-                self._addr):
-            raise AccE(
-              _(u"There is already a relocated user with the address “%s”.") %\
-                    self._addr, ERR.RELOCATED_EXISTS)
-
-    def _exists(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT uid, mid, tid FROM users \
-WHERE gid=%s AND local_part=%s",
-                self._gid, self._addr._localpart)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self._uid, self._mid, self._tid = result
-            return True
-        else:
-            return False
-
-    def _setAddr(self):
-        dom = Domain(self._dbh, self._addr._domainname)
-        self._gid = dom.getID()
-        if self._gid == 0:
-            raise AccE(_(u"The domain “%s” doesn't exist.") %\
-                    self._addr._domainname, ERR.NO_SUCH_DOMAIN)
-        self._base = dom.getDir()
-        self._tid = dom.getTransportID()
-
-    def _setID(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('users_uid')")
-        self._uid = dbc.fetchone()[0]
-        dbc.close()
-
-    def _prepare(self, maillocation):
-        self._setID()
-        self._mid = MailLocation(self._dbh, maillocation=maillocation).getID()
-
-    def _switchState(self, state, dcvers, service):
-        if not isinstance(state, bool):
-            return False
-        if not service in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
-            raise AccE(_(u"Unknown service “%s”.") % service,
-                    ERR.UNKNOWN_SERVICE)
-        if self._uid < 1:
-            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_ACCOUNT)
-        if dcvers > 11:
-            sieve_col = 'sieve'
-        else:
-            sieve_col = 'managesieve'
-        if service in ('smtp', 'pop3', 'imap'):
-            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (service, state,
-                    self._uid)
-        elif service == 'sieve':
-            sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col,
-                    state, self._uid)
-        else:
-            sql = 'UPDATE users SET smtp = %(s)s, pop3 = %(s)s, imap = %(s)s,\
- %(col)s = %(s)s WHERE uid = %(uid)d' % {
-                's': state, 'col': sieve_col, 'uid': self._uid}
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        dbc.close()
-
-    def __aliaseCount(self):
-        dbc = self._dbh.cursor()
-        q = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
-            %self._addr
-        dbc.execute(q)
-        a_count = dbc.fetchone()[0]
-        dbc.close()
-        return a_count
-
-    def setPassword(self, password):
-        self._passwd = password
-
-    def getUID(self):
-        return self._uid
-
-    def getGID(self):
-        return self._gid
-
-    def getDir(self, directory):
-        if directory == 'domain':
-            return '%s' % self._base
-        elif directory == 'home':
-            return '%s/%i' % (self._base, self._uid)
-
-    def enable(self, dcvers, service=None):
-        self._switchState(True, dcvers, service)
-
-    def disable(self, dcvers, service=None):
-        self._switchState(False, dcvers, service)
-
-    def save(self, maillocation, dcvers, smtp, pop3, imap, sieve):
-        if self._uid < 1:
-            if dcvers > 11:
-                sieve_col = 'sieve'
-            else:
-                sieve_col = 'managesieve'
-            self._prepare(maillocation)
-            sql = "INSERT INTO users (local_part, passwd, uid, gid, mid, tid,\
- smtp, pop3, imap, %s) VALUES ('%s', '%s', %d, %d, %d, %d, %s, %s, %s, %s)" % (
-                sieve_col, self._addr._localpart, self._passwd, self._uid,
-                self._gid, self._mid, self._tid, smtp, pop3, imap, sieve)
-            dbc = self._dbh.cursor()
-            dbc.execute(sql)
-            self._dbh.commit()
-            dbc.close()
-        else:
-            raise AccE(_(u'The account “%s” already exists.') % self._addr,
-                    ERR.ACCOUNT_EXISTS)
-
-    def modify(self, what, value):
-        if self._uid == 0:
-            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_ACCOUNT)
-        if what not in ['name', 'password', 'transport']:
-            return False
-        dbc = self._dbh.cursor()
-        if what == 'password':
-            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
-                    value, self._uid)
-        elif what == 'transport':
-            self._tid = Transport(self._dbh, transport=value).getID()
-            dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
-                    self._tid, self._uid)
-        else:
-            dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
-                    value, self._uid)
-        if dbc.rowcount > 0:
-            self._dbh.commit()
-        dbc.close()
-
-    def getInfo(self, dcvers):
-        if dcvers > 11:
-            sieve_col = 'sieve'
-        else:
-            sieve_col = 'managesieve'
-        sql = 'SELECT name, uid, gid, mid, tid, smtp, pop3, imap, %s\
- FROM users WHERE uid = %d' % (sieve_col, self._uid)
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        info = dbc.fetchone()
-        dbc.close()
-        if info is None:
-            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_ACCOUNT)
-        else:
-            keys = ['name', 'uid', 'gid', 'maildir', 'transport', 'smtp',
-                    'pop3', 'imap', sieve_col]
-            info = dict(zip(keys, info))
-            for service in ('smtp', 'pop3', 'imap', sieve_col):
-                if bool(info[service]):
-                    # TP: A service (pop3/imap/…) is enabled/usable for a user
-                    info[service] = _('enabled')
-                else:
-                    # TP: A service (pop3/imap) isn't enabled/usable for a user
-                    info[service] = _('disabled')
-            info['address'] = self._addr
-            info['maildir'] = '%s/%s/%s' % (self._base, info['uid'],
-                    MailLocation(self._dbh,
-                        mid=info['maildir']).getMailLocation())
-            info['transport'] = Transport(self._dbh,
-                    tid=info['transport']).getTransport()
-            return info
-
-    def getAliases(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT address ||'@'|| domainname FROM alias, domain_name\
- WHERE destination = %s AND domain_name.gid = alias.gid\
- AND domain_name.is_primary ORDER BY address", str(self._addr))
-        addresses = dbc.fetchall()
-        dbc.close()
-        aliases = []
-        if len(addresses) > 0:
-            aliases = [alias[0] for alias in addresses]
-        return aliases
-
-    def delete(self, delalias):
-        if self._uid < 1:
-            raise AccE(_(u"The account “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_ACCOUNT)
-        dbc = self._dbh.cursor()
-        if delalias == 'delalias':
-            dbc.execute('DELETE FROM users WHERE uid= %s', self._uid)
-            u_rc = dbc.rowcount
-            # delete also all aliases where the destination address is the same
-            # as for this account.
-            dbc.execute("DELETE FROM alias WHERE destination = %s",
-                    str(self._addr))
-            if u_rc > 0 or dbc.rowcount > 0:
-                self._dbh.commit()
-        else: # check first for aliases
-            a_count = self.__aliaseCount()
-            if a_count == 0:
-                dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
-                if dbc.rowcount > 0:
-                    self._dbh.commit()
-            else:
-                dbc.close()
-                raise AccE(
-                  _(u"There are %(count)d aliases with the destination address\
- “%(address)s”.") %{'count': a_count, 'address': self._addr}, ERR.ALIAS_PRESENT)
-        dbc.close()
-
-def getAccountByID(uid, dbh):
-    try:
-        uid = long(uid)
-    except ValueError:
-        raise AccE(_(u'uid must be an int/long.'), ERR.INVALID_AGUMENT)
-    if uid < 1:
-        raise AccE(_(u'uid must be greater than 0.'), ERR.INVALID_AGUMENT)
-    dbc = dbh.cursor()
-    dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address,\
- uid, users.gid FROM users LEFT JOIN domain_name ON (domain_name.gid \
- = users.gid AND is_primary) WHERE uid = %s;", uid)
-    info = dbc.fetchone()
-    dbc.close()
-    if info is None:
-        raise AccE(_(u"There is no account with the UID “%d”.") % uid,
-                ERR.NO_SUCH_ACCOUNT)
-    keys = ['address', 'uid', 'gid']
-    info = dict(zip(keys, info))
-    return info
-
--- a/VirtualMailManager/Alias.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's Alias class to manage e-mail aliases."""
-
-from __main__ import ERR
-from Exceptions import VMMAliasException as VMMAE
-from Domain import Domain
-from EmailAddress import EmailAddress
-import VirtualMailManager as VMM
-
-class Alias(object):
-    """Class to manage e-mail aliases."""
-    __slots__ = ('_addr', '_dest', '_gid', '_isNew', '_dbh')
-    def __init__(self, dbh, address, destination=None):
-        if isinstance(address, EmailAddress):
-            self._addr = address
-        else:
-            raise TypeError("Argument 'address' is not an EmailAddress")
-        if destination is None:
-            self._dest = None
-        elif isinstance(destination, EmailAddress):
-            self._dest = destination
-        else:
-            raise TypeError("Argument 'destination' is not an EmailAddress")
-        if address == destination:
-            raise VMMAE(_(u"Address and destination are identical."),
-                ERR.ALIAS_ADDR_DEST_IDENTICAL)
-        self._dbh = dbh
-        self._gid = 0
-        self._isNew = False
-        self._setAddr()
-        if not self._dest is None:
-            self._exists()
-        if VMM.VirtualMailManager.accountExists(self._dbh, self._addr):
-            raise VMMAE(_(u"There is already an account with address “%s”.") %\
-                    self._addr, ERR.ACCOUNT_EXISTS)
-        if VMM.VirtualMailManager.relocatedExists(self._dbh, self._addr):
-            raise VMMAE(
-              _(u"There is already a relocated user with the address “%s”.") %\
-                    self._addr, ERR.RELOCATED_EXISTS)
-
-    def _exists(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT gid FROM alias WHERE gid=%s AND address=%s\
- AND destination=%s", self._gid, self._addr._localpart, str(self._dest))
-        gid = dbc.fetchone()
-        dbc.close()
-        if gid is None:
-            self._isNew = True
-
-    def _setAddr(self):
-        dom = Domain(self._dbh, self._addr._domainname)
-        self._gid = dom.getID()
-        if self._gid == 0:
-            raise VMMAE(_(u"The domain “%s” doesn't exist.") %\
-                    self._addr._domainname, ERR.NO_SUCH_DOMAIN)
-
-    def _checkExpansion(self, limit):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT count(gid) FROM alias where gid=%s AND address=%s',
-                self._gid, self._addr._localpart)
-        curEx = dbc.fetchone()[0]
-        dbc.close()
-        if curEx == limit:
-            errmsg = _(u"""Can't add new destination to alias “%(address)s”.
-Currently this alias expands into %(count)i recipients.
-One more destination will render this alias unusable.
-Hint: Increase Postfix' virtual_alias_expansion_limit
-""") % {'address': self._addr, 'count': curEx}
-            raise VMMAE(errmsg, ERR.ALIAS_EXCEEDS_EXPANSION_LIMIT)
-
-    def save(self, expansion_limit):
-        if self._dest is None:
-           raise VMMAE(_(u"No destination address specified for alias."),
-               ERR.ALIAS_MISSING_DEST)
-        if self._isNew:
-            self._checkExpansion(expansion_limit)
-            dbc = self._dbh.cursor()
-            dbc.execute("INSERT INTO alias (gid, address, destination) VALUES\
- (%s, %s, %s)", self._gid, self._addr._localpart, str(self._dest))
-            self._dbh.commit()
-            dbc.close()
-        else:
-            raise VMMAE(
-               _(u"The alias “%(a)s” with destination “%(d)s” already exists.")\
-                       % {'a': self._addr, 'd': self._dest}, ERR.ALIAS_EXISTS)
-
-    def getInfo(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT destination FROM alias WHERE gid=%s AND address=%s',
-                self._gid, self._addr._localpart)
-        destinations = dbc.fetchall()
-        dbc.close()
-        if len(destinations) > 0:
-            targets = [destination[0] for destination in destinations]
-            return targets
-        else:
-            raise VMMAE(_(u"The alias “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_ALIAS)
-
-    def delete(self):
-        dbc = self._dbh.cursor()
-        if self._dest is None:
-            dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s",
-                    self._gid, self._addr._localpart)
-        else:
-            dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \
- destination=%s", self._gid, self._addr._localpart, str(self._dest))
-        rowcount = dbc.rowcount
-        dbc.close()
-        if rowcount > 0:
-            self._dbh.commit()
-        else:
-            if self._dest is None:
-                msg = _(u"The alias “%s” doesn't exist.") % self._addr
-            else:
-                msg = _(u"The alias “%(a)s” with destination “%(d)s” doesn't\
- exist.") % {'a': self._addr, 'd': self._dest}
-            raise VMMAE(msg, ERR.NO_SUCH_ALIAS)
-
--- a/VirtualMailManager/AliasDomain.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's AliasDomain class to manage alias domains."""
-
-from __main__ import ERR
-from Exceptions import VMMAliasDomainException as VADE
-import VirtualMailManager as VMM
-
-class AliasDomain(object):
-    """Class to manage e-mail alias domains."""
-    __slots__ = ('__gid', '__name', '_domain', '_dbh')
-    def __init__(self, dbh, domainname, targetDomain=None):
-        self._dbh = dbh
-        self.__name = VMM.VirtualMailManager.chkDomainname(domainname)
-        self.__gid = 0
-        self._domain = targetDomain
-        self._exists()
-
-    def _exists(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT gid, is_primary FROM domain_name WHERE domainname\
- = %s', self.__name)
-        alias = dbc.fetchone()
-        dbc.close()
-        if alias is not None:
-            self.__gid, primary = alias
-            if primary:
-                raise VADE(_(u"The domain “%s” is a primary domain.") %
-                        self.__name, ERR.ALIASDOMAIN_ISDOMAIN)
-
-    def save(self):
-        if self.__gid > 0:
-            raise VADE(_(u'The alias domain “%s” already exists.') %self.__name,
-                    ERR.ALIASDOMAIN_EXISTS)
-        if self._domain is None:
-            raise VADE(_(u'No destination domain specified for alias domain.'),
-                    ERR.ALIASDOMAIN_NO_DOMDEST)
-        if self._domain._id < 1:
-            raise VADE (_(u"The target domain “%s” doesn't exist.") %
-                    self._domain._name, ERR.NO_SUCH_DOMAIN)
-        dbc = self._dbh.cursor()
-        dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary)\
- VALUES (%s, %s, FALSE)', self.__name, self._domain._id)
-        self._dbh.commit()
-        dbc.close()
-
-    def info(self):
-        if self.__gid > 0:
-            dbc = self._dbh.cursor()
-            dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s\
- AND is_primary', self.__gid)
-            domain = dbc.fetchone()
-            dbc.close()
-            if domain is not None:
-                return {'alias': self.__name, 'domain': domain[0]}
-            else:# an almost unlikely case, isn't it?
-                raise VADE(
-                    _(u'There is no primary domain for the alias domain “%s”.')\
-                            % self.__name, ERR.NO_SUCH_DOMAIN)
-        else:
-            raise VADE(_(u"The alias domain “%s” doesn't exist.") %
-                    self.__name, ERR.NO_SUCH_ALIASDOMAIN)
-
-    def switch(self):
-        if self._domain is None:
-            raise VADE(_(u'No destination domain specified for alias domain.'),
-                    ERR.ALIASDOMAIN_NO_DOMDEST)
-        if self._domain._id < 1:
-            raise VADE (_(u"The target domain “%s” doesn't exist.") %
-                    self._domain._name, ERR.NO_SUCH_DOMAIN)
-        if self.__gid < 1:
-            raise VADE(_(u"The alias domain “%s” doesn't exist.") %
-                    self.__name, ERR.NO_SUCH_ALIASDOMAIN)
-        if self.__gid == self._domain._id:
-            raise VADE(_(u"The alias domain “%(alias)s” is already assigned to\
- the domain “%(domain)s”.") %
-                    {'alias': self.__name, 'domain': self._domain._name},
-                    ERR.ALIASDOMAIN_EXISTS)
-        dbc = self._dbh.cursor()
-        dbc.execute('UPDATE domain_name SET gid = %s WHERE gid = %s\
- AND domainname = %s AND NOT is_primary',
-                self._domain._id, self.__gid, self.__name)
-        self._dbh.commit()
-        dbc.close()
-
-    def delete(self):
-        if self.__gid > 0:
-            dbc = self._dbh.cursor()
-            dbc.execute('DELETE FROM domain_name WHERE domainname = %s \
- AND NOT is_primary', self.__name)
-            if dbc.rowcount > 0:
-                self._dbh.commit()
-        else:
-            raise VADE(
-                  _(u"The alias domain “%s” doesn't exist.") % self.__name,
-                  ERR.NO_SUCH_ALIASDOMAIN)
-
--- a/VirtualMailManager/Config.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Configuration class for read, modify and write the
-configuration from Virtual Mail Manager.
-
-"""
-
-from shutil import copy2
-from ConfigParser import ConfigParser, MissingSectionHeaderError, ParsingError
-from cStringIO import StringIO
-
-from __main__ import ENCODING, ERR, w_std
-from Exceptions import VMMConfigException
-
-class Config(ConfigParser):
-    """This class is for reading and modifying vmm's configuration file."""
-
-    def __init__(self, filename):
-        """Creates a new Config instance
-
-        Arguments:
-        filename -- path to the configuration file
-        """
-        ConfigParser.__init__(self)
-        self.__cfgFileName = filename
-        self.__cfgFile = None
-        self.__VMMsections = ['database', 'maildir', 'services', 'domdir',
-                'bin', 'misc', 'config']
-        self.__changes = False
-        self.__missing = {}
-        self.__dbopts = [
-                ['host', 'localhot'],
-                ['user', 'vmm'],
-                ['pass', 'your secret password'],
-                ['name', 'mailsys']
-                ]
-        self.__mdopts = [
-                ['name', 'Maildir'],
-                ['folders', 'Drafts:Sent:Templates:Trash'],
-                ['mode', 448],
-                ['diskusage', 'false'],
-                ['delete', 'false']
-                ]
-        self.__serviceopts = [
-                ['smtp', 'true'],
-                ['pop3', 'true'],
-                ['imap', 'true'],
-                ['sieve', 'true']
-                ]
-        self.__domdopts = [
-                ['base', '/srv/mail'],
-                ['mode', 504],
-                ['delete', 'false']
-                ]
-        self.__binopts = [
-                ['dovecotpw', '/usr/sbin/dovecotpw'],
-                ['du', '/usr/bin/du'],
-                ['postconf', '/usr/sbin/postconf']
-                ]
-        self.__miscopts = [
-                ['passwdscheme', 'PLAIN'],
-                ['gid_mail', 8],
-                ['forcedel', 'false'],
-                ['transport', 'dovecot:'],
-                ['dovecotvers', '11']
-                ]
-
-    def load(self):
-        """Loads the configuration, read only.
-
-        Raises a VMMConfigException if the configuration syntax is invalid.
-        """
-        try:
-            self.__cfgFile = file(self.__cfgFileName, 'r')
-            self.readfp(self.__cfgFile)
-        except (MissingSectionHeaderError, ParsingError), e:
-            self.__cfgFile.close()
-            raise VMMConfigException(str(e), ERR.CONF_ERROR)
-        self.__cfgFile.close()
-
-    def check(self):
-        """Performs a configuration check.
-
-        Raises a VMMConfigException if the check fails.
-        """
-        if not self.__chkSections():
-            errmsg = StringIO()
-            errmsg.write(_("Using configuration file: %s\n") %\
-                    self.__cfgFileName)
-            for k,v in self.__missing.items():
-                if v[0] is True:
-                    errmsg.write(_(u"missing section: %s\n") % k)
-                else:
-                    errmsg.write(_(u"missing options in section %s:\n") % k)
-                    for o in v:
-                        errmsg.write(" * %s\n" % o)
-            raise VMMConfigException(errmsg.getvalue(), ERR.CONF_ERROR)
-
-    def getsections(self):
-        """Return a list with all configurable sections."""
-        return self.__VMMsections[:-1]
-
-    def get(self, section, option, raw=False, vars=None):
-        return unicode(ConfigParser.get(self, section, option, raw, vars),
-                ENCODING, 'replace')
-
-    def configure(self, sections):
-        """Interactive method for configuring all options in the given sections
-
-        Arguments:
-        sections -- list of strings with section names
-        """
-        if not isinstance(sections, list):
-            raise TypeError("Argument 'sections' is not a list.")
-        # if [config] done = false (default at 1st run),
-        # then set changes true
-        try:
-            if not self.getboolean('config', 'done'):
-                self.__changes = True
-        except ValueError:
-            self.set('config', 'done', 'False')
-            self.__changes = True
-        w_std(_(u'Using configuration file: %s\n') % self.__cfgFileName)
-        for s in sections:
-            if s != 'config':
-                w_std(_(u'* Config section: “%s”') % s )
-            for opt, val in self.items(s):
-                newval = raw_input(
-                _('Enter new value for option %(opt)s [%(val)s]: ').encode(
-                    ENCODING, 'replace') % {'opt': opt, 'val': val})
-                if newval and newval != val:
-                    self.set(s, opt, newval)
-                    self.__changes = True
-            print
-        if self.__changes:
-            self.__saveChanges()
-
-    def __saveChanges(self):
-        """Writes changes to the configuration file."""
-        self.set('config', 'done', 'true')
-        copy2(self.__cfgFileName, self.__cfgFileName+'.bak')
-        self.__cfgFile = file(self.__cfgFileName, 'w')
-        self.write(self.__cfgFile)
-        self.__cfgFile.close()
-
-    def __chkSections(self):
-        """Checks if all configuration sections are existing."""
-        errors = False
-        for s in self.__VMMsections:
-            if not self.has_section(s):
-                self.__missing[s] = [True]
-                errors = True
-            elif not self.__chkOptions(s):
-                errors = True
-        return not errors
-
-    def __chkOptions(self, section):
-        """Checks if all configuration options in section are existing.
-
-        Arguments:
-        section -- the section to be checked
-        """
-        retval = True
-        missing = []
-        if section == 'database':
-            opts = self.__dbopts
-        elif section == 'maildir':
-            opts = self.__mdopts
-        elif section == 'services':
-            opts = self.__serviceopts
-        elif section == 'domdir':
-            opts = self.__domdopts
-        elif section == 'bin':
-            opts = self.__binopts
-        elif section == 'misc':
-            opts = self.__miscopts
-        elif section == 'config':
-            opts = [['done', 'false']]
-        for o, v in opts:
-            if not self.has_option(section, o):
-                missing.append(o)
-                retval = False
-        if len(missing):
-            self.__missing[section] = missing
-        return retval
--- a/VirtualMailManager/Domain.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's Domain class to manage e-mail domains."""
-
-from random import choice
-
-from __main__ import ERR
-from Exceptions import VMMDomainException as VMMDE
-import VirtualMailManager as VMM
-from Transport import Transport
-
-MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
-
-class Domain(object):
-    """Class to manage e-mail domains."""
-    __slots__ = ('_basedir','_domaindir','_id','_name','_transport','_dbh')
-    def __init__(self, dbh, domainname, basedir=None, transport=None):
-        """Creates a new Domain instance.
-
-        Keyword arguments:
-        dbh -- a pyPgSQL.PgSQL.connection
-        domainname -- name of the domain (str)
-        transport -- default vmm.cfg/misc/transport  (str)
-        """
-        self._dbh = dbh
-        self._name = VMM.VirtualMailManager.chkDomainname(domainname)
-        self._basedir = basedir
-        if transport is not None:
-            self._transport = Transport(self._dbh, transport=transport)
-        else:
-            self._transport = transport
-        self._id = 0
-        self._domaindir = None
-        if not self._exists() and self._isAlias():
-            raise VMMDE(_(u"The domain “%s” is an alias domain.") %self._name,
-                    ERR.DOMAIN_ALIAS_EXISTS)
-
-    def _exists(self):
-        """Checks if the domain already exists.
-
-        If the domain exists _id will be set and returns True, otherwise False
-        will be returned.
-        """
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT gid, tid, domaindir FROM domain_data WHERE gid =\
- (SELECT gid FROM domain_name WHERE domainname = %s AND is_primary)",
-                self._name)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self._id, self._domaindir = result[0], result[2]
-            self._transport = Transport(self._dbh, tid=result[1])
-            return True
-        else:
-            return False
-
-    def _isAlias(self):
-        """Checks if self._name is known for an alias domain."""
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT is_primary FROM domain_name WHERE domainname = %s',
-                self._name)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None and not result[0]:
-            return True
-        else:
-            return False
-
-    def _setID(self):
-        """Sets the ID of the domain."""
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('domain_gid')")
-        self._id = dbc.fetchone()[0]
-        dbc.close()
-
-    def _prepare(self):
-        self._setID()
-        self._domaindir = "%s/%s/%i" % (self._basedir, choice(MAILDIR_CHARS),
-                self._id)
-
-    def _has(self, what):
-        """Checks if aliases or accounts are assigned to the domain.
-
-        If there are assigned accounts or aliases True will be returned,
-        otherwise False will be returned.
-
-        Keyword arguments:
-        what -- 'alias' or 'users' (strings)
-        """
-        if what not in ['alias', 'users']:
-            return False
-        dbc = self._dbh.cursor()
-        if what == 'users':
-            dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._id)
-        else:
-            dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._id)
-        count = dbc.fetchone()
-        dbc.close()
-        if count[0] > 0:
-            return True
-        else:
-            return False
-
-    def _chkDelete(self, delUser, delAlias):
-        """Checks dependencies for deletion.
-
-        Keyword arguments:
-        delUser -- ignore available accounts (bool)
-        delAlias -- ignore available aliases (bool)
-        """
-        if not delUser:
-            hasUser = self._has('users')
-        else:
-            hasUser = False
-        if not delAlias:
-            hasAlias = self._has('alias')
-        else:
-            hasAlias = False
-        if hasUser and hasAlias:
-            raise VMMDE(_(u'There are accounts and aliases.'),
-                ERR.ACCOUNT_AND_ALIAS_PRESENT)
-        elif hasUser:
-            raise VMMDE(_(u'There are accounts.'),
-                ERR.ACCOUNT_PRESENT)
-        elif hasAlias:
-            raise VMMDE(_(u'There are aliases.'),
-                ERR.ALIAS_PRESENT)
-
-    def save(self):
-        """Stores the new domain in the database."""
-        if self._id < 1:
-            self._prepare()
-            dbc = self._dbh.cursor()
-            dbc.execute("INSERT INTO domain_data (gid, tid, domaindir)\
- VALUES (%s, %s, %s)", self._id, self._transport.getID(), self._domaindir)
-            dbc.execute("INSERT INTO domain_name (domainname, gid, is_primary)\
- VALUES (%s, %s, %s)", self._name, self._id, True)
-            self._dbh.commit()
-            dbc.close()
-        else:
-            raise VMMDE(_(u'The domain “%s” already exists.') % self._name,
-                ERR.DOMAIN_EXISTS)
-
-    def delete(self, delUser=False, delAlias=False):
-        """Deletes the domain.
-
-        Keyword arguments:
-        delUser -- force deletion of available accounts (bool)
-        delAlias -- force deletion of available aliases (bool)
-        """
-        if self._id > 0:
-            self._chkDelete(delUser, delAlias)
-            dbc = self._dbh.cursor()
-            for t in ('alias','users','relocated','domain_name','domain_data'):
-                dbc.execute("DELETE FROM %s WHERE gid = %d" % (t, self._id))
-            self._dbh.commit()
-            dbc.close()
-        else:
-            raise VMMDE(_(u"The domain “%s” doesn't exist.") % self._name,
-                ERR.NO_SUCH_DOMAIN)
-
-    def updateTransport(self, transport, force=False):
-        """Sets a new transport for the domain.
-
-        Keyword arguments:
-        transport -- the new transport (str)
-        force -- True/False force new transport for all accounts (bool)
-        """
-        if self._id > 0:
-            if transport == self._transport.getTransport():
-                return
-            trsp = Transport(self._dbh, transport=transport)
-            dbc = self._dbh.cursor()
-            dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s",
-                    trsp.getID(), self._id)
-            if dbc.rowcount > 0:
-                self._dbh.commit()
-            if force:
-                dbc.execute("UPDATE users SET tid = %s WHERE gid = %s",
-                        trsp.getID(), self._id)
-                if dbc.rowcount > 0:
-                    self._dbh.commit()
-            dbc.close()
-        else:
-            raise VMMDE(_(u"The domain “%s” doesn't exist.") % self._name,
-                ERR.NO_SUCH_DOMAIN)
-
-    def getID(self):
-        """Returns the ID of the domain."""
-        return self._id
-
-    def getDir(self):
-        """Returns the directory of the domain."""
-        return self._domaindir
-
-    def getTransport(self):
-        """Returns domain's transport."""
-        return self._transport.getTransport()
-
-    def getTransportID(self):
-        """Returns the ID from the domain's transport."""
-        return self._transport.getID()
-
-    def getInfo(self):
-        """Returns a dictionary with information about the domain."""
-        sql = """\
-SELECT gid, domainname, transport, domaindir, aliasdomains, accounts,
-       aliases, relocated
-  FROM vmm_domain_info
- WHERE gid = %i""" % self._id
-        dbc = self._dbh.cursor()
-        dbc.execute(sql)
-        info = dbc.fetchone()
-        dbc.close()
-        if info is None:
-            raise VMMDE(_(u"The domain “%s” doesn't exist.") % self._name,
-                    ERR.NO_SUCH_DOMAIN)
-        else:
-            keys = ['gid', 'domainname', 'transport', 'domaindir',
-                    'aliasdomains', 'accounts', 'aliases', 'relocated']
-            return dict(zip(keys, info))
-
-    def getAccounts(self):
-        """Returns a list with all accounts from the domain."""
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT local_part from users where gid = %s ORDER BY\
- local_part", self._id)
-        users = dbc.fetchall()
-        dbc.close()
-        accounts = []
-        if len(users) > 0:
-            addr = u'@'.join
-            _dom = self._name
-            accounts = [addr((account[0], _dom)) for account in users]
-        return accounts
-
-    def getAliases(self):
-        """Returns a list with all aliases from the domain."""
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT DISTINCT address FROM alias WHERE gid = %s\
- ORDER BY address",  self._id)
-        addresses = dbc.fetchall()
-        dbc.close()
-        aliases = []
-        if len(addresses) > 0:
-            addr = u'@'.join
-            _dom = self._name
-            aliases = [addr((alias[0], _dom)) for alias in addresses]
-        return aliases
-
-    def getRelocated(self):
-        """Returns a list with all addresses from relocated users."""
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT address FROM relocated WHERE gid = %s\
- ORDER BY address", self._id)
-        addresses = dbc.fetchall()
-        dbc.close()
-        relocated = []
-        if len(addresses) > 0:
-            addr = u'@'.join
-            _dom = self._name
-            relocated = [addr((address[0], _dom)) for address in addresses]
-        return relocated
-
-    def getAliaseNames(self):
-        """Returns a list with all alias names from the domain."""
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT domainname FROM domain_name WHERE gid = %s\
- AND NOT is_primary ORDER BY domainname", self._id)
-        anames = dbc.fetchall()
-        dbc.close()
-        aliasdomains = []
-        if len(anames) > 0:
-            aliasdomains = [aname[0] for aname in anames]
-        return aliasdomains
-
-def search(dbh, pattern=None, like=False):
-    if pattern is not None and like is False:
-        pattern = VMM.VirtualMailManager.chkDomainname(pattern)
-    sql = 'SELECT gid, domainname, is_primary FROM domain_name'
-    if pattern is None:
-        pass
-    elif like:
-        sql += " WHERE domainname LIKE '%s'" % pattern
-    else:
-        sql += " WHERE domainname = '%s'" % pattern
-    sql += ' ORDER BY is_primary DESC, domainname'
-    dbc = dbh.cursor()
-    dbc.execute(sql)
-    result = dbc.fetchall()
-    dbc.close()
-
-    gids = [domain[0] for domain in result if domain[2]]
-    domains = {}
-    for gid, domain, is_primary in result:
-        if is_primary:
-            if not gid in domains:
-                domains[gid] = [domain]
-            else:
-                domains[gid].insert(0, domain)
-        else:
-            if gid in gids:
-                if gid in domains:
-                    domains[gid].append(domain)
-                else:
-                    domains[gid] = [domain]
-            else:
-                gids.append(gid)
-                domains[gid] = [None, domain]
-    return gids, domains
-
--- a/VirtualMailManager/EmailAddress.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's EmailAddress class to handle e-mail addresses."""
-
-from __main__ import re, ERR
-from Exceptions import VMMEmailAddressException as VMMEAE
-import VirtualMailManager as VMM
-
-RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
-
-class EmailAddress(object):
-    __slots__ = ('_localpart', '_domainname')
-    def __init__(self, address):
-        self._localpart = None
-        self._domainname = None
-        self.__chkAddress(address)
-
-    def __eq__(self, other):
-        if isinstance(other, self.__class__):
-            return self._localpart == other._localpart\
-                    and self._domainname == other._domainname
-        return NotImplemented
-
-    def __ne__(self, other):
-        if isinstance(other, self.__class__):
-            return self._localpart != other._localpart\
-                    or self._domainname != other._domainname
-        return NotImplemented
-
-    def __repr__(self):
-        return "EmailAddress('%s@%s')" % (self._localpart, self._domainname)
-
-    def __str__(self):
-        return "%s@%s" % (self._localpart, self._domainname)
-
-    def __chkAddress(self, address):
-        try:
-            localpart, domain = address.split('@')
-        except ValueError:
-            raise VMMEAE(_(u"Missing '@' sign in e-mail address “%s”.") %
-                address, ERR.INVALID_ADDRESS)
-        except AttributeError:
-            raise VMMEAE(_(u"“%s” doesn't look like an e-mail address.") %
-                address, ERR.INVALID_ADDRESS)
-        if len(domain) > 0:
-            domain = VMM.VirtualMailManager.chkDomainname(domain)
-        else:
-            raise VMMEAE(_(u"Missing domain name after “%s@”.") %
-                    localpart, ERR.DOMAIN_NO_NAME)
-        localpart = self.__chkLocalpart(localpart)
-        self._localpart, self._domainname = localpart, domain
-
-    def __chkLocalpart(self, localpart):
-        """Validates the local-part of an e-mail address.
-
-        Arguments:
-        localpart -- local-part of the e-mail address that should be validated (str)
-        """
-        if len(localpart) < 1:
-            raise VMMEAE(_(u'No local-part specified.'),
-                ERR.LOCALPART_INVALID)
-        if len(localpart) > 64:
-            raise VMMEAE(_(u'The local-part “%s” is too long') %
-                localpart, ERR.LOCALPART_TOO_LONG)
-        ic = set(re.findall(RE_LOCALPART, localpart))
-        if len(ic):
-            ichrs = ''
-            for c in ic:
-                ichrs += u"“%s” " % c
-            raise VMMEAE(_(u"The local-part “%(lpart)s” contains invalid\
- characters: %(ichrs)s") % {'lpart': localpart, 'ichrs': ichrs},
-                ERR.LOCALPART_INVALID)
-        return localpart
-
--- a/VirtualMailManager/Exceptions.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Exception classes for Virtual Mail Manager"""
-
-class VMMException(Exception):
-    """Exception class for VirtualMailManager exceptions"""
-    def __init__(self, msg, code):
-        Exception.__init__(self, msg)
-        self._code = int(code)
-        ### for older python versions, like py 2.4.4 on OpenBSD 4.2
-        if not hasattr(self, 'message'):
-            self.message = msg
-
-    def msg(self):
-        """Returns the exception message."""
-        return self.message
-
-    def code(self):
-        """Returns the numeric exception error code."""
-        return self._code
-
-class VMMConfigException(VMMException):
-    """Exception class for Configurtion exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMPermException(VMMException):
-    """Exception class for permissions exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMNotRootException(VMMException):
-    """Exception class for non-root exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMDomainException(VMMException):
-    """Exception class for Domain exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMAliasDomainException(VMMException):
-    """Exception class for AliasDomain exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMAccountException(VMMException):
-    """Exception class for Account exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMAliasException(VMMException):
-    """Exception class for Alias exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMEmailAddressException(VMMException):
-    """Exception class for EmailAddress exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMMailLocationException(VMMException):
-    """Exception class for MailLocation exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMRelocatedException(VMMException):
-    """Exception class for Relocated exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
-
-class VMMTransportException(VMMException):
-    """Exception class for Transport exceptions"""
-    def __init__(self, msg, code):
-        VMMException.__init__(self, msg, code)
--- a/VirtualMailManager/MailLocation.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's MailLocation class to manage the mail_location
-for accounts."""
-
-from __main__ import re, ERR
-from Exceptions import VMMMailLocationException as MLE
-
-RE_MAILLOCATION = """^\w{1,20}$"""
-
-class MailLocation(object):
-    """A wrapper class thats provide access to the maillocation table"""
-    __slots__ = ('__id', '__maillocation', '_dbh')
-    def __init__(self, dbh, mid=None, maillocation=None):
-        """Creates a new MailLocation instance.
-
-        Either mid or maillocation must be specified.
-
-        Keyword arguments:
-        dbh -- a pyPgSQL.PgSQL.connection
-        mid -- the id of a maillocation (long)
-        maillocation -- the value of the maillocation (str)
-        """
-        self._dbh = dbh
-        if mid is None and maillocation is None:
-            raise MLE(_('Either mid or maillocation must be specified.'),
-                ERR.MAILLOCATION_INIT)
-        elif mid is not None:
-            try:
-                self.__id = long(mid)
-            except ValueError:
-                raise MLE(_('mid must be an int/long.'), ERR.MAILLOCATION_INIT)
-            self._loadByID()
-        else:
-            if re.match(RE_MAILLOCATION, maillocation):
-                self.__maillocation = maillocation
-                self._loadByName()
-            else:
-                raise MLE(
-                    _(u'Invalid folder name “%s”, it may consist only of\n\
-1 - 20 single byte characters (A-Z, a-z, 0-9 and _).') % maillocation,
-                        ERR.MAILLOCATION_INIT)
-
-    def _loadByID(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT maillocation FROM maillocation WHERE mid = %s',
-                self.__id)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self.__maillocation = result[0]
-        else:
-            raise MLE(_('Unknown mid specified.'), ERR.UNKNOWN_MAILLOCATION_ID)
-
-    def _loadByName(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT mid FROM maillocation WHERE maillocation = %s',
-                self.__maillocation)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self.__id = result[0]
-        else:
-            self._save()
-
-    def _save(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('maillocation_id')")
-        self.__id = dbc.fetchone()[0]
-        dbc.execute('INSERT INTO maillocation(mid,maillocation) VALUES(%s,%s)',
-                self.__id, self.__maillocation)
-        self._dbh.commit()
-        dbc.close()
-
-    def getID(self):
-        """Returns the unique ID of the maillocation."""
-        return self.__id
-
-    def getMailLocation(self):
-        """Returns the value of maillocation, ex: 'Maildir'"""
-        return self.__maillocation
-
--- a/VirtualMailManager/Relocated.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's Relocated class to manage relocated users."""
-
-from __main__ import ERR
-from Exceptions import VMMRelocatedException as VMMRE
-from Domain import Domain
-from EmailAddress import EmailAddress
-import VirtualMailManager as VMM
-
-class Relocated(object):
-    """Class to manage e-mail addresses of relocated users."""
-    __slots__ = ('_addr', '_dest', '_gid', '_isNew', '_dbh')
-    def __init__(self, dbh, address, destination=None):
-        if isinstance(address, EmailAddress):
-            self._addr = address
-        else:
-            raise TypeError("Argument 'address' is not an EmailAddress")
-        if destination is None:
-            self._dest = None
-        elif isinstance(destination, EmailAddress):
-            self._dest = destination
-        else:
-            raise TypeError("Argument 'destination' is not an EmailAddress")
-        if address == destination:
-            raise VMMRE(_(u"Address and destination are identical."),
-                ERR.RELOCATED_ADDR_DEST_IDENTICAL)
-        self._dbh = dbh
-        self._gid = 0
-        self._isNew = False
-        self._setAddr()
-        self._exists()
-        if self._isNew and VMM.VirtualMailManager.accountExists(self._dbh,
-                self._addr):
-            raise VMMRE(_(u"There is already an account with address “%s”.") %\
-                    self._addr, ERR.ACCOUNT_EXISTS)
-        if self._isNew and VMM.VirtualMailManager.aliasExists(self._dbh,
-                self._addr):
-            raise VMMRE(
-                    _(u"There is already an alias with the address “%s”.") %\
-                    self._addr, ERR.ALIAS_EXISTS)
-
-    def _exists(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT gid FROM relocated WHERE gid = %s AND address = %s",
-                self._gid, self._addr._localpart)
-        gid = dbc.fetchone()
-        dbc.close()
-        if gid is None:
-            self._isNew = True
-
-    def _setAddr(self):
-        dom = Domain(self._dbh, self._addr._domainname)
-        self._gid = dom.getID()
-        if self._gid == 0:
-            raise VMMRE(_(u"The domain “%s” doesn't exist.") %\
-                    self._addr._domainname, ERR.NO_SUCH_DOMAIN)
-
-    def save(self):
-        if self._dest is None:
-           raise VMMRE(
-                   _(u"No destination address specified for relocated user."),
-                   ERR.RELOCATED_MISSING_DEST)
-        if self._isNew:
-            dbc = self._dbh.cursor()
-            dbc.execute("INSERT INTO relocated VALUES (%s, %s, %s)",
-                    self._gid, self._addr._localpart, str(self._dest))
-            self._dbh.commit()
-            dbc.close()
-        else:
-            raise VMMRE(
-                    _(u"The relocated user “%s” already exists.") % self._addr,
-                    ERR.RELOCATED_EXISTS)
-
-    def getInfo(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT destination FROM relocated WHERE gid=%s\
- AND address=%s',
-                self._gid, self._addr._localpart)
-        destination = dbc.fetchone()
-        dbc.close()
-        if destination is not None:
-            return destination[0]
-        else:
-            raise VMMRE(
-                    _(u"The relocated user “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_RELOCATED)
-
-    def delete(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("DELETE FROM relocated WHERE gid = %s AND address = %s",
-                self._gid, self._addr._localpart)
-        rowcount = dbc.rowcount
-        dbc.close()
-        if rowcount > 0:
-            self._dbh.commit()
-        else:
-            raise VMMRE(
-                    _(u"The relocated user “%s” doesn't exist.") % self._addr,
-                    ERR.NO_SUCH_RELOCATED)
-
--- a/VirtualMailManager/Transport.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""Virtual Mail Manager's Transport class to manage the transport for
-domains and accounts."""
-
-from __main__ import ERR
-from Exceptions import VMMTransportException
-
-class Transport(object):
-    """A wrapper class that provides access to the transport table"""
-    __slots__ = ('__id', '__transport', '_dbh')
-    def __init__(self, dbh, tid=None, transport=None):
-        """Creates a new Transport instance.
-
-        Either tid or transport must be specified.
-
-        Keyword arguments:
-        dbh -- a pyPgSQL.PgSQL.connection
-        tid -- the id of a transport (long)
-        transport -- the value of the transport (str)
-        """
-        self._dbh = dbh
-        if tid is None and transport is None:
-            raise VMMTransportException(
-                _('Either tid or transport must be specified.'),
-                ERR.TRANSPORT_INIT)
-        elif tid is not None:
-            try:
-                self.__id = long(tid)
-            except ValueError:
-                raise VMMTransportException(_('tid must be an int/long.'),
-                    ERR.TRANSPORT_INIT)
-            self._loadByID()
-        else:
-            self.__transport = transport
-            self._loadByName()
-
-    def __eq__(self, other):
-        if isinstance(other, self.__class__):
-            return self.__id == other.getID()
-        return NotImplemented
-
-    def __ne__(self, other):
-        if isinstance(other, self.__class__):
-            return self.__id != other.getID()
-        return NotImplemented
-
-    def __str__(self):
-        return self.__transport
-
-    def _loadByID(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT transport FROM transport WHERE tid = %s', self.__id)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self.__transport = result[0]
-        else:
-            raise VMMTransportException(_('Unknown tid specified.'),
-                ERR.UNKNOWN_TRANSPORT_ID)
-
-    def _loadByName(self):
-        dbc = self._dbh.cursor()
-        dbc.execute('SELECT tid FROM transport WHERE transport = %s',
-                self.__transport)
-        result = dbc.fetchone()
-        dbc.close()
-        if result is not None:
-            self.__id = result[0]
-        else:
-            self._save()
-
-    def _save(self):
-        dbc = self._dbh.cursor()
-        dbc.execute("SELECT nextval('transport_id')")
-        self.__id = dbc.fetchone()[0]
-        dbc.execute('INSERT INTO transport (tid, transport) VALUES (%s, %s)',
-                self.__id, self.__transport)
-        self._dbh.commit()
-        dbc.close()
-
-    def getID(self):
-        """Returns the unique ID of the transport."""
-        return self.__id
-
-    def getTransport(self):
-        """Returns the value of transport, ex: 'dovecot:'"""
-        return self.__transport
--- a/VirtualMailManager/VirtualMailManager.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,709 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""The main class for vmm."""
-
-
-from encodings.idna import ToASCII, ToUnicode
-from getpass import getpass
-from shutil import rmtree
-from subprocess import Popen, PIPE
-
-from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net
-
-from __main__ import os, re, ENCODING, ERR, w_std
-from ext.Postconf import Postconf
-from Account import Account
-from Alias import Alias
-from AliasDomain import AliasDomain
-from Config import Config as Cfg
-from Domain import Domain
-from EmailAddress import EmailAddress
-from Exceptions import *
-from Relocated import Relocated
-
-SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
-RE_ASCII_CHARS = """^[\x20-\x7E]*$"""
-RE_DOMAIN = """^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$"""
-RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$"""
-RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
-RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$"""
-
-class VirtualMailManager(object):
-    """The main class for vmm"""
-    __slots__ = ('__Cfg', '__cfgFileName', '__cfgSections', '__dbh', '__scheme',
-            '__warnings', '_postconf')
-    def __init__(self):
-        """Creates a new VirtualMailManager instance.
-        Throws a VMMNotRootException if your uid is greater 0.
-        """
-        self.__cfgFileName = ''
-        self.__warnings = []
-        self.__Cfg = None
-        self.__dbh = None
-
-        if os.geteuid():
-            raise VMMNotRootException(_(u"You are not root.\n\tGood bye!\n"),
-                ERR.CONF_NOPERM)
-        if self.__chkCfgFile():
-            self.__Cfg = Cfg(self.__cfgFileName)
-            self.__Cfg.load()
-            self.__Cfg.check()
-            self.__cfgSections = self.__Cfg.getsections()
-            self.__scheme = self.__Cfg.get('misc', 'passwdscheme')
-            self._postconf = Postconf(self.__Cfg.get('bin', 'postconf'))
-        if not os.sys.argv[1] in ['cf', 'configure']:
-            self.__chkenv()
-
-    def __findCfgFile(self):
-        for path in ['/root', '/usr/local/etc', '/etc']:
-            tmp = os.path.join(path, 'vmm.cfg')
-            if os.path.isfile(tmp):
-                self.__cfgFileName = tmp
-                break
-        if not len(self.__cfgFileName):
-            raise VMMException(
-                _(u"No “vmm.cfg” found in: /root:/usr/local/etc:/etc"),
-                ERR.CONF_NOFILE)
-
-    def __chkCfgFile(self):
-        """Checks the configuration file, returns bool"""
-        self.__findCfgFile()
-        fstat = os.stat(self.__cfgFileName)
-        fmode = int(oct(fstat.st_mode & 0777))
-        if fmode % 100 and fstat.st_uid != fstat.st_gid \
-        or fmode % 10 and fstat.st_uid == fstat.st_gid:
-            raise VMMPermException(_(
-                u'fix permissions (%(perms)s) for “%(file)s”\n\
-`chmod 0600 %(file)s` would be great.') % {'file':
-                self.__cfgFileName, 'perms': fmode}, ERR.CONF_WRONGPERM)
-        else:
-            return True
-
-    def __chkenv(self):
-        """"""
-        if not os.path.exists(self.__Cfg.get('domdir', 'base')):
-            old_umask = os.umask(0006)
-            os.makedirs(self.__Cfg.get('domdir', 'base'), 0771)
-            os.chown(self.__Cfg.get('domdir', 'base'), 0,
-                    self.__Cfg.getint('misc', 'gid_mail'))
-            os.umask(old_umask)
-        elif not os.path.isdir(self.__Cfg.get('domdir', 'base')):
-            raise VMMException(_(u'“%s” is not a directory.\n\
-(vmm.cfg: section "domdir", option "base")') %
-                self.__Cfg.get('domdir', 'base'), ERR.NO_SUCH_DIRECTORY)
-        for opt, val in self.__Cfg.items('bin'):
-            if not os.path.exists(val):
-                raise VMMException(_(u'“%(binary)s” doesn\'t exist.\n\
-(vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
-                    ERR.NO_SUCH_BINARY)
-            elif not os.access(val, os.X_OK):
-                raise VMMException(_(u'“%(binary)s” is not executable.\n\
-(vmm.cfg: section "bin", option "%(option)s")') %{'binary': val,'option': opt},
-                    ERR.NOT_EXECUTABLE)
-
-    def __dbConnect(self):
-        """Creates a pyPgSQL.PgSQL.connection instance."""
-        if self.__dbh is None or not self.__dbh._isOpen:
-            try:
-                self.__dbh = PgSQL.connect(
-                        database=self.__Cfg.get('database', 'name'),
-                        user=self.__Cfg.get('database', 'user'),
-                        host=self.__Cfg.get('database', 'host'),
-                        password=self.__Cfg.get('database', 'pass'),
-                        client_encoding='utf8', unicode_results=True)
-                dbc = self.__dbh.cursor()
-                dbc.execute("SET NAMES 'UTF8'")
-                dbc.close()
-            except PgSQL.libpq.DatabaseError, e:
-                raise VMMException(str(e), ERR.DATABASE_ERROR)
-
-    def idn2ascii(domainname):
-        """Converts an idn domainname in punycode.
-
-        Arguments:
-        domainname -- the domainname to convert (unicode)
-        """
-        return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl])
-    idn2ascii = staticmethod(idn2ascii)
-
-    def ace2idna(domainname):
-        """Convertis a domainname from ACE according to IDNA
-
-        Arguments:
-        domainname -- the domainname to convert (str)
-        """
-        return u'.'.join([ToUnicode(lbl) for lbl in domainname.split('.')\
-                if lbl])
-    ace2idna = staticmethod(ace2idna)
-
-    def chkDomainname(domainname):
-        """Validates the domain name of an e-mail address.
-
-        Keyword arguments:
-        domainname -- the domain name that should be validated
-        """
-        if not re.match(RE_ASCII_CHARS, domainname):
-            domainname = VirtualMailManager.idn2ascii(domainname)
-        if len(domainname) > 255:
-            raise VMMException(_(u'The domain name is too long.'),
-                ERR.DOMAIN_TOO_LONG)
-        if not re.match(RE_DOMAIN, domainname):
-            raise VMMException(_(u'The domain name “%s” is invalid.') %\
-                    domainname, ERR.DOMAIN_INVALID)
-        return domainname
-    chkDomainname = staticmethod(chkDomainname)
-
-    def _exists(dbh, query):
-        dbc = dbh.cursor()
-        dbc.execute(query)
-        gid = dbc.fetchone()
-        dbc.close()
-        if gid is None:
-            return False
-        else:
-            return True
-    _exists = staticmethod(_exists)
-
-    def accountExists(dbh, address):
-        sql = "SELECT gid FROM users WHERE gid = (SELECT gid FROM domain_name\
- WHERE domainname = '%s') AND local_part = '%s'" % (address._domainname,
-            address._localpart)
-        return VirtualMailManager._exists(dbh, sql)
-    accountExists = staticmethod(accountExists)
-
-    def aliasExists(dbh, address):
-        sql = "SELECT DISTINCT gid FROM alias WHERE gid = (SELECT gid FROM\
- domain_name WHERE domainname = '%s') AND address = '%s'" %\
-            (address._domainname, address._localpart)
-        return VirtualMailManager._exists(dbh, sql)
-    aliasExists = staticmethod(aliasExists)
-
-    def relocatedExists(dbh, address):
-        sql = "SELECT gid FROM relocated WHERE gid = (SELECT gid FROM\
- domain_name WHERE domainname = '%s') AND address = '%s'" %\
-            (address._domainname, address._localpart)
-        return VirtualMailManager._exists(dbh, sql)
-    relocatedExists = staticmethod(relocatedExists)
-
-    def _readpass(self):
-        # TP: Please preserve the trailing space.
-        readp_msg0 = _(u'Enter new password: ').encode(ENCODING, 'replace')
-        # TP: Please preserve the trailing space.
-        readp_msg1 = _(u'Retype new password: ').encode(ENCODING, 'replace')
-        mismatched = True
-        flrs = 0
-        while mismatched:
-            if flrs > 2:
-                raise VMMException(_(u'Too many failures - try again later.'),
-                        ERR.VMM_TOO_MANY_FAILURES)
-            clear0 = getpass(prompt=readp_msg0)
-            clear1 = getpass(prompt=readp_msg1)
-            if clear0 != clear1:
-                flrs += 1
-                w_std(_(u'Sorry, passwords do not match'))
-                continue
-            if len(clear0) < 1:
-                flrs += 1
-                w_std(_(u'Sorry, empty passwords are not permitted'))
-                continue
-            mismatched = False
-        return clear0
-
-    def __getAccount(self, address, password=None):
-        self.__dbConnect()
-        address = EmailAddress(address)
-        if not password is None:
-            password = self.__pwhash(password)
-        return Account(self.__dbh, address, password)
-
-    def __getAlias(self, address, destination=None):
-        self.__dbConnect()
-        address = EmailAddress(address)
-        if destination is not None:
-            destination = EmailAddress(destination)
-        return Alias(self.__dbh, address, destination)
-
-    def __getRelocated(self,address, destination=None):
-        self.__dbConnect()
-        address = EmailAddress(address)
-        if destination is not None:
-            destination = EmailAddress(destination)
-        return Relocated(self.__dbh, address, destination)
-
-    def __getDomain(self, domainname, transport=None):
-        if transport is None:
-            transport = self.__Cfg.get('misc', 'transport')
-        self.__dbConnect()
-        return Domain(self.__dbh, domainname,
-                self.__Cfg.get('domdir', 'base'), transport)
-
-    def __getDiskUsage(self, directory):
-        """Estimate file space usage for the given directory.
-
-        Keyword arguments:
-        directory -- the directory to summarize recursively disk usage for
-        """
-        if self.__isdir(directory):
-            return Popen([self.__Cfg.get('bin', 'du'), "-hs", directory],
-                stdout=PIPE).communicate()[0].split('\t')[0]
-        else:
-            return 0
-
-    def __isdir(self, directory):
-        isdir = os.path.isdir(directory)
-        if not isdir:
-            self.__warnings.append(_('No such directory: %s') % directory)
-        return isdir
-
-    def __makedir(self, directory, mode=None, uid=None, gid=None):
-        if mode is None:
-            mode = self.__Cfg.getint('maildir', 'mode')
-        if uid is None:
-            uid = 0
-        if gid is None:
-            gid = 0
-        os.makedirs(directory, mode)
-        os.chown(directory, uid, gid)
-
-    def __domDirMake(self, domdir, gid):
-        os.umask(0006)
-        oldpwd = os.getcwd()
-        basedir = self.__Cfg.get('domdir', 'base')
-        domdirdirs = domdir.replace(basedir+'/', '').split('/')
-
-        os.chdir(basedir)
-        if not os.path.isdir(domdirdirs[0]):
-            self.__makedir(domdirdirs[0], 489, 0,
-                    self.__Cfg.getint('misc', 'gid_mail'))
-        os.chdir(domdirdirs[0])
-        os.umask(0007)
-        self.__makedir(domdirdirs[1], self.__Cfg.getint('domdir', 'mode'), 0,
-                gid)
-        os.chdir(oldpwd)
-
-    def __subscribeFL(self, folderlist, uid, gid):
-        fname = os.path.join(self.__Cfg.get('maildir','name'), 'subscriptions')
-        sf = file(fname, 'w')
-        for f in folderlist:
-            sf.write(f+'\n')
-        sf.flush()
-        sf.close()
-        os.chown(fname, uid, gid)
-        os.chmod(fname, 384)
-
-    def __mailDirMake(self, domdir, uid, gid):
-        """Creates maildirs and maildir subfolders.
-
-        Keyword arguments:
-        domdir -- the path to the domain directory
-        uid -- user id from the account
-        gid -- group id from the account
-        """
-        os.umask(0007)
-        oldpwd = os.getcwd()
-        os.chdir(domdir)
-
-        maildir = self.__Cfg.get('maildir', 'name')
-        folders = [maildir]
-        for folder in self.__Cfg.get('maildir', 'folders').split(':'):
-            folder = folder.strip()
-            if len(folder) and not folder.count('..')\
-            and re.match(RE_MBOX_NAMES, folder):
-                folders.append('%s/.%s' % (maildir, folder))
-        subdirs = ['cur', 'new', 'tmp']
-        mode = self.__Cfg.getint('maildir', 'mode')
-
-        self.__makedir('%s' % uid, mode, uid, gid)
-        os.chdir('%s' % uid)
-        for folder in folders:
-            self.__makedir(folder, mode, uid, gid)
-            for subdir in subdirs:
-                self.__makedir(os.path.join(folder, subdir), mode, uid, gid)
-        self.__subscribeFL([f.replace(maildir+'/.', '') for f in folders[1:]],
-                uid, gid)
-        os.chdir(oldpwd)
-
-    def __userDirDelete(self, domdir, uid, gid):
-        if uid > 0 and gid > 0:
-            userdir = '%s' % uid
-            if userdir.count('..') or domdir.count('..'):
-                raise VMMException(_(u'Found ".." in home directory path.'),
-                    ERR.FOUND_DOTS_IN_PATH)
-            if os.path.isdir(domdir):
-                os.chdir(domdir)
-                if os.path.isdir(userdir):
-                    mdstat = os.stat(userdir)
-                    if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
-                        raise VMMException(
-                         _(u'Detected owner/group mismatch in home directory.'),
-                         ERR.MAILDIR_PERM_MISMATCH)
-                    rmtree(userdir, ignore_errors=True)
-                else:
-                    raise VMMException(_(u"No such directory: %s") %
-                        os.path.join(domdir, userdir), ERR.NO_SUCH_DIRECTORY)
-
-    def __domDirDelete(self, domdir, gid):
-        if gid > 0:
-            if not self.__isdir(domdir):
-                return
-            basedir = self.__Cfg.get('domdir', 'base')
-            domdirdirs = domdir.replace(basedir+'/', '').split('/')
-            domdirparent = os.path.join(basedir, domdirdirs[0])
-            if basedir.count('..') or domdir.count('..'):
-                raise VMMException(_(u'Found ".." in domain directory path.'),
-                        ERR.FOUND_DOTS_IN_PATH)
-            if os.path.isdir(domdirparent):
-                os.chdir(domdirparent)
-                if os.lstat(domdirdirs[1]).st_gid != gid:
-                    raise VMMException(_(
-                        u'Detected group mismatch in domain directory.'),
-                        ERR.DOMAINDIR_GROUP_MISMATCH)
-                rmtree(domdirdirs[1], ignore_errors=True)
-
-    def __getSalt(self):
-        from random import choice
-        salt = None
-        if self.__scheme == 'CRYPT':
-            salt = '%s%s' % (choice(SALTCHARS), choice(SALTCHARS))
-        elif self.__scheme in ['MD5', 'MD5-CRYPT']:
-            salt = '$1$%s$' % ''.join([choice(SALTCHARS) for x in xrange(8)])
-        return salt
-
-    def __pwCrypt(self, password):
-        # for: CRYPT, MD5 and MD5-CRYPT
-        from crypt import crypt
-        return crypt(password, self.__getSalt())
-
-    def __pwSHA1(self, password):
-        # for: SHA/SHA1
-        import sha
-        from base64 import standard_b64encode
-        sha1 = sha.new(password)
-        return standard_b64encode(sha1.digest())
-
-    def __pwMD5(self, password, emailaddress=None):
-        import md5
-        _md5 = md5.new(password)
-        if self.__scheme == 'LDAP-MD5':
-            from base64 import standard_b64encode
-            return standard_b64encode(_md5.digest())
-        elif self.__scheme == 'PLAIN-MD5':
-            return _md5.hexdigest()
-        elif self.__scheme == 'DIGEST-MD5' and emailaddress is not None:
-            # use an empty realm - works better with usenames like user@dom
-            _md5 = md5.new('%s::%s' % (emailaddress, password))
-            return _md5.hexdigest()
-
-    def __pwMD4(self, password):
-        # for: PLAIN-MD4
-        from Crypto.Hash import MD4
-        _md4 = MD4.new(password)
-        return _md4.hexdigest()
-
-    def __pwhash(self, password, scheme=None, user=None):
-        if scheme is not None:
-            self.__scheme = scheme
-        if self.__scheme in ['CRYPT', 'MD5', 'MD5-CRYPT']:
-            return '{%s}%s' % (self.__scheme, self.__pwCrypt(password))
-        elif self.__scheme in ['SHA', 'SHA1']:
-            return '{%s}%s' % (self.__scheme, self.__pwSHA1(password))
-        elif self.__scheme in ['PLAIN-MD5', 'LDAP-MD5', 'DIGEST-MD5']:
-            return '{%s}%s' % (self.__scheme, self.__pwMD5(password, user))
-        elif self.__scheme == 'PLAIN-MD4':
-            return '{%s}%s' % (self.__scheme, self.__pwMD4(password))
-        elif self.__scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
-                'LANMAN', 'NTLM', 'RPA']:
-            cmd_args = [self.__Cfg.get('bin', 'dovecotpw'), '-s',
-                        self.__scheme, '-p', password]
-            if self.__Cfg.getint('misc', 'dovecotvers') >= 20:
-                cmd_args.insert(1, 'pw')
-            return Popen(cmd_args, stdout=PIPE).communicate()[0][:-1]
-        else:
-            return '{%s}%s' % (self.__scheme, password)
-
-    def hasWarnings(self):
-        """Checks if warnings are present, returns bool."""
-        return bool(len(self.__warnings))
-
-    def getWarnings(self):
-        """Returns a list with all available warnings."""
-        return self.__warnings
-
-    def cfgGetBoolean(self, section, option):
-        return self.__Cfg.getboolean(section, option)
-
-    def cfgGetInt(self, section, option):
-        return self.__Cfg.getint(section, option)
-
-    def cfgGetString(self, section, option):
-        return self.__Cfg.get(section, option)
-
-    def setupIsDone(self):
-        """Checks if vmm is configured, returns bool"""
-        try:
-            return self.__Cfg.getboolean('config', 'done')
-        except ValueError, e:
-            raise VMMConfigException(_(u"""Configuration error: "%s"
-(in section "config", option "done") see also: vmm.cfg(5)\n""") % str(e),
-                  ERR.CONF_ERROR)
-
-    def configure(self, section=None):
-        """Starts interactive configuration.
-
-        Configures in interactive mode options in the given section.
-        If no section is given (default) all options from all sections
-        will be prompted.
-
-        Keyword arguments:
-        section -- the section to configure (default None):
-            'database', 'maildir', 'bin' or 'misc'
-        """
-        if section is None:
-            self.__Cfg.configure(self.__cfgSections)
-        elif section in self.__cfgSections:
-            self.__Cfg.configure([section])
-        else:
-            raise VMMException(_(u"Invalid section: “%s”") % section,
-                ERR.INVALID_SECTION)
-
-    def domainAdd(self, domainname, transport=None):
-        dom = self.__getDomain(domainname, transport)
-        dom.save()
-        self.__domDirMake(dom.getDir(), dom.getID())
-
-    def domainTransport(self, domainname, transport, force=None):
-        if force is not None and force != 'force':
-            raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
-                ERR.INVALID_OPTION)
-        dom = self.__getDomain(domainname, None)
-        if force is None:
-            dom.updateTransport(transport)
-        else:
-            dom.updateTransport(transport, force=True)
-
-    def domainDelete(self, domainname, force=None):
-        if not force is None and force not in ['deluser','delalias','delall']:
-            raise VMMDomainException(_(u"Invalid argument: “%s”") % force,
-                ERR.INVALID_OPTION)
-        dom = self.__getDomain(domainname)
-        gid = dom.getID()
-        domdir = dom.getDir()
-        if self.__Cfg.getboolean('misc', 'forcedel') or force == 'delall':
-            dom.delete(True, True)
-        elif force == 'deluser':
-            dom.delete(delUser=True)
-        elif force == 'delalias':
-            dom.delete(delAlias=True)
-        else:
-            dom.delete()
-        if self.__Cfg.getboolean('domdir', 'delete'):
-            self.__domDirDelete(domdir, gid)
-
-    def domainInfo(self, domainname, details=None):
-        if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
-                'relocated', 'detailed']:
-            raise VMMException(_(u'Invalid argument: “%s”') % details,
-                    ERR.INVALID_AGUMENT)
-        if details == 'detailed':
-            details = 'full'
-            self.__warnings.append(_(u'\
-The keyword “detailed” is deprecated and will be removed in a future release.\n\
-   Please use the keyword “full” to get full details.'))
-        dom = self.__getDomain(domainname)
-        dominfo = dom.getInfo()
-        if dominfo['domainname'].startswith('xn--'):
-            dominfo['domainname'] += ' (%s)'\
-                % VirtualMailManager.ace2idna(dominfo['domainname'])
-        if details is None:
-            return dominfo
-        elif details == 'accounts':
-            return (dominfo, dom.getAccounts())
-        elif details == 'aliasdomains':
-            return (dominfo, dom.getAliaseNames())
-        elif details == 'aliases':
-            return (dominfo, dom.getAliases())
-        elif details == 'relocated':
-            return(dominfo, dom.getRelocated())
-        else:
-            return (dominfo, dom.getAliaseNames(), dom.getAccounts(),
-                    dom.getAliases(), dom.getRelocated())
-
-    def aliasDomainAdd(self, aliasname, domainname):
-        """Adds an alias domain to the domain.
-
-        Keyword arguments:
-        aliasname -- the name of the alias domain (str)
-        domainname -- name of the target domain (str)
-        """
-        dom = self.__getDomain(domainname)
-        aliasDom = AliasDomain(self.__dbh, aliasname, dom)
-        aliasDom.save()
-
-    def aliasDomainInfo(self, aliasname):
-        self.__dbConnect()
-        aliasDom = AliasDomain(self.__dbh, aliasname, None)
-        return aliasDom.info()
-
-    def aliasDomainSwitch(self, aliasname, domainname):
-        """Modifies the target domain of an existing alias domain.
-
-        Keyword arguments:
-        aliasname -- the name of the alias domain (str)
-        domainname -- name of the new target domain (str)
-        """
-        dom = self.__getDomain(domainname)
-        aliasDom = AliasDomain(self.__dbh, aliasname, dom)
-        aliasDom.switch()
-
-    def aliasDomainDelete(self, aliasname):
-        """Deletes the specified alias domain.
-
-        Keyword arguments:
-        aliasname -- the name of the alias domain (str)
-        """
-        self.__dbConnect()
-        aliasDom = AliasDomain(self.__dbh, aliasname, None)
-        aliasDom.delete()
-
-    def domainList(self, pattern=None):
-        from Domain import search
-        like = False
-        if pattern is not None:
-            if pattern.startswith('%') or pattern.endswith('%'):
-                like = True
-                domain = pattern.strip('%')
-                if not re.match(RE_DOMAIN_SRCH, domain):
-                    raise VMMException(
-                    _(u"The pattern “%s” contains invalid characters.") %
-                    pattern, ERR.DOMAIN_INVALID)
-        self.__dbConnect()
-        return search(self.__dbh, pattern=pattern, like=like)
-
-    def userAdd(self, emailaddress, password):
-        acc = self.__getAccount(emailaddress, password)
-        if password is None:
-            password = self._readpass()
-            acc.setPassword(self.__pwhash(password))
-        acc.save(self.__Cfg.get('maildir', 'name'),
-                self.__Cfg.getint('misc', 'dovecotvers'),
-                self.__Cfg.getboolean('services', 'smtp'),
-                self.__Cfg.getboolean('services', 'pop3'),
-                self.__Cfg.getboolean('services', 'imap'),
-                self.__Cfg.getboolean('services', 'sieve'))
-        self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
-
-    def aliasAdd(self, aliasaddress, targetaddress):
-        alias = self.__getAlias(aliasaddress, targetaddress)
-        alias.save(long(self._postconf.read('virtual_alias_expansion_limit')))
-        gid = self.__getDomain(alias._dest._domainname).getID()
-        if gid > 0 and not VirtualMailManager.accountExists(self.__dbh,
-        alias._dest) and not VirtualMailManager.aliasExists(self.__dbh,
-        alias._dest):
-            self.__warnings.append(
-                _(u"The destination account/alias “%s” doesn't exist.")%\
-                        alias._dest)
-
-    def userDelete(self, emailaddress, force=None):
-        if force not in [None, 'delalias']:
-            raise VMMException(_(u"Invalid argument: “%s”") % force,
-                    ERR.INVALID_AGUMENT)
-        acc = self.__getAccount(emailaddress)
-        uid = acc.getUID()
-        gid = acc.getGID()
-        acc.delete(force)
-        if self.__Cfg.getboolean('maildir', 'delete'):
-            try:
-                self.__userDirDelete(acc.getDir('domain'), uid, gid)
-            except VMMException, e:
-                if e.code() in [ERR.FOUND_DOTS_IN_PATH,
-                        ERR.MAILDIR_PERM_MISMATCH, ERR.NO_SUCH_DIRECTORY]:
-                    warning = _(u"""\
-The account has been successfully deleted from the database.
-    But an error occurred while deleting the following directory:
-    “%(directory)s”
-    Reason: %(reason)s""") % {'directory': acc.getDir('home'),'reason': e.msg()}
-                    self.__warnings.append(warning)
-                else:
-                    raise e
-
-    def aliasInfo(self, aliasaddress):
-        alias = self.__getAlias(aliasaddress)
-        return alias.getInfo()
-
-    def aliasDelete(self, aliasaddress, targetaddress=None):
-        alias = self.__getAlias(aliasaddress, targetaddress)
-        alias.delete()
-
-    def userInfo(self, emailaddress, details=None):
-        if details not in [None, 'du', 'aliases', 'full']:
-            raise VMMException(_(u'Invalid argument: “%s”') % details,
-                    ERR.INVALID_AGUMENT)
-        acc = self.__getAccount(emailaddress)
-        info = acc.getInfo(self.__Cfg.getint('misc', 'dovecotvers'))
-        if self.__Cfg.getboolean('maildir', 'diskusage')\
-        or details in ['du', 'full']:
-            info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
-            if details in [None, 'du']:
-                return info
-        if details in ['aliases', 'full']:
-            return (info, acc.getAliases())
-        return info
-
-    def userByID(self, uid):
-        from Account import getAccountByID
-        self.__dbConnect()
-        return getAccountByID(uid, self.__dbh)
-
-    def userPassword(self, emailaddress, password):
-        acc = self.__getAccount(emailaddress)
-        if acc.getUID() == 0:
-           raise VMMException(_(u"Account doesn't exist"), ERR.NO_SUCH_ACCOUNT)
-        if password is None:
-            password = self._readpass()
-        acc.modify('password', self.__pwhash(password, user=emailaddress))
-
-    def userName(self, emailaddress, name):
-        acc = self.__getAccount(emailaddress)
-        acc.modify('name', name)
-
-    def userTransport(self, emailaddress, transport):
-        acc = self.__getAccount(emailaddress)
-        acc.modify('transport', transport)
-
-    def userDisable(self, emailaddress, service=None):
-        if service == 'managesieve':
-            service = 'sieve'
-            self.__warnings.append(_(u'\
-The service name “managesieve” is deprecated and will be removed\n\
-   in a future release.\n\
-   Please use the service name “sieve” instead.'))
-        acc = self.__getAccount(emailaddress)
-        acc.disable(self.__Cfg.getint('misc', 'dovecotvers'), service)
-
-    def userEnable(self, emailaddress, service=None):
-        if service == 'managesieve':
-            service = 'sieve'
-            self.__warnings.append(_(u'\
-The service name “managesieve” is deprecated and will be removed\n\
-   in a future release.\n\
-   Please use the service name “sieve” instead.'))
-        acc = self.__getAccount(emailaddress)
-        acc.enable(self.__Cfg.getint('misc', 'dovecotvers'), service)
-
-    def relocatedAdd(self, emailaddress, targetaddress):
-        relocated = self.__getRelocated(emailaddress, targetaddress)
-        relocated.save()
-
-    def relocatedInfo(self, emailaddress):
-        relocated = self.__getRelocated(emailaddress)
-        return relocated.getInfo()
-
-    def relocatedDelete(self, emailaddress):
-        relocated = self.__getRelocated(emailaddress)
-        relocated.delete()
-
-    def __del__(self):
-        if not self.__dbh is None and self.__dbh._isOpen:
-            self.__dbh.close()
--- a/VirtualMailManager/__init__.py	Mon Nov 07 03:22:15 2011 +0000
+++ b/VirtualMailManager/__init__.py	Thu Jun 28 19:26:50 2012 +0000
@@ -1,51 +1,35 @@
 # -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
+# Copyright (c) 2007 - 2012, Pascal Volk
 # See COPYING for distribution information.
-# package initialization code
-#
+"""
+    VirtualMailManager
+    ~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager package initialization code
+"""
 
-import os
-import re
+import gettext
 import locale
+import sys
 
-from constants.VERSION import *
-import constants.ERROR as ERR
+from VirtualMailManager.constants import __author__, __date__, __version__
+
+__all__ = [
+    # version information from VERSION
+    '__author__', '__date__', '__version__',
+    # defined stuff
+    'ENCODING',
+]
+
 
 # Try to set all of the locales according to the current
 # environment variables and get the character encoding.
 try:
     locale.setlocale(locale.LC_ALL, '')
 except locale.Error:
+    sys.stderr.write('warning: unsupported locale setting - '
+                     'that may cause encoding problems.\n\n')
     locale.setlocale(locale.LC_ALL, 'C')
 ENCODING = locale.nl_langinfo(locale.CODESET)
 
-def w_std(*args):
-    """Writes each arg of args, encoded in the current ENCODING, to stdout and
-    appends a newline."""
-    _write = os.sys.stdout.write
-    for arg in args:
-        _write(arg.encode(ENCODING, 'replace'))
-        _write('\n')
-
-def w_err(code, *args):
-    """Writes each arg of args, encoded in the current ENCODING, to stderr and
-    appends a newline.
-    This function additional interrupts the program execution and uses 'code'
-    system exit status."""
-    _write = os.sys.stderr.write
-    for arg in args:
-        _write(arg.encode(ENCODING, 'replace'))
-        _write('\n')
-    os.sys.exit(code)
-
-__all__ = [
-        # imported modules
-        'os', 're', 'locale',
-        # version information from VERSION
-        '__author__', '__date__', '__version__',
-        # error codes
-        'ERR',
-        # defined stuff
-        'ENCODING', 'w_std', 'w_err'
-        ]
-# EOF
+gettext.install('vmm', '/usr/local/share/locale', unicode=1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/account.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,506 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.account
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Account class to manage e-mail accounts.
+"""
+
+from VirtualMailManager.common import version_str, \
+     format_domain_default
+from VirtualMailManager.constants import \
+     ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
+     INVALID_ARGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, \
+     NO_SUCH_DOMAIN, VMM_ERROR
+from VirtualMailManager.domain import Domain
+from VirtualMailManager.emailaddress import EmailAddress
+from VirtualMailManager.errors import VMMError, AccountError as AErr
+from VirtualMailManager.maillocation import MailLocation
+from VirtualMailManager.password import pwhash
+from VirtualMailManager.quotalimit import QuotaLimit
+from VirtualMailManager.transport import Transport
+from VirtualMailManager.serviceset import ServiceSet
+
+__all__ = ('Account', 'get_account_by_uid')
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class Account(object):
+    """Class to manage e-mail accounts."""
+    __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
+                 '_qlimit', '_services', '_transport', '_note', '_uid')
+
+    def __init__(self, dbh, address):
+        """Creates a new Account instance.
+
+        When an account with the given *address* could be found in the
+        database all relevant data will be loaded.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          A database connection for the database access.
+        `address` : VirtualMailManager.EmailAddress.EmailAddress
+          The e-mail address of the (new) Account.
+        """
+        if not isinstance(address, EmailAddress):
+            raise TypeError("Argument 'address' is not an EmailAddress")
+        self._addr = address
+        self._dbh = dbh
+        self._domain = Domain(self._dbh, self._addr.domainname)
+        if not self._domain.gid:
+            # 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.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._uid = 0
+        self._mail = None
+        self._qlimit = None
+        self._services = None
+        self._transport = None
+        self._note = None
+        self._passwd = None
+        self._new = True
+        self._load()
+
+    def __nonzero__(self):
+        """Returns `True` if the Account is known, `False` if it's new."""
+        return not self._new
+
+    def _load(self):
+        """Load 'uid', 'mid', 'qid', 'ssid', 'tid' and 'note' from the
+        database and set _new to `False` - if the user could be found. """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT uid, mid, qid, ssid, tid, note FROM users '
+                    'WHERE gid = %s AND local_part = %s',
+                    (self._domain.gid, self._addr.localpart))
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._uid, _mid, _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)
+                if cur != dbresult:
+                    kwargs = {field: dbresult}
+                    if dbresult is None:
+                        return dbresult
+                    else:
+                        return ctor(self._dbh, **kwargs)
+
+            self._qlimit = load_helper(QuotaLimit, self._qlimit, 'qid', _qid)
+            self._services = load_helper(ServiceSet, self._services, 'ssid',
+                                         _ssid)
+            self._transport = load_helper(Transport, self._transport, 'tid',
+                                          _tid)
+            self._mail = MailLocation(self._dbh, mid=_mid)
+            self._note = _note
+            self._new = False
+
+    def _set_uid(self):
+        """Set the unique ID for the new Account."""
+        assert self._uid == 0
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('users_uid')")
+        self._uid = dbc.fetchone()[0]
+        dbc.close()
+
+    def _prepare(self, maillocation):
+        """Check and set different attributes - before we store the
+        information in the database.
+        """
+        if maillocation.dovecot_version > cfg_dget('misc.dovecot_version'):
+            raise AErr(_(u"The mailbox format '%(mbfmt)s' requires Dovecot "
+                         u">= v%(version)s.") % {
+                       'mbfmt': maillocation.mbformat,
+                       'version': version_str(maillocation.dovecot_version)},
+                       INVALID_MAIL_LOCATION)
+        if self._transport and not maillocation.postfix and \
+          self._transport.transport.lower() in ('virtual:', 'virtual'):
+            raise AErr(_(u"Invalid transport '%(transport)s' for mailbox "
+                         u"format '%(mbfmt)s'.") %
+                       {'transport': self._transport,
+                        'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
+        self._mail = maillocation
+        self._set_uid()
+
+    def _update_tables(self, column, value):
+        """Update various columns in the users table.
+
+        Arguments:
+
+        `column` : basestring
+          Name of the table column. Currently: qid, ssid and tid
+        `value` : long
+          The referenced key
+        """
+        if column not in ('qid', 'ssid', 'tid'):
+            raise ValueError('Unknown column: %r' % column)
+        dbc = self._dbh.cursor()
+        dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column,
+                    (value, self._uid))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def _count_aliases(self):
+        """Count all alias addresses where the destination address is the
+        address of the Account."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT COUNT(destination) FROM alias WHERE destination '
+                    '= %s', (str(self._addr),))
+        a_count = dbc.fetchone()[0]
+        dbc.close()
+        return a_count
+
+    def _chk_state(self):
+        """Raise an AccountError if the Account is new - not yet saved in the
+        database."""
+        if self._new:
+            raise AErr(_(u"The account '%s' does not exist.") % self._addr,
+                       NO_SUCH_ACCOUNT)
+
+    @property
+    def address(self):
+        """The Account's EmailAddress instance."""
+        return self._addr
+
+    @property
+    def domain(self):
+        """The Domain to which the Account belongs to."""
+        if self._domain:
+            return self._domain
+        return None
+
+    @property
+    def gid(self):
+        """The Account's group ID."""
+        if self._domain:
+            return self._domain.gid
+        return None
+
+    @property
+    def home(self):
+        """The Account's home directory."""
+        if not self._new:
+            return '%s/%s' % (self._domain.directory, self._uid)
+        return None
+
+    @property
+    def mail_location(self):
+        """The Account's MailLocation."""
+        return self._mail
+
+    @property
+    def note(self):
+        """The Account's note."""
+        return self._note
+
+    @property
+    def uid(self):
+        """The Account's unique ID."""
+        return self._uid
+
+    def set_password(self, password):
+        """Set a password for the new Account.
+
+        If you want to update the password of an existing Account use
+        Account.modify().
+
+        Argument:
+
+        `password` : basestring
+          The password for the new Account.
+        """
+        if not self._new:
+            raise AErr(_(u"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,
+                       ACCOUNT_MISSING_PASSWORD)
+        self._passwd = password
+
+    def set_note(self, note):
+        """Set the account's (optional) note.
+
+        Argument:
+
+        `note` : basestring or None
+          The note, or None to remove
+        """
+        assert note is None or isinstance(note, basestring)
+        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,
+                       ACCOUNT_EXISTS)
+        if not self._passwd:
+            raise AErr(_(u"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._note))
+        self._dbh.commit()
+        dbc.close()
+        self._new = False
+
+    def modify(self, field, value):
+        """Update the Account's *field* to the new *value*.
+
+        Possible values for *field* are: 'name', 'password', 'note'.
+
+        Arguments:
+
+        `field` : basestring
+          The attribute name: 'name', 'password' or 'note'
+        `value` : basestring
+          The new value of the attribute.
+        """
+        if field not in ('name', 'password', 'note'):
+            raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        if field == 'password':
+            dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
+                        (pwhash(value, user=self._addr), self._uid))
+        else:
+            dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % field,
+                        (value, self._uid))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def update_quotalimit(self, quotalimit):
+        """Update the user's quota limit.
+
+        Arguments:
+
+        `quotalimit` : VirtualMailManager.quotalimit.QuotaLimit
+          the new quota limit of the domain.
+        """
+        if cfg_dget('misc.dovecot_version') < 0x10102f00:
+            raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
+                             u'Dovecot >= v1.1.2.'), VMM_ERROR)
+        self._chk_state()
+        if quotalimit == self._qlimit:
+            return
+        self._qlimit = quotalimit
+        if quotalimit is not None:
+            assert isinstance(quotalimit, QuotaLimit)
+            quotalimit = quotalimit.qid
+        self._update_tables('qid', quotalimit)
+
+    def update_serviceset(self, serviceset):
+        """Assign a different set of services to the Account.
+
+        Argument:
+
+        `serviceset` : VirtualMailManager.serviceset.ServiceSet
+          the new service set.
+        """
+        self._chk_state()
+        if serviceset == self._services:
+            return
+        self._services = serviceset
+        if serviceset is not None:
+            assert isinstance(serviceset, ServiceSet)
+            serviceset = serviceset.ssid
+        self._update_tables('ssid', serviceset)
+
+    def update_transport(self, transport):
+        """Sets a new transport for the Account.
+
+        Arguments:
+
+        `transport` : VirtualMailManager.transport.Transport
+          the new transport
+        """
+        self._chk_state()
+        if transport == self._transport:
+            return
+        self._transport = transport
+        if transport is not None:
+            assert isinstance(transport, Transport)
+            if transport.transport.lower() in ('virtual', 'virtual:') and \
+                not self._mail.postfix:
+                raise AErr(_(u"Invalid transport '%(transport)s' for mailbox "
+                             u"format '%(mbfmt)s'.") %
+                           {'transport': transport, 'mbfmt': self._mail.mbformat},
+                           INVALID_MAIL_LOCATION)
+            transport = transport.tid
+        self._update_tables('tid', transport)
+
+    def _get_info_transport(self):
+        if self._transport:
+            return self._transport.transport
+        return format_domain_default(self._domain.transport.transport)
+
+    def _get_info_serviceset(self):
+        if self._services:
+            services = self._services.services
+            fmt = lambda s: s
+        else:
+            services = self._domain.serviceset.services
+            fmt = format_domain_default
+
+        ret = {}
+        for service, state in services.iteritems():
+            # TP: A service (e.g. pop3 or imap) may be enabled/usable or
+            # disabled/unusable for a user.
+            ret[service] = fmt((_('disabled'), _('enabled'))[state])
+        return ret
+
+    def get_info(self):
+        """Returns a dict with some information about the Account.
+
+        The keys of the dict are: 'address', 'gid', 'home', 'imap'
+        'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid',
+        'uq_bytes', 'uq_messages', 'ql_bytes', 'ql_messages', and
+        'ql_domaindefault'.
+        """
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT name, CASE WHEN bytes IS NULL THEN 0 ELSE bytes '
+                    'END, CASE WHEN messages IS NULL THEN 0 ELSE messages END '
+                    'FROM users LEFT JOIN userquota USING (uid) WHERE '
+                    'users.uid = %s', (self._uid,))
+        info = dbc.fetchone()
+        dbc.close()
+        if info:
+            info = dict(zip(('name', 'uq_bytes', 'uq_messages'), info))
+            info.update(self._get_info_serviceset())
+            info['address'] = self._addr
+            info['gid'] = self._domain.gid
+            info['home'] = '%s/%s' % (self._domain.directory, self._uid)
+            info['mail_location'] = self._mail.mail_location
+            if self._qlimit:
+                info['ql_bytes'] = self._qlimit.bytes
+                info['ql_messages'] = self._qlimit.messages
+                info['ql_domaindefault'] = False
+            else:
+                info['ql_bytes'] = self._domain.quotalimit.bytes
+                info['ql_messages'] = self._domain.quotalimit.messages
+                info['ql_domaindefault'] = True
+            info['transport'] = self._get_info_transport()
+            info['note'] = self._note
+            info['uid'] = self._uid
+            return info
+        # nearly impossible‽
+        raise AErr(_(u"Could not fetch information for account: '%s'") %
+                   self._addr, NO_SUCH_ACCOUNT)
+
+    def get_aliases(self):
+        """Return a list with all alias e-mail addresses, whose destination
+        is the address of the Account."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT address ||'@'|| domainname FROM alias, "
+                    "domain_name WHERE destination = %s AND domain_name.gid = "
+                    "alias.gid AND domain_name.is_primary ORDER BY address",
+                    (str(self._addr),))
+        addresses = dbc.fetchall()
+        dbc.close()
+        aliases = []
+        if addresses:
+            aliases = [alias[0] for alias in addresses]
+        return aliases
+
+    def delete(self, force=False):
+        """Delete the Account from the database.
+
+        Argument:
+
+        `force` : bool
+          if *force* is `True`, all aliases, which points to the Account,
+          will be also deleted.  If there are aliases and *force* is
+          `False`, an AccountError will be raised.
+        """
+        if not isinstance(force, bool):
+            raise TypeError('force must be a bool')
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        if force:
+            dbc.execute('DELETE FROM users WHERE uid = %s', (self._uid),)
+            # delete also all aliases where the destination address is the same
+            # as for this account.
+            dbc.execute("DELETE FROM alias WHERE destination = %s",
+                        (str(self._addr),))
+            self._dbh.commit()
+        else:  # check first for aliases
+            a_count = self._count_aliases()
+            if a_count > 0:
+                dbc.close()
+                raise AErr(_(u"There are %(count)d aliases with the "
+                             u"destination address '%(address)s'.") %
+                           {'count': a_count, 'address': self._addr},
+                           ALIAS_PRESENT)
+            dbc.execute('DELETE FROM users WHERE uid = %s', (self._uid,))
+            self._dbh.commit()
+        dbc.close()
+        self._new = True
+        self._uid = 0
+        self._addr = self._dbh = self._domain = self._passwd = None
+        self._mail = self._qlimit = self._services = self._transport = None
+
+
+def get_account_by_uid(uid, dbh):
+    """Search an Account by its UID.
+
+    This function returns a dict (keys: 'address', 'gid' and 'uid'), if an
+    Account with the given *uid* exists.
+
+    Argument:
+
+    `uid` : long
+      The Account unique ID.
+    `dbh` : pyPgSQL.PgSQL.Connection
+      a database connection for the database access.
+    """
+    try:
+        uid = long(uid)
+    except ValueError:
+        raise AErr(_(u'UID must be an int/long.'), INVALID_ARGUMENT)
+    if uid < 1:
+        raise AErr(_(u'UID must be greater than 0.'), INVALID_ARGUMENT)
+    dbc = dbh.cursor()
+    dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address, "
+                "uid, users.gid, note FROM users LEFT JOIN domain_name ON "
+                "(domain_name.gid = users.gid AND is_primary) WHERE uid = %s",
+                (uid,))
+    info = dbc.fetchone()
+    dbc.close()
+    if not info:
+        raise AErr(_(u"There is no account with the UID: '%d'") % uid,
+                   NO_SUCH_ACCOUNT)
+    info = dict(zip(('address', 'uid', 'gid', 'note'), info))
+    return info
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/alias.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,166 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.alias
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Alias class to manage e-mail aliases.
+"""
+
+from VirtualMailManager.domain import get_gid
+from VirtualMailManager.emailaddress import \
+     EmailAddress, 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
+
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class Alias(object):
+    """Class to manage e-mail aliases."""
+    __slots__ = ('_addr', '_dests', '_gid', '_dbh')
+
+    def __init__(self, dbh, address):
+        assert isinstance(address, EmailAddress)
+        self._addr = address
+        self._dbh = dbh
+        self._gid = get_gid(self._dbh, self._addr.domainname)
+        if not self._gid:
+            raise AErr(_(u"The domain '%s' does not exist.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._dests = []
+
+        self._load_dests()
+
+    def _load_dests(self):
+        """Loads all known destination addresses into the _dests list."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT destination FROM alias WHERE gid = %s AND '
+                    'address = %s', (self._gid, self._addr.localpart))
+        dests = dbc.fetchall()
+        if dbc.rowcount > 0:
+            self._dests.extend(DestAddr(dest[0], self._dbh) for dest in dests)
+        dbc.close()
+
+    def _check_expansion(self, count_new):
+        """Checks the current expansion limit of the alias."""
+        postconf = Postconf(cfg_dget('bin.postconf'))
+        limit = long(postconf.read('virtual_alias_expansion_limit'))
+        dcount = len(self._dests)
+        failed = False
+        if dcount == limit or dcount + count_new > limit:
+            failed = True
+            errmsg = _(
+u"""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'.
+This alias already exceeds its expansion limit (%(count)i/%(limit)i).
+So its unusable, all messages addressed to this alias will be bounced.
+Hint: Delete some destination addresses.""")
+        if failed:
+            raise AErr(errmsg % {'address': self._addr, 'count': dcount,
+                                 'limit': limit, 'count_new': count_new},
+                       ALIAS_EXCEEDS_EXPANSION_LIMIT)
+
+    def _delete(self, destination=None):
+        """Deletes a destination from the alias, if ``destination`` is
+        not ``None``.  If ``destination`` is None, the alias with all
+        its destination addresses will be deleted.
+
+        """
+        dbc = self._dbh.cursor()
+        if not destination:
+            dbc.execute('DELETE FROM alias WHERE gid = %s AND address = %s',
+                        (self._gid, self._addr.localpart))
+        else:
+            dbc.execute('DELETE FROM alias WHERE gid = %s AND address = %s '
+                        'AND destination = %s',
+                        (self._gid, self._addr.localpart, str(destination)))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def __len__(self):
+        """Returns the number of destinations of the alias."""
+        return len(self._dests)
+
+    @property
+    def address(self):
+        """The Alias' EmailAddress instance."""
+        return self._addr
+
+    def add_destinations(self, destinations, warnings=None):
+        """Adds the `EmailAddress`es from *destinations* list to the
+        destinations of the alias.
+
+        Destinations, that are already assigned to the alias, will be
+        removed from *destinations*.  When done, this method will return
+        a set with all destinations, that were saved in the database.
+        """
+        destinations = set(destinations)
+        assert destinations and \
+                all(isinstance(dest, EmailAddress) for dest in destinations)
+        if not warnings is None:
+            assert isinstance(warnings, list)
+        if self._addr in destinations:
+            destinations.remove(self._addr)
+            if not warnings is None:
+                warnings.append(self._addr)
+        duplicates = destinations.intersection(set(self._dests))
+        if duplicates:
+            destinations.difference_update(set(self._dests))
+            if not warnings is None:
+                warnings.extend(duplicates)
+        if not destinations:
+            return destinations
+        self._check_expansion(len(destinations))
+        dbc = self._dbh.cursor()
+        dbc.executemany("INSERT INTO alias (gid, address, destination) "
+                        "VALUES (%d, '%s', %%s)" % (self._gid,
+                                                    self._addr.localpart),
+                        ((str(destination),) for destination in destinations))
+        self._dbh.commit()
+        dbc.close()
+        self._dests.extend(destinations)
+        return destinations
+
+    def del_destination(self, destination):
+        """Deletes the specified ``destination`` address from the alias."""
+        assert isinstance(destination, EmailAddress)
+        if not self._dests:
+            raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+                       NO_SUCH_ALIAS)
+        if not destination in self._dests:
+            raise AErr(_(u"The address '%(addr)s' is not a destination of "
+                         u"the alias '%(alias)s'.") % {'addr': destination,
+                       'alias': self._addr}, NO_SUCH_ALIAS)
+        self._delete(destination)
+        self._dests.remove(destination)
+
+    def get_destinations(self):
+        """Returns an iterator for all destinations of the alias."""
+        if not self._dests:
+            raise AErr(_(u"The alias '%s' 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,
+                       NO_SUCH_ALIAS)
+        self._delete()
+        del self._dests[:]
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/aliasdomain.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,143 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.aliasdomain
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's AliasDomain class to manage alias domains.
+"""
+
+from VirtualMailManager.domain import Domain, check_domainname
+from VirtualMailManager.constants import \
+     ALIASDOMAIN_EXISTS, ALIASDOMAIN_ISDOMAIN, ALIASDOMAIN_NO_DOMDEST, \
+     NO_SUCH_ALIASDOMAIN, NO_SUCH_DOMAIN
+from VirtualMailManager.errors import AliasDomainError as ADErr
+
+
+_ = lambda msg: msg
+
+
+class AliasDomain(object):
+    """Class to manage e-mail alias domains."""
+    __slots__ = ('_gid', '_name', '_domain', '_dbh')
+
+    def __init__(self, dbh, domainname):
+        """Creates a new AliasDomain instance.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          a database connection for the database access
+        `domainname` : basestring
+          the name of the AliasDomain"""
+        self._dbh = dbh
+        self._name = check_domainname(domainname)
+        self._gid = 0
+        self._domain = None
+        self._load()
+
+    def _load(self):
+        """Loads the AliasDomain's GID from the database and checks if the
+        domain name is marked as primary."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT gid, is_primary FROM domain_name WHERE '
+                    'domainname = %s', (self._name,))
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            if result[1]:
+                raise ADErr(_(u"The domain '%s' is a primary domain.") %
+                            self._name, ALIASDOMAIN_ISDOMAIN)
+            self._gid = result[0]
+
+    def set_destination(self, dest_domain):
+        """Set the destination of a new AliasDomain or updates the
+        destination of an existing AliasDomain.
+
+        Argument:
+
+        `dest_domain` : VirtualMailManager.Domain.Domain
+          the AliasDomain's destination domain
+        """
+        assert isinstance(dest_domain, Domain)
+        self._domain = dest_domain
+
+    def save(self):
+        """Stores information about the new AliasDomain in the database."""
+        if self._gid > 0:
+            raise ADErr(_(u"The alias domain '%s' already exists.") %
+                        self._name, ALIASDOMAIN_EXISTS)
+        if not self._domain:
+            raise ADErr(_(u'No destination domain set for the alias domain.'),
+                        ALIASDOMAIN_NO_DOMDEST)
+        if self._domain.gid < 1:
+            raise ADErr(_(u"The target domain '%s' does not exist.") %
+                        self._domain.name, NO_SUCH_DOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary) '
+                    'VALUES (%s, %s, FALSE)', (self._name, self._domain.gid))
+        self._dbh.commit()
+        dbc.close()
+        self._gid = self._domain.gid
+
+    def info(self):
+        """Returns a dict (keys: "alias" and "domain") with the names of the
+        AliasDomain and its primary domain."""
+        if self._gid < 1:
+            raise ADErr(_(u"The alias domain '%s' does not exist.") %
+                        self._name, NO_SUCH_ALIASDOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
+                    'is_primary', (self._gid,))
+        domain = dbc.fetchone()
+        dbc.close()
+        if domain:
+            return {'alias': self._name, 'domain': domain[0]}
+        else:  # an almost unlikely case, isn't it?
+            raise ADErr(_(u'There is no primary domain for the alias domain '
+                          u"'%s'.") % self._name, NO_SUCH_DOMAIN)
+
+    def switch(self):
+        """Switch the destination of the AliasDomain to the new destination,
+        set with the method `set_destination()`.
+        """
+        if not self._domain:
+            raise ADErr(_(u'No destination domain set for the alias domain.'),
+                        ALIASDOMAIN_NO_DOMDEST)
+        if self._domain.gid < 1:
+            raise ADErr(_(u"The target domain '%s' does not exist.") %
+                        self._domain.name, NO_SUCH_DOMAIN)
+        if self._gid < 1:
+            raise ADErr(_(u"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'.") %
+                        {'alias': self._name, 'domain': self._domain.name},
+                        ALIASDOMAIN_EXISTS)
+        dbc = self._dbh.cursor()
+        dbc.execute('UPDATE domain_name SET gid = %s WHERE gid = %s AND '
+                    'domainname = %s AND NOT is_primary', (self._domain.gid,
+                    self._gid, self._name))
+        self._dbh.commit()
+        dbc.close()
+        self._gid = self._domain.gid
+
+    def delete(self):
+        """Delete the AliasDomain's record form the database.
+
+        Raises an AliasDomainError if the AliasDomain doesn't exist.
+        """
+        if self._gid < 1:
+            raise ADErr(_(u"The alias domain '%s' does not exist.") %
+                        self._name, NO_SUCH_ALIASDOMAIN)
+        dbc = self._dbh.cursor()
+        dbc.execute('DELETE FROM domain_name WHERE domainname = %s AND NOT '
+                    'is_primary', (self._name,))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+            self._gid = 0
+        dbc.close()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/catchall.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,171 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2012 martin f. krafft
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.catchall
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's CatchallAlias class to manage domain catch-all
+    aliases.
+
+    This is heavily based on (more or less a copy of) the Alias class, because
+    fundamentally, catchall aliases are aliases, but without a localpart.
+    While Alias could potentially derive from CatchallAlias to reuse some of
+    the functionality, it's probably not worth it. I found no sensible way to
+    derive CatchallAlias from Alias, or at least none that would harness the
+    powers of polymorphism.
+
+    Yet, we reuse the AliasError exception class, which makes sense.
+"""
+
+from VirtualMailManager.domain import get_gid
+from VirtualMailManager.emailaddress import \
+     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
+
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class CatchallAlias(object):
+    """Class to manage domain catch-all aliases."""
+    __slots__ = ('_domain', '_dests', '_gid', '_dbh')
+
+    def __init__(self, dbh, domain):
+        self._domain = domain
+        self._dbh = dbh
+        self._gid = get_gid(self._dbh, self.domain)
+        if not self._gid:
+            raise AErr(_(u"The domain '%s' does not exist.") %
+                       self.domain, NO_SUCH_DOMAIN)
+        self._dests = []
+
+        self._load_dests()
+
+    def _load_dests(self):
+        """Loads all known destination addresses into the _dests list."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT destination FROM catchall WHERE gid = %s',
+                    (self._gid,))
+        dests = dbc.fetchall()
+        if dbc.rowcount > 0:
+            self._dests.extend(DestAddr(dest[0], self._dbh) for dest in dests)
+        dbc.close()
+
+    def _check_expansion(self, count_new):
+        """Checks the current expansion limit of the alias."""
+        postconf = Postconf(cfg_dget('bin.postconf'))
+        limit = long(postconf.read('virtual_alias_expansion_limit'))
+        dcount = len(self._dests)
+        failed = False
+        if dcount == limit or dcount + count_new > limit:
+            failed = True
+            errmsg = _(
+u"""Cannot add %(count_new)i new destination(s) to catchall alias for
+domain '%(domain)s'. Currently this alias expands into %(count)i/%(limit)i
+recipients. %(count_new)i additional destination(s) will render this alias
+unusable.
+Hint: Increase Postfix' virtual_alias_expansion_limit""")
+        elif dcount > limit:
+            failed = True
+            errmsg = _(
+u"""Cannot add %(count_new)i new destination(s) to catchall alias for
+domain '%(domain)s'. This alias already exceeds its expansion limit (%(count)i/%(limit)i).
+So its unusable, all messages addressed to this alias will be bounced.
+Hint: Delete some destination addresses.""")
+        if failed:
+            raise AErr(errmsg % {'domain': self._domain, 'count': dcount,
+                                 'limit': limit, 'count_new': count_new},
+                       ALIAS_EXCEEDS_EXPANSION_LIMIT)
+
+    def _delete(self, destination=None):
+        """Deletes a destination from the catchall alias, if ``destination``
+        is not ``None``.  If ``destination`` is None, the catchall alias with
+        all its destination addresses will be deleted.
+
+        """
+        dbc = self._dbh.cursor()
+        if not destination:
+            dbc.execute('DELETE FROM catchall WHERE gid = %s', (self._gid,))
+        else:
+            dbc.execute('DELETE FROM catchall WHERE gid = %s '
+                        'AND destination = %s', (self._gid, str(destination)))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def __len__(self):
+        """Returns the number of destinations of the catchall alias."""
+        return len(self._dests)
+
+    @property
+    def domain(self):
+        """The Alias' domain."""
+        return self._domain
+
+    def add_destinations(self, destinations, warnings=None):
+        """Adds the `EmailAddress`es from *destinations* list to the
+        destinations of the catchall alias.
+
+        Destinations, that are already assigned to the alias, will be
+        removed from *destinations*.  When done, this method will return
+        a set with all destinations, that were saved in the database.
+        """
+        destinations = set(destinations)
+        assert destinations and \
+                all(isinstance(dest, EmailAddress) for dest in destinations)
+        if not warnings is None:
+            assert isinstance(warnings, list)
+        duplicates = destinations.intersection(set(self._dests))
+        if duplicates:
+            destinations.difference_update(set(self._dests))
+            if not warnings is None:
+                warnings.extend(duplicates)
+        if not destinations:
+            return destinations
+        self._check_expansion(len(destinations))
+        dbc = self._dbh.cursor()
+        dbc.executemany("INSERT INTO catchall (gid, destination) "
+                        "VALUES (%d, %%s)" % self._gid,
+                        ((str(destination),) for destination in destinations))
+        self._dbh.commit()
+        dbc.close()
+        self._dests.extend(destinations)
+        return destinations
+
+    def del_destination(self, destination):
+        """Deletes the specified ``destination`` address from the catchall
+        alias."""
+        assert isinstance(destination, EmailAddress)
+        if not self._dests:
+            raise AErr(_(u"There are no catchall aliases defined for "
+                         u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+        if not destination in self._dests:
+            raise AErr(_(u"The address '%(addr)s' is not a destination of "
+                         u"the catchall alias for domain '%(domain)s'.")
+                       % {'addr': destination, 'domain': self._domain},
+                       NO_SUCH_ALIAS)
+        self._delete(destination)
+        self._dests.remove(destination)
+
+    def get_destinations(self):
+        """Returns an iterator for all destinations of the catchall alias."""
+        if not self._dests:
+            raise AErr(_(u"There are no catchall aliases defined for "
+                         u"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 catchall aliases defined for "
+                         u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+        self._delete()
+        del self._dests[:]
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/__init__.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,100 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's command line interface.
+"""
+
+import os
+from array import array
+from fcntl import ioctl
+from getpass import getpass
+from termios import TIOCGWINSZ
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.constants import VMM_TOO_MANY_FAILURES
+from VirtualMailManager.errors import VMMError
+
+
+__all__ = ('prog', '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')
+
+
+def w_err(code, *args):
+    """Writes a line for each arg of *args*, encoded in the current
+    ENCODING, to stderr.
+    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')
+    if code:
+        os.sys.exit(code)
+
+
+def get_winsize():
+    """Returns a tuple of integers ``(ws_row, ws_col)`` with the height and
+    width of the terminal."""
+    fd = None
+    for dev in (os.sys.stdout, os.sys.stderr, os.sys.stdin):
+        if hasattr(dev, 'fileno') and os.isatty(dev.fileno()):
+            fd = dev.fileno()
+            break
+    if fd is None:  # everything seems to be redirected
+        # fall back to environment or assume some common defaults
+        ws_row, ws_col = 24, 80
+        try:
+            ws_col = int(os.environ.get('COLUMNS', 80))
+            ws_row = int(os.environ.get('LINES', 24))
+        except ValueError:
+            pass
+        return ws_row, ws_col
+    #"struct winsize" with the ``unsigned short int``s ws_{row,col,{x,y}pixel}
+    ws = array('H', (0, 0, 0, 0))
+    ioctl(fd, TIOCGWINSZ, ws, True)
+    ws_row, ws_col = ws[:2]
+    return ws_row, ws_col
+
+
+def read_pass():
+    """Interactive 'password chat', returns the password in plain format.
+
+    Throws a VMMError after the third failure.
+    """
+    # TP: Please preserve the trailing space.
+    readp_msg0 = _(u'Enter new password: ').encode(ENCODING, 'replace')
+    # TP: Please preserve the trailing space.
+    readp_msg1 = _(u'Retype new password: ').encode(ENCODING, 'replace')
+    mismatched = True
+    failures = 0
+    while mismatched:
+        if failures > 2:
+            raise VMMError(_(u'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.'))
+            continue
+        if not clear0:
+            failures += 1
+            w_err(0, _(u'Sorry, empty passwords are not permitted.'))
+            continue
+        mismatched = False
+    return clear0
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/config.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,96 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.config
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Adds some interactive stuff to the Config class.
+"""
+
+from ConfigParser import RawConfigParser
+from shutil import copy2
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.config import Config, ConfigValueError, LazyConfig
+from VirtualMailManager.errors import ConfigError, VMMError
+from VirtualMailManager.cli import w_err, w_std
+from VirtualMailManager.constants import CONF_ERROR, VMM_TOO_MANY_FAILURES
+
+_ = lambda msg: msg
+
+
+class CliConfig(Config):
+    """Adds the interactive ``configure`` method to the `Config` class
+    and overwrites `LazyConfig.set(), in order to update a single option
+    in the configuration file with a single command line command.
+    """
+
+    def configure(self, sections):
+        """Interactive method for configuring all options of the given
+        iterable ``sections`` object."""
+        input_fmt = _(u'Enter new value for option %(option)s '
+                      u'[%(current_value)s]: ')
+        failures = 0
+
+        w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
+        for section in sections:
+            w_std(_(u"* Configuration section: '%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 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)
+                            failures += 1
+                            if failures > 2:
+                                raise ConfigError(_(u'Too many failures - try '
+                                                    u'again later.'),
+                                                  VMM_TOO_MANY_FAILURES)
+                    else:
+                        break
+            print
+        if self._modified:
+            self._save_changes()
+
+    def set(self, option, value):
+        """Set the value of an option.
+
+        If the new `value` has been set, the configuration file will be
+        immediately updated.
+
+        Throws a ``ConfigError`` if `value` couldn't be converted to
+        ``LazyConfigOption.cls`` or ``LazyConfigOption.validate`` fails."""
+        section, option_ = self._get_section_option(option)
+        try:
+            val = self._cfg[section][option_].cls(value)
+            if self._cfg[section][option_].validate:
+                val = self._cfg[section][option_].validate(val)
+        except (ValueError, ConfigValueError), 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:
+            old_val = self.dget(option)
+        else:
+            old_val = self.pget(option)
+        if val == old_val:
+            return
+        if not RawConfigParser.has_section(self, section):
+            self.add_section(section)
+        RawConfigParser.set(self, section, option_, val)
+        self._save_changes()
+
+    def _save_changes(self):
+        """Writes changes to the configuration file."""
+        copy2(self._cfg_filename, self._cfg_filename + '.bak')
+        self._cfg_file = open(self._cfg_filename, 'w')
+        self.write(self._cfg_file)
+        self._cfg_file.close()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/handler.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,99 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.handler
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    A derived Handler class with a few changes/additions for cli use.
+"""
+
+import os
+
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.handler import Handler
+from VirtualMailManager.cli import read_pass
+from VirtualMailManager.cli.config import CliConfig as Cfg
+from VirtualMailManager.constants import ACCOUNT_EXISTS, INVALID_SECTION, \
+     NO_SUCH_ACCOUNT, TYPE_ACCOUNT
+from VirtualMailManager.password import randompw
+
+_ = lambda msg: msg
+
+
+class CliHandler(Handler):
+    """This class uses a `CliConfig` for configuration stuff, instead of
+    the non-interactive `Config` class.
+
+    It provides the additional methods cfgSet() and configure().
+
+    Additionally it uses `VirtualMailManager.cli.read_pass()` for for the
+    interactive password dialog.
+    """
+
+    __slots__ = ()  # nothing additional, also no __dict__/__weakref__
+
+    def __init__(self):
+        """Creates a new CliHandler instance.
+
+        Throws a NotRootError if your uid is greater 0.
+        """
+        # Overwrite the parent CTor partly, we use the CliConfig class
+        # and add some command line checks.
+        skip_some_checks = os.sys.argv[1] in ('cf', 'configure', 'h', 'help',
+                                              'v', 'version')
+        super(CliHandler, self).__init__(skip_some_checks)
+
+        self._cfg = Cfg(self._cfg_fname)
+        self._cfg.load()
+
+    def cfg_set(self, option, value):
+        """Set a new value for the given option."""
+        return self._cfg.set(option, value)
+
+    def configure(self, section=None):
+        """Starts the interactive configuration.
+
+        Configures in interactive mode options in the given ``section``.
+        If no section is given (default) all options from all sections
+        will be prompted.
+        """
+        if section is None:
+            self._cfg.configure(self._cfg.sections())
+        elif self._cfg.has_section(section):
+            self._cfg.configure([section])
+        else:
+            raise VMMError(_(u"Invalid section: '%s'") % section,
+                           INVALID_SECTION)
+
+    def user_add(self, emailaddress, password=None):
+        """Override the parent user_add() - add the interactive password
+        dialog.
+
+        Returns the generated password, if account.random_password == True.
+        """
+        acc = self._get_account(emailaddress)
+        if acc:
+            raise VMMError(_(u"The account '%s' already exists.") %
+                           acc.address, ACCOUNT_EXISTS)
+        self._is_other_address(acc.address, TYPE_ACCOUNT)
+        rand_pass = self._cfg.dget('account.random_password')
+        if password is None:
+            password = (read_pass, randompw)[rand_pass]()
+        acc.set_password(password)
+        acc.save()
+        self._make_account_dirs(acc)
+        return (None, password)[rand_pass]
+
+    def user_password(self, emailaddress, password=None):
+        """Override the parent user_password() - add the interactive
+        password dialog."""
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        if not isinstance(password, basestring) or not password:
+            password = read_pass()
+        acc.modify('password', password)
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/main.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,80 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.main
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's command line interface.
+"""
+
+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
+
+
+_ = lambda msg: msg
+
+
+def _get_handler():
+    """Try to get a CliHandler. Exit the program when an error occurs."""
+    try:
+        handler = CliHandler()
+    except (errors.NotRootError, errors.PermissionError, errors.VMMError,
+            errors.ConfigError), err:
+        w_err(err.code, _(u'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)
+
+    handler = _get_handler()
+    run_ctx = RunContext(argv, handler, sub_cmd)
+    try:
+        cmd_func(run_ctx)
+    except (EOFError, KeyboardInterrupt):
+        # TP: We have to cry, because root has killed/interrupted vmm
+        # with Ctrl+C or Ctrl+D.
+        w_err(EX_USER_INTERRUPT, '', _(u'Ouch!'), '')
+    except errors.VMMError, err:
+        if err.code != DATABASE_ERROR:
+            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(INVALID_ARGUMENT,
+              _(u"Error: Unknown section: '%s'") % err.section)
+    except NoOptionError, err:
+        w_err(INVALID_ARGUMENT,
+              _(u"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())
+    return EX_SUCCESS
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/subcommands.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,1091 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.subcommands
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's cli subcommands.
+"""
+
+import locale
+import os
+
+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.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
+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',
+)
+
+_ = 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')
+
+    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)
+
+
+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')
+
+    def __init__(self, argv, handler, command):
+        """Create a new RunContext"""
+        self.argc = len(argv)
+        self.args = [unicode(arg, ENCODING) for arg in argv]
+        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:])
+
+
+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])
+
+
+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()
+    try:
+        _print_aliase_info(address, ctx.hdlr.alias_info(address))
+    except VMMError, err:
+        if err.code is ACCOUNT_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+                  'object': address})
+            ctx.scmd = ctx.args[1] = 'userinfo'
+            user_info(ctx)
+        elif err.code is RELOCATED_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
+                  'object': address})
+            ctx.scmd = ctx.args[1] = 'relocatedinfo'
+            relocated_info(ctx)
+        else:
+            raise
+
+
+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())
+
+
+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())
+
+
+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)
+    try:
+        _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
+    except VMMError, 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'
+            domain_info(ctx)
+        else:
+            raise
+
+
+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())
+
+
+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:])
+
+
+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.'), 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])
+
+
+def catchall_info(ctx):
+    """show the catchall destination(s) of the specified domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing domain.'), ctx.scmd)
+    address = ctx.args[2].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()
+    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])
+
+
+def configure(ctx):
+    """start interactive configuration modus"""
+    if ctx.argc < 3:
+        ctx.hdlr.configure()
+    else:
+        ctx.hdlr.configure(ctx.args[2].lower())
+
+
+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])
+    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
+        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)
+
+
+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)
+    try:
+        info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
+    except VMMError, 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'
+            aliasdomain_info(ctx)
+        else:
+            raise
+    else:
+        q_limit = u'Storage: %(bytes)s; Messages: %(messages)s'
+        if not details:
+            info['bytes'] = human_size(info['bytes'])
+            info['messages'] = locale.format('%d', info['messages'], True)
+            info['quota limit/user'] = q_limit % info
+            _print_info(ctx, info, _(u'Domain'))
+        else:
+            info[0]['bytes'] = human_size(info[0]['bytes'])
+            info[0]['messages'] = locale.format('%d', info[0]['messages'],
+                                                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'))
+            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'))
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+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)
+        # FIXME
+        w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)')
+
+    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 = ''
+
+
+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()
+    _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:'))
+    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))), '')
+
+    txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si
+    txt_wrpr.width = txt_wrpr.width + 8
+
+
+def list_addresses(ctx, limit=None):
+    """List all addresses / search addresses by pattern. The output can be
+    limited with TYPE_ACCOUNT, TYPE_ALIAS and TYPE_RELOCATED, which can be
+    bitwise ORed as a combination. Not specifying a limit is the same as
+    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)
+    _print_address_list(limit, gids, addresses, matching)
+
+
+def list_users(ctx):
+    """list all user accounts / search user accounts by pattern"""
+    return list_addresses(ctx, TYPE_ACCOUNT)
+
+def list_aliases(ctx):
+    """list all aliases / search aliases by pattern"""
+    return list_addresses(ctx, TYPE_ALIAS)
+
+def list_relocated(ctx):
+    """list all relocated records / search relocated records by pattern"""
+    return list_addresses(ctx, TYPE_RELOCATED)
+
+
+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])
+
+
+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())
+
+
+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()
+    try:
+        _print_relocated_info(addr=relocated,
+                              dest=ctx.hdlr.relocated_info(relocated))
+    except VMMError, err:
+        if err.code is ACCOUNT_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+                  'object': relocated})
+            ctx.scmd = ctx.args[1] = 'userinfoi'
+            user_info(ctx)
+        elif err.code is ALIAS_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
+                  'object': relocated})
+            ctx.scmd = ctx.args[1] = 'aliasinfo'
+            alias_info(ctx)
+        else:
+            raise
+
+
+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)
+
+
+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)
+
+
+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)
+    try:
+        info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
+    except VMMError, 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'
+            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'
+            relocated_info(ctx)
+        else:
+            raise
+    else:
+        if 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'))
+        else:
+            info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'],
+                    info[0]['uq_bytes'], True, info[0]['ql_domaindefault'])
+            info[0]['quota messages'] = \
+                _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'))
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+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)
+
+
+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.')))
+
+
+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 or update 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'update 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 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'update 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 pattern')),
+    'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
+                      _(u'list all user accounts or search for accounts by 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 entries or search for entries 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 modus')),
+    '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 _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 \
+           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))
+        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))
+    return order
+
+
+def _format_quota_usage(limit, used, human=False, domaindefault=False):
+    """Put quota's limit / usage / percentage in a formatted string."""
+    if human:
+        q_usage = {
+            'used': human_size(used),
+            'limit': human_size(limit),
+        }
+    else:
+        q_usage = {
+            '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
+    return fmt(_(u'[%(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))
+    for key, upper in _get_order(ctx):
+        if upper:
+            w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key]))
+        else:
+            w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key]))
+    print
+    note = info.get('note', None)
+    if note is not None:
+        _print_note(note)
+
+def _print_note(note):
+    msg = _(u'Note')
+    w_std(msg, u'-' * len(msg))
+    old_ii = txt_wrpr.initial_indent
+    old_si = txt_wrpr.subsequent_indent
+    txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
+    txt_wrpr.width -= 8
+    for para in note.split('\n'):
+        w_std(txt_wrpr.fill(para))
+    txt_wrpr.width += 8
+    txt_wrpr.subsequent_indent = old_si
+    txt_wrpr.initial_indent = old_ii
+
+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))
+    if alist:
+        if title != _(u'alias domains'):
+            w_std(*(u'\t%s' % item for item in alist))
+        else:
+            for domain in alist:
+                if not domain.startswith('xn--'):
+                    w_std(u'\t%s' % domain)
+                else:
+                    w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
+        print
+    else:
+        w_std(_(u'\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
+
+
+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 localparts in domain %s will be sent to:')
+          % domain)
+    w_std(*(u'\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, '')
+
+
+def _format_domain(domain, main=True):
+    """format (prefix/convert) the domain name."""
+    if domain.startswith('xn--'):
+        domain = u'%s (%s)' % (domain, domain.decode('idna'))
+    if main:
+        return u'\t[+] %s' % domain
+    return u'\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')
+    w_std(title, '-' * len(title))
+    if domains:
+        for did in dids:
+            if domains[did][0] is not None:
+                w_std(_format_domain(domains[did][0]))
+            if len(domains[did]) > 1:
+                w_std(*(_format_domain(a, False) for a in domains[did][1:]))
+    else:
+        w_std(_('\tNone'))
+    print
+
+
+def _print_address_list(which, dids, addresses, matching):
+    """Print a list of (matching) addresses."""
+    _trans = { TYPE_ACCOUNT                  : _('user accounts')
+             , TYPE_ALIAS                    : _('aliases')
+             , TYPE_RELOCATED                : _('relocated entries')
+             , TYPE_ACCOUNT | TYPE_ALIAS
+                 : _('user accounts and aliases')
+             , TYPE_ACCOUNT | TYPE_RELOCATED
+                 : _('user accounts and relocated entries')
+             , TYPE_ALIAS | TYPE_RELOCATED
+                 : _('aliases and relocated entries')
+             , TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED : _('addresses')
+             }
+    try:
+        if matching:
+            title = _(u'Matching %s') % _trans[which]
+        else:
+            title = _(u'Existing %s') % _trans[which]
+        w_std(title, '-' * len(title))
+    except KeyError:
+        raise VMMError(_("Invalid address type for list: '%s'") % which,
+                       INVALID_ARGUMENT)
+    if addresses:
+        if which & (which - 1) == 0:
+            # only one type is requested, so no type indicator
+            _trans = { TYPE_ACCOUNT   : _('')
+                     , TYPE_ALIAS     : _('')
+                     , TYPE_RELOCATED : _('')
+                     }
+        else:
+            _trans = { TYPE_ACCOUNT   : _('u')
+                     , TYPE_ALIAS     : _('a')
+                     , TYPE_RELOCATED : _('r')
+                     }
+        for did in dids:
+            for addr, atype, aliasdomain in addresses[did]:
+                if aliasdomain:
+                    leader = '[%s-]' % _trans[atype]
+                else:
+                    leader = '[%s+]' % _trans[atype]
+                w_std('\t%s %s' % (leader, addr))
+    else:
+        w_std(_('\tNone'))
+    print
+
+
+def _print_aliasdomain_info(info):
+    """Print alias domain information."""
+    title = _(u'Alias domain information')
+    for key in ('alias', 'domain'):
+        if info[key].startswith('xn--'):
+            info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
+    w_std(title, '-' * len(title),
+          _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
+          info, '')
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/common.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,262 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.common
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Some common functions
+"""
+
+import locale
+import os
+import re
+import stat
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.constants import NOT_EXECUTABLE, NO_SUCH_BINARY, \
+     TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
+from VirtualMailManager.errors import VMMError
+
+VERSION_RE = re.compile(r'^(\d+)\.(\d+)\.(?:(\d+)|(alpha|beta|rc)(\d+))$')
+
+_version_level = dict(alpha=0xA, beta=0xB, rc=0xC)
+_version_cache = {}
+_ = lambda msg: msg
+
+
+def expand_path(path):
+    """Expands paths, starting with ``.`` or ``~``, to an absolute path."""
+    if path.startswith('.'):
+        return os.path.abspath(path)
+    if path.startswith('~'):
+        return os.path.expanduser(path)
+    return path
+
+
+def get_unicode(string):
+    """Converts `string` to `unicode`, if necessary."""
+    if isinstance(string, unicode):
+        return string
+    return unicode(string, ENCODING, 'replace')
+
+
+def lisdir(path):
+    """Checks if `path` is a directory.  Doesn't follow symbolic links.
+    Returns bool.
+    """
+    try:
+        lstat = os.lstat(path)
+    except OSError:
+        return False
+    return stat.S_ISDIR(lstat.st_mode)
+
+
+def exec_ok(binary):
+    """Checks if the `binary` exists and if it is executable.
+
+    Throws a `VMMError` if the `binary` isn't a file or is not
+    executable.
+    """
+    binary = expand_path(binary)
+    if not os.path.isfile(binary):
+        raise VMMError(_(u"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'") %
+                       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)):
+        try:
+            size = long(size)
+        except ValueError:
+            raise TypeError("'size' must be a positive long or int.")
+    if size < 0:
+        raise ValueError("'size' must be a positive long or int.")
+    if size < 1024:
+        return str(size)
+    prefix_multiply = ((_(u'TiB'), 1 << 40), (_(u'GiB'), 1 << 30),
+                       (_(u'MiB'), 1 << 20), (_(u'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') % {
+                    'size': locale.format('%.2f', float(size) / multiply,
+                                          True),
+                    'prefix': prefix}
+
+
+def size_in_bytes(size):
+    """Converts the string `size` to a long (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:
+        raise TypeError('size must be a non empty string.')
+    if size[-1].upper() in ('B', 'K', 'M', 'G'):
+        try:
+            num = int(size[:-1])
+        except ValueError:
+            raise ValueError('Not a valid integer value: %r' % size[:-1])
+        unit = size[-1].upper()
+        if unit == 'B':
+            return num
+        elif unit == 'K':
+            return num << 10L
+        elif unit == 'M':
+            return num << 20L
+        else:
+            return num << 30L
+    else:
+        try:
+            num = int(size)
+        except ValueError:
+            raise ValueError('Not a valid size value: %r' % size)
+        return num
+
+
+def version_hex(version_string):
+    """Converts a Dovecot version, e.g.: '1.2.3' or '2.0.beta4', to an int.
+    Raises a `ValueError` if the *version_string* has the wrong™ format.
+
+    version_hex('1.2.3') -> 270548736
+    hex(version_hex('1.2.3')) -> '0x10203f00'
+    """
+    global _version_cache
+    if version_string in _version_cache:
+        return _version_cache[version_string]
+    version = 0
+    version_mo = VERSION_RE.match(version_string)
+    if not version_mo:
+        raise ValueError('Invalid version string: %r' % version_string)
+    major, minor, patch, level, serial = version_mo.groups()
+    major = int(major)
+    minor = int(minor)
+    if patch:
+        patch = int(patch)
+    if serial:
+        serial = int(serial)
+
+    if major > 0xFF or minor > 0xFF or \
+      patch and patch > 0xFF or serial and serial > 0xFF:
+        raise ValueError('Invalid version string: %r' % version_string)
+
+    version += major << 28
+    version += minor << 20
+    if patch:
+        version += patch << 12
+    version += _version_level.get(level, 0xF) << 8
+    if serial:
+        version += serial
+
+    _version_cache[version_string] = version
+    return version
+
+
+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 `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)
+    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()))
+    if level == 0xF and not serial:
+        version_string = '%u.%u.%u' % (major, minor, patch)
+    elif level in levels and not patch:
+        version_string = '%u.%u.%s%u' % (major, minor, levels[level], serial)
+    else:
+        raise ValueError('Invalid version: %r' % hex(version))
+
+    _version_cache[version] = version_string
+    return version_string
+
+def format_domain_default(domaindata):
+    """Format info output when the value displayed is the domain default."""
+    return _(u'%s [domain default]') % domaindata
+
+
+def search_addresses(dbh, typelimit=None, lpattern=None, llike=False,
+                     dpattern=None, dlike=False):
+    """'Search' for addresses by *pattern* in the database.
+
+    The search is limited by *typelimit*, a bitfield with values TYPE_ACCOUNT,
+    TYPE_ALIAS, TYPE_RELOCATED, or a bitwise OR thereof. If no limit is
+    specified, all types will be searched.
+
+    *lpattern* may be a local part or a partial local part - starting and/or
+    ending with a '%' sign.  When the *lpattern* starts or ends with a '%' sign
+    *llike* has to be `True` to perform a wildcard search. To retrieve all
+    available addresses use the arguments' default values.
+
+    *dpattern* and *dlike* behave analogously for the domain part of an
+    address, allowing for separate pattern matching: testuser%@example.%
+
+    The return value of this function is a tuple. The first element is a list
+    of domain IDs sorted alphabetically by the corresponding domain names. The
+    second element is a dictionary indexed by domain ID, holding lists to
+    associated addresses. Each address is itself actually a tuple of address,
+    type, and boolean indicating whether the address stems from an alias
+    domain.
+    """
+    if typelimit == None:
+            typelimit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
+    queries = []
+    if typelimit & TYPE_ACCOUNT:
+        queries.append('SELECT gid, local_part, %d AS type FROM users'
+                       % TYPE_ACCOUNT)
+    if typelimit & TYPE_ALIAS:
+        queries.append('SELECT gid, address as local_part, %d AS type '
+                       'FROM alias' % TYPE_ALIAS)
+    if typelimit & TYPE_RELOCATED:
+        queries.append('SELECT gid, address as local_part, %d AS type '
+                       'FROM relocated' % TYPE_RELOCATED)
+    sql  = "SELECT gid, local_part || '@' || domainname AS address, "
+    sql += 'type, NOT is_primary AS from_aliasdomain FROM ('
+    sql += ' UNION '.join(queries)
+    sql += ') a JOIN domain_name USING (gid)'
+    nextkw = 'WHERE'
+    sqlargs = []
+    for like, field, pattern in ((dlike, 'domainname', dpattern),
+                                 (llike, 'local_part', lpattern)):
+        if like:
+            match = 'LIKE'
+        else:
+            if not pattern: continue
+            match = '='
+        sql += ' %s %s %s %%s' % (nextkw, field, match)
+        sqlargs.append(pattern)
+        nextkw = 'AND'
+    sql += ' ORDER BY domainname, local_part'
+    dbc = dbh.cursor()
+    dbc.execute(sql, sqlargs)
+    result = dbc.fetchall()
+    dbc.close()
+
+    gids = []
+    daddrs = {}
+    lastgid = None
+    for gid, address, addrtype, aliasdomain in result:
+        if gid != lastgid:
+            gids.append(gid)
+            lastgid = gid
+            daddrs[gid] = []
+        daddrs[gid].append((address, addrtype, aliasdomain))
+    return gids, daddrs
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/config.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,540 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.config
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VMM's configuration module for simplified configuration access.
+"""
+
+from ConfigParser import \
+     Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
+     ParsingError, RawConfigParser
+from cStringIO import StringIO
+
+from VirtualMailManager.common import VERSION_RE, \
+     exec_ok, expand_path, get_unicode, lisdir, size_in_bytes, version_hex
+from VirtualMailManager.constants import CONF_ERROR
+from VirtualMailManager.errors import ConfigError, VMMError
+from VirtualMailManager.maillocation import known_format
+from VirtualMailManager.password import verify_scheme as _verify_scheme
+
+DB_MODULES = ('psycopg2', 'pypgsql')
+DB_SSL_MODES = ('allow', 'disabled', 'prefer', 'require', 'verify-ca',
+                'verify-full')
+
+_ = lambda msg: msg
+
+
+class BadOptionError(Error):
+    """Raised when a option isn't in the format 'section.option'."""
+    pass
+
+
+class ConfigValueError(Error):
+    """Raised when creating or validating of new values fails."""
+    pass
+
+
+class NoDefaultError(Error):
+    """Raised when the requested option has no default value."""
+
+    def __init__(self, section, option):
+        Error.__init__(self, 'Option %r in section %r has no default value' %
+                             (option, section))
+
+
+class LazyConfig(RawConfigParser):
+    """The **lazy** derivate of the `RawConfigParser`.
+
+    There are two additional getters:
+
+    `pget()`
+      The polymorphic getter, which returns a option's value with the
+      appropriate type.
+    `dget()`
+      Like `LazyConfig.pget()`, but returns the option's default, from
+      `LazyConfig._cfg['sectionname']['optionname'].default`, if the
+      option is not configured in a ini-like configuration file.
+
+    `set()` differs from `RawConfigParser`'s `set()` method. `set()`
+    takes the `section` and `option` arguments combined to a single
+    string in the form "section.option".
+    """
+
+    def __init__(self):
+        RawConfigParser.__init__(self)
+        self._modified = False
+        # sample _cfg dict.  Create your own in your derived class.
+        self._cfg = {
+            'sectionname': {
+                'optionname': LazyConfigOption(int, 1, self.getint),
+            }
+        }
+
+    def bool_new(self, value):
+        """Converts the string `value` into a `bool` and returns it.
+
+        | '1', 'on', 'yes' and 'true' will become `True`
+        | '0', 'off', 'no' and 'false' will become `False`
+
+        Throws a `ConfigValueError` for all other values, except bools.
+        """
+        if isinstance(value, bool):
+            return value
+        if value.lower() in self._boolean_states:
+            return self._boolean_states[value.lower()]
+        else:
+            raise ConfigValueError(_(u"Not a boolean: '%s'") %
+                                   get_unicode(value))
+
+    def getboolean(self, section, option):
+        """Returns the boolean value of the option, in the given
+        section.
+
+        For a boolean True, the value must be set to '1', 'on', 'yes',
+        'true' or True. For a boolean False, the value must set to '0',
+        'off', 'no', 'false' or False.
+        If the option has another value assigned this method will raise
+        a ValueError.
+        """
+        # if the setting was modified it may be still a boolean value lets see
+        tmp = self.get(section, option)
+        if isinstance(tmp, bool):
+            return tmp
+        if not tmp.lower() in self._boolean_states:
+            raise ValueError('Not a boolean: %s' % tmp)
+        return self._boolean_states[tmp.lower()]
+
+    def _get_section_option(self, section_option):
+        """splits ``section_option`` (section.option) in two parts and
+        returns them as list ``[section, option]``, if:
+
+          * it likes the format of ``section_option``
+          * the ``section`` is known
+          * the ``option`` is known
+
+        Else one of the following exceptions will be thrown:
+
+          * `BadOptionError`
+          * `NoSectionError`
+          * `NoOptionError`
+        """
+        sect_opt = section_option.lower().split('.')
+        # TODO: cache it
+        if len(sect_opt) != 2 or not sect_opt[0] or not sect_opt[1]:
+            raise BadOptionError(_(u"Bad format: '%s' - expected: "
+                                   u"section.option") %
+                                 get_unicode(section_option))
+        if not sect_opt[0] in self._cfg:
+            raise NoSectionError(sect_opt[0])
+        if not sect_opt[1] in self._cfg[sect_opt[0]]:
+            raise NoOptionError(sect_opt[1], sect_opt[0])
+        return sect_opt
+
+    def items(self, section):
+        """returns an iterable that returns key, value ``tuples`` from
+        the given ``section``.
+        """
+        if section in self._sections:  # check if the section was parsed
+            sect = self._sections[section]
+        elif not section in self._cfg:
+            raise NoSectionError(section)
+        else:
+            return ((k, self._cfg[section][k].default) \
+                    for k in self._cfg[section].iterkeys())
+        # still here? Get defaults and merge defaults with configured setting
+        defaults = dict((k, self._cfg[section][k].default) \
+                        for k in self._cfg[section].iterkeys())
+        defaults.update(sect)
+        if '__name__' in defaults:
+            del defaults['__name__']
+        return defaults.iteritems()
+
+    def dget(self, option):
+        """Returns the value of the `option`.
+
+        If the option could not be found in the configuration file, the
+        configured default value, from ``LazyConfig._cfg`` will be
+        returned.
+
+        Arguments:
+
+        `option` : string
+            the configuration option in the form "section.option"
+
+        Throws a `NoDefaultError`, if no default value was passed to
+        `LazyConfigOption.__init__()` for the `option`.
+        """
+        section, option = self._get_section_option(option)
+        try:
+            return self._cfg[section][option].getter(section, option)
+        except (NoSectionError, NoOptionError):
+            if not self._cfg[section][option].default is None:  # may be False
+                return self._cfg[section][option].default
+            else:
+                raise NoDefaultError(section, option)
+
+    def pget(self, option):
+        """Returns the value of the `option`."""
+        section, option = self._get_section_option(option)
+        return self._cfg[section][option].getter(section, option)
+
+    def set(self, option, value):
+        """Set the `value` of the `option`.
+
+        Throws a `ValueError` if `value` couldn't be converted using
+        `LazyConfigOption.cls`.
+        """
+        # pylint: disable=W0221
+        # @pylint: _L A Z Y_
+        section, option = self._get_section_option(option)
+        val = self._cfg[section][option].cls(value)
+        if self._cfg[section][option].validate:
+            val = self._cfg[section][option].validate(val)
+        if not RawConfigParser.has_section(self, section):
+            self.add_section(section)
+        RawConfigParser.set(self, section, option, val)
+        self._modified = True
+
+    def has_section(self, section):
+        """Checks if `section` is a known configuration section."""
+        return section.lower() in self._cfg
+
+    def has_option(self, option):
+        """Checks if the option (section.option) is a known
+        configuration option.
+        """
+        # pylint: disable=W0221
+        # @pylint: _L A Z Y_
+        try:
+            self._get_section_option(option)
+            return True
+        except(BadOptionError, NoSectionError, NoOptionError):
+            return False
+
+    def sections(self):
+        """Returns an iterator object for all configuration sections."""
+        return self._cfg.iterkeys()
+
+
+class LazyConfigOption(object):
+    """A simple container class for configuration settings.
+
+    `LazyConfigOption` instances are required by `LazyConfig` instances,
+    and instances of classes derived from `LazyConfig`, like the
+    `Config` class.
+    """
+    __slots__ = ('__cls', '__default', '__getter', '__validate')
+
+    def __init__(self, cls, default, getter, validate=None):
+        """Creates a new `LazyConfigOption` instance.
+
+        Arguments:
+
+        `cls` : type
+          The class/type of the option's value
+        `default`
+          Default value of the option. Use ``None`` if the option should
+          not have a default value.
+        `getter` : callable
+          A method's name of `RawConfigParser` and derived classes, to
+          get a option's value, e.g. `self.getint`.
+        `validate` : NoneType or a callable
+          None or any method, that takes one argument, in order to
+          check the value, when `LazyConfig.set()` is called.
+        """
+        self.__cls = cls
+        if not default is None:  # enforce the type of the default value
+            self.__default = self.__cls(default)
+        else:
+            self.__default = default
+        if not callable(getter):
+            raise TypeError('getter has to be a callable, got a %r' %
+                            getter.__class__.__name__)
+        self.__getter = getter
+        if validate and not callable(validate):
+            raise TypeError('validate has to be callable or None, got a %r' %
+                            validate.__class__.__name__)
+        self.__validate = validate
+
+    @property
+    def cls(self):
+        """The class of the option's value e.g. `str`, `unicode` or `bool`."""
+        return self.__cls
+
+    @property
+    def default(self):
+        """The option's default value, may be `None`"""
+        return self.__default
+
+    @property
+    def getter(self):
+        """The getter method or function to get the option's value"""
+        return self.__getter
+
+    @property
+    def validate(self):
+        """A method or function to validate the value"""
+        return self.__validate
+
+
+class Config(LazyConfig):
+    """This class is for reading vmm's configuration file."""
+
+    def __init__(self, filename):
+        """Creates a new Config instance
+
+        Arguments:
+
+        `filename` : str
+          path to the configuration file
+        """
+        LazyConfig.__init__(self)
+        self._cfg_filename = filename
+        self._cfg_file = None
+        self._missing = {}
+
+        LCO = LazyConfigOption
+        bool_t = self.bool_new
+        self._cfg = {
+            'account': {
+                'delete_directory': LCO(bool_t, False, self.getboolean),
+                'directory_mode': LCO(int, 448, self.getint),
+                'disk_usage': LCO(bool_t, False, self.getboolean),
+                'password_length': LCO(int, 8, self.getint),
+                'random_password': LCO(bool_t, False, self.getboolean),
+            },
+            'bin': {
+                'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
+                                 exec_ok),
+                'du': LCO(str, '/usr/bin/du', self.get, exec_ok),
+                'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
+            },
+            'database': {
+                'host': LCO(str, 'localhost', self.get),
+                'module': LCO(str, 'psycopg2', self.get, check_db_module),
+                'name': LCO(str, 'mailsys', self.get),
+                'pass': LCO(str, None, self.get),
+                'port': LCO(int, 5432, self.getint),
+                'sslmode': LCO(str, 'prefer', self.get, check_db_ssl_mode),
+                'user': LCO(str, None, self.get),
+            },
+            'domain': {
+                'auto_postmaster': LCO(bool_t, True, self.getboolean),
+                'delete_directory': LCO(bool_t, False, self.getboolean),
+                'directory_mode': LCO(int, 504, self.getint),
+                'force_deletion': LCO(bool_t, False, self.getboolean),
+                'imap': LCO(bool_t, True, self.getboolean),
+                'pop3': LCO(bool_t, True, self.getboolean),
+                'sieve': LCO(bool_t, True, self.getboolean),
+                'smtp': LCO(bool_t, True, self.getboolean),
+                'quota_bytes': LCO(str, '0', self.get_in_bytes,
+                                   check_size_value),
+                'quota_messages': LCO(int, 0, self.getint),
+                'transport': LCO(str, 'dovecot:', self.get),
+            },
+            'mailbox': {
+                'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
+                               self.unicode),
+                'format': LCO(str, 'maildir', self.get, check_mailbox_format),
+                'root': LCO(str, 'Maildir', self.unicode),
+                'subscribe': LCO(bool_t, True, self.getboolean),
+            },
+            'misc': {
+                'base_directory': LCO(str, '/srv/mail', self.get, is_dir),
+                'crypt_blowfish_rounds': LCO(int, 5, self.getint),
+                'crypt_sha256_rounds': LCO(int, 5000, self.getint),
+                'crypt_sha512_rounds': LCO(int, 5000, self.getint),
+                'dovecot_version': LCO(str, None, self.hexversion,
+                                       check_version_format),
+                'password_scheme': LCO(str, 'CRAM-MD5', self.get,
+                                       verify_scheme),
+            },
+        }
+
+    def load(self):
+        """Loads the configuration, read only.
+
+        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()
+
+    def check(self):
+        """Performs a configuration check.
+
+        Raises a ConfigError if settings w/o a default value are missed.
+        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)
+            self._missing.clear()
+
+        errmsg = None
+        self._chk_non_default()
+        miss_vers = 'misc' in self._missing and \
+                    'dovecot_version' in self._missing['misc']
+        if self._missing:
+            errmsg = StringIO()
+            errmsg.write(_(u'Check of configuration file %s failed.\n') %
+                         self._cfg_filename)
+            errmsg.write(_(u'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') %
+                             self._cfg_filename)
+                errmsg.write(_(u'Invalid configuration values.\n'))
+            else:
+                errmsg.write('\n' + _(u'Invalid configuration values.\n'))
+            iter_dict()
+        if errmsg:
+            raise ConfigError(errmsg.getvalue(), CONF_ERROR)
+
+    def hexversion(self, section, option):
+        """Converts the version number (e.g.: 1.2.3) from the *option*'s
+        value to an int."""
+        return version_hex(self.get(section, option))
+
+    def get_in_bytes(self, section, option):
+        """Converts the size value (e.g.: 1024k) from the *option*'s
+        value to a long"""
+        return size_in_bytes(self.get(section, option))
+
+    def unicode(self, section, option):
+        """Returns the value of the `option` from `section`, converted
+        to Unicode."""
+        return get_unicode(self.get(section, option))
+
+    def _chk_non_default(self):
+        """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():
+            missing = []
+            for option, value in self._cfg[section].iteritems():
+                if (value.default is None and
+                    not RawConfigParser.has_option(self, section, option)):
+                    missing.append(option)
+            if missing:
+                self._missing[section] = missing
+
+    def _chk_possible_values(self, miss_vers):
+        """Check settings for which the possible values are known."""
+        if not miss_vers:
+            value = self.get('misc', 'dovecot_version')
+            if not VERSION_RE.match(value):
+                self._missing['misc'] = ['version: ' +\
+                        _(u"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)
+        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)
+        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]
+        # section domain
+        try:
+            value = self.dget('domain.quota_bytes')
+        except (ValueError, TypeError), err:
+            self._missing['domain'] = [u'quota_bytes: ' + str(err)]
+
+
+def is_dir(path):
+    """Check if the expanded path is a directory.  When the expanded path
+    is a directory the expanded path will be returned.  Otherwise a
+    ConfigValueError will be raised.
+    """
+    path = expand_path(path)
+    if lisdir(path):
+        return path
+    raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path))
+
+
+def check_db_module(module):
+    """Check if the *module* is a supported pgsql module."""
+    if module.lower() in DB_MODULES:
+        return module
+    raise ConfigValueError(_(u"Unsupported database module: '%s'") %
+                           get_unicode(module))
+
+
+def check_db_ssl_mode(ssl_mode):
+    """Check if the *ssl_mode* is one of the SSL modes, known by pgsql."""
+    if ssl_mode in DB_SSL_MODES:
+        return ssl_mode
+    raise ConfigValueError(_(u"Unknown pgsql SSL mode: '%s'") %
+                           get_unicode(ssl_mode))
+
+
+def check_mailbox_format(format):
+    """
+    Check if the mailbox format *format* is supported.  When the *format*
+    is supported it will be returned, otherwise a `ConfigValueError` will
+    be raised.
+    """
+    format = format.lower()
+    if known_format(format):
+        return format
+    raise ConfigValueError(_(u"Unsupported mailbox format: '%s'") %
+                           get_unicode(format))
+
+
+def check_size_value(value):
+    """Check if the size value *value* has the proper format, e.g.: 1024k.
+    Returns the validated value string if it has the expected format.
+    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'") %
+                               get_unicode(value))
+    return value
+
+
+def check_version_format(version_string):
+    """Check if the *version_string* has the proper format, e.g.: '1.2.3'.
+    Returns the validated version string if it has the expected format.
+    Otherwise a `ConfigValueError` will be raised.
+    """
+    if not VERSION_RE.match(version_string):
+        raise ConfigValueError(_(u"Not a valid Dovecot version: '%s'") %
+                               get_unicode(version_string))
+    return version_string
+
+
+def verify_scheme(scheme):
+    """Checks if the password scheme *scheme* can be accepted and returns
+    the verified scheme.
+    """
+    try:
+        scheme, encoding = _verify_scheme(scheme)
+    except VMMError, err:  # 'cast' it
+        raise ConfigValueError(err.msg)
+    if not encoding:
+        return scheme
+    return '%s.%s' % (scheme, encoding)
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/constants.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,89 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.constants
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's constants:
+        * version information
+        * upper and lower limits MIN_* / MAX_*
+        * exit codes
+        * error codes
+"""
+# version information
+
+__all__ = ['__author__', '__date__', '__version__']
+AUTHOR = 'Pascal Volk <user+vmm@localhost.localdomain.org>'
+RELDATE = '2009-09-09'
+VERSION = '0.5.2'
+__author__ = AUTHOR
+__copyright__ = 'Copyright (c) 2007-2012 %s' % __author__
+__date__ = RELDATE
+__version__ = VERSION
+
+
+# limits
+
+MIN_GID = 70000
+MIN_UID = 70000
+
+
+# exit codes
+
+EX_SUCCESS = 0
+EX_MISSING_ARGS = 1
+EX_UNKNOWN_COMMAND = 2
+EX_USER_INTERRUPT = 3
+
+
+# error codes
+
+ACCOUNT_AND_ALIAS_PRESENT = 20
+ACCOUNT_EXISTS = 21
+ACCOUNT_MISSING_PASSWORD = 69
+ALIASDOMAIN_EXISTS = 23
+ALIASDOMAIN_ISDOMAIN = 24
+ALIASDOMAIN_NO_DOMDEST = 25
+ALIAS_EXCEEDS_EXPANSION_LIMIT = 27
+ALIAS_EXISTS = 28
+ALIAS_PRESENT = 30
+CONF_ERROR = 31
+CONF_NOFILE = 32
+CONF_NOPERM = 33
+CONF_WRONGPERM = 34
+DATABASE_ERROR = 35
+DOMAINDIR_GROUP_MISMATCH = 36
+DOMAIN_ALIAS_EXISTS = 37
+DOMAIN_EXISTS = 38
+DOMAIN_INVALID = 39
+DOMAIN_NO_NAME = 40
+DOMAIN_TOO_LONG = 41
+FOUND_DOTS_IN_PATH = 42
+INVALID_ADDRESS = 43
+INVALID_ARGUMENT = 44
+INVALID_MAIL_LOCATION = 70
+INVALID_SECTION = 46
+LOCALPART_INVALID = 47
+LOCALPART_TOO_LONG = 48
+MAILDIR_PERM_MISMATCH = 49
+MAILLOCATION_INIT = 50
+NOT_EXECUTABLE = 51
+NO_SUCH_ACCOUNT = 52
+NO_SUCH_ALIAS = 53
+NO_SUCH_ALIASDOMAIN = 54
+NO_SUCH_BINARY = 55
+NO_SUCH_DIRECTORY = 56
+NO_SUCH_DOMAIN = 57
+NO_SUCH_RELOCATED = 58
+RELOCATED_ADDR_DEST_IDENTICAL = 59
+RELOCATED_EXISTS = 60
+UNKNOWN_SERVICE = 65
+VMM_ERROR = 67
+VMM_TOO_MANY_FAILURES = 68
+
+# address types
+
+TYPE_ACCOUNT = 0x1
+TYPE_ALIAS = 0x2
+TYPE_RELOCATED = 0x4
--- a/VirtualMailManager/constants/ERROR.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-ACCOUNT_AND_ALIAS_PRESENT = 20
-ACCOUNT_EXISTS = 21
-ACCOUNT_PRESENT = 22
-ALIASDOMAIN_EXISTS = 23
-ALIASDOMAIN_ISDOMAIN = 24
-ALIASDOMAIN_NO_DOMDEST = 25
-ALIAS_ADDR_DEST_IDENTICAL = 26
-ALIAS_EXCEEDS_EXPANSION_LIMIT = 27
-ALIAS_EXISTS = 28
-ALIAS_MISSING_DEST = 29
-ALIAS_PRESENT = 30
-CONF_ERROR = 31
-CONF_NOFILE = 32
-CONF_NOPERM = 33
-CONF_WRONGPERM = 34
-DATABASE_ERROR = 35
-DOMAINDIR_GROUP_MISMATCH = 36
-DOMAIN_ALIAS_EXISTS = 37
-DOMAIN_EXISTS = 38
-DOMAIN_INVALID = 39
-DOMAIN_NO_NAME = 40
-DOMAIN_TOO_LONG = 41
-FOUND_DOTS_IN_PATH = 42
-INVALID_ADDRESS = 43
-INVALID_AGUMENT = 44
-INVALID_OPTION = 45
-INVALID_SECTION = 46
-LOCALPART_INVALID = 47
-LOCALPART_TOO_LONG = 48
-MAILDIR_PERM_MISMATCH = 49
-MAILLOCATION_INIT = 50
-NOT_EXECUTABLE = 51
-NO_SUCH_ACCOUNT = 52
-NO_SUCH_ALIAS = 53
-NO_SUCH_ALIASDOMAIN = 54
-NO_SUCH_BINARY = 55
-NO_SUCH_DIRECTORY = 56
-NO_SUCH_DOMAIN = 57
-NO_SUCH_RELOCATED = 58
-RELOCATED_ADDR_DEST_IDENTICAL = 59
-RELOCATED_EXISTS = 60
-RELOCATED_MISSING_DEST = 61
-TRANSPORT_INIT = 62
-UNKNOWN_MAILLOCATION_ID = 63
-UNKNOWN_SERVICE = 64
-UNKNOWN_TRANSPORT_ID = 65
-VMM_ERROR = 66
-VMM_TOO_MANY_FAILURES = 67
--- a/VirtualMailManager/constants/EXIT.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-MISSING_ARGS = 1
-UNKNOWN_COMMAND = 2
-USER_INTERRUPT = 3
--- a/VirtualMailManager/constants/VERSION.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-AUTHOR  = 'Pascal Volk <neverseen@users.sourceforge.net>'
-RELDATE = '2009-09-09'
-VERSION = '0.5.2'
-__author__  = AUTHOR
-__date__    = RELDATE
-__version__ = VERSION
-__all__ = ['__author__', '__date__', '__version__']
--- a/VirtualMailManager/constants/__init__.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2007 - 2010, Pascal Volk
-# See COPYING for distribution information.
-# package placeholder
-#
-# EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/domain.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,573 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.domain
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Domain class to manage e-mail domains.
+"""
+
+import os
+import re
+from random import choice
+
+from VirtualMailManager.constants import \
+     ACCOUNT_AND_ALIAS_PRESENT, DOMAIN_ALIAS_EXISTS, DOMAIN_EXISTS, \
+     DOMAIN_INVALID, DOMAIN_TOO_LONG, NO_SUCH_DOMAIN, VMM_ERROR
+from VirtualMailManager.errors import VMMError, DomainError as DomErr
+from VirtualMailManager.pycompat import all, any
+from VirtualMailManager.quotalimit import QuotaLimit
+from VirtualMailManager.serviceset import ServiceSet
+from VirtualMailManager.transport import Transport
+
+
+MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
+RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$")
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+class Domain(object):
+    """Class to manage e-mail domains."""
+    __slots__ = ('_directory', '_gid', '_name', '_qlimit', '_services',
+                 '_transport', '_note', '_dbh', '_new')
+
+    def __init__(self, dbh, domainname):
+        """Creates a new Domain instance.
+
+        Loads all relevant data from the database, if the domain could be
+        found.  To create a new domain call the methods set_directory() and
+        set_transport() before save().
+
+        A DomainError will be thrown when the *domainname* is the name of
+        an alias domain.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          a database connection for the database access
+        `domainname` : basestring
+          The name of the domain
+        """
+        self._name = check_domainname(domainname)
+        self._dbh = dbh
+        self._gid = 0
+        self._qlimit = None
+        self._services = None
+        self._transport = None
+        self._directory = None
+        self._note = None
+        self._new = True
+        self._load()
+
+    def _load(self):
+        """Load information from the database and checks if the domain name
+        is the primary one.
+
+        Raises a DomainError if Domain._name isn't the primary name of the
+        domain.
+        """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT dd.gid, qid, ssid, tid, domaindir, is_primary, '
+                    'note '
+                    'FROM domain_data dd, domain_name dn WHERE domainname = '
+                    '%s AND dn.gid = dd.gid', (self._name,))
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            if not result[5]:
+                raise DomErr(_(u"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])
+            self._services = ServiceSet(self._dbh, ssid=result[2])
+            self._transport = Transport(self._dbh, tid=result[3])
+            self._note = result[6]
+            self._new = False
+
+    def _set_gid(self):
+        """Sets the ID of the domain - if not set yet."""
+        assert self._gid == 0
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('domain_gid')")
+        self._gid = dbc.fetchone()[0]
+        dbc.close()
+
+    def _check_for_addresses(self):
+        """Checks dependencies for deletion. Raises a DomainError if there
+        are accounts, aliases and/or relocated users.
+        """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT '
+                    '(SELECT count(gid) FROM users WHERE gid = %(gid)u)'
+                    '  as account_count, '
+                    '(SELECT count(gid) FROM alias WHERE gid = %(gid)u)'
+                    '  as alias_count, '
+                    '(SELECT count(gid) FROM relocated WHERE gid = %(gid)u)'
+                    '  as relocated_count'
+                    % {'gid': self._gid})
+        result = dbc.fetchall()
+        dbc.close()
+        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)),
+                         ACCOUNT_AND_ALIAS_PRESENT)
+
+    def _chk_state(self, must_exist=True):
+        """Checks the state of the Domain instance and will raise a
+        VirtualMailManager.errors.DomainError:
+          - if *must_exist* is `True` and the domain doesn't exist
+          - 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,
+                         NO_SUCH_DOMAIN)
+        elif not must_exist and not self._new:
+            raise DomErr(_(u"The domain '%s' already exists.") % self._name,
+                         DOMAIN_EXISTS)
+
+    def _update_tables(self, column, value):
+        """Update table columns in the domain_data table."""
+        dbc = self._dbh.cursor()
+        dbc.execute('UPDATE domain_data SET %s = %%s WHERE gid = %%s' % column,
+                    (value, self._gid))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+
+    def _update_tables_ref(self, column, value, force=False):
+        """Update various columns in the domain_data table. When *force* is
+        `True`, the corresponding column in the users table will be reset to
+        NULL.
+
+        Arguments:
+
+        `column` : basestring
+          Name of the table column. Currently: qid, ssid and tid
+        `value` : long
+          The referenced key
+        `force` : bool
+          reset existing users. Default: `False`
+        """
+        if column not in ('qid', 'ssid', 'tid'):
+            raise ValueError('Unknown column: %r' % column)
+        self._update_tables(column, value)
+        if force:
+            dbc = self._dbh.cursor()
+            dbc.execute('UPDATE users SET %s = NULL WHERE gid = %%s' % column,
+                        (self._gid,))
+            if dbc.rowcount > 0:
+                self._dbh.commit()
+            dbc.close()
+
+    @property
+    def gid(self):
+        """The GID of the Domain."""
+        return self._gid
+
+    @property
+    def name(self):
+        """The Domain's name."""
+        return self._name
+
+    @property
+    def directory(self):
+        """The Domain's directory."""
+        return self._directory
+
+    @property
+    def quotalimit(self):
+        """The Domain's quota limit."""
+        return self._qlimit
+
+    @property
+    def serviceset(self):
+        """The Domain's serviceset."""
+        return self._services
+
+    @property
+    def transport(self):
+        """The Domain's transport."""
+        return self._transport
+
+    @property
+    def note(self):
+        """The Domain's note."""
+        return self._note
+
+    def set_directory(self, basedir):
+        """Set the path value of the Domain's directory, inside *basedir*.
+
+        Argument:
+
+        `basedir` : basestring
+          The base directory of all domains
+        """
+        self._chk_state(False)
+        assert self._directory is None
+        self._set_gid()
+        self._directory = os.path.join(basedir, choice(MAILDIR_CHARS),
+                                       str(self._gid))
+
+    def set_quotalimit(self, quotalimit):
+        """Set the quota limit for the new Domain.
+
+        Argument:
+
+        `quotalimit` : VirtualMailManager.quotalimit.QuotaLimit
+          The quota limit of the new Domain.
+        """
+        self._chk_state(False)
+        assert isinstance(quotalimit, QuotaLimit)
+        self._qlimit = quotalimit
+
+    def set_serviceset(self, serviceset):
+        """Set the services for the new Domain.
+
+        Argument:
+
+       `serviceset` : VirtualMailManager.serviceset.ServiceSet
+         The service set for the new Domain.
+        """
+        self._chk_state(False)
+        assert isinstance(serviceset, ServiceSet)
+        self._services = serviceset
+
+    def set_transport(self, transport):
+        """Set the transport for the new Domain.
+
+        Argument:
+
+        `transport` : VirtualMailManager.Transport
+          The transport of the new Domain
+        """
+        self._chk_state(False)
+        assert isinstance(transport, Transport)
+        self._transport = transport
+
+    def set_note(self, note):
+        """Set the domain's (optional) note.
+
+        Argument:
+
+        `note` : basestring or None
+          The note, or None to remove
+        """
+        self._chk_state(False)
+        assert note is None or isinstance(note, basestring)
+        self._note = note
+
+    def save(self):
+        """Stores the new domain in the database."""
+        self._chk_state(False)
+        assert all((self._directory, self._qlimit, self._services,
+                    self._transport))
+        dbc = self._dbh.cursor()
+        dbc.execute('INSERT INTO domain_data (gid, qid, ssid, tid, domaindir, '
+                    'note) '
+                    'VALUES (%s, %s, %s, %s, %s, %s)', (self._gid,
+                    self._qlimit.qid, self._services.ssid, self._transport.tid,
+                    self._directory, self._note))
+        dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary) '
+                    'VALUES (%s, %s, TRUE)', (self._name, self._gid))
+        self._dbh.commit()
+        dbc.close()
+        self._new = False
+
+    def delete(self, force=False):
+        """Deletes the domain.
+
+        Arguments:
+
+        `force` : bool
+          force the deletion of all available accounts, aliases and
+          relocated users.  When *force* is `False` and there are accounts,
+          aliases and/or relocated users a DomainError will be raised.
+          Default `False`
+        """
+        if not isinstance(force, bool):
+            raise TypeError('force must be a bool')
+        self._chk_state()
+        if not force:
+            self._check_for_addresses()
+        dbc = self._dbh.cursor()
+        for tbl in ('alias', 'users', 'relocated', 'domain_name',
+                    'domain_data'):
+            dbc.execute("DELETE FROM %s WHERE gid = %u" % (tbl, self._gid))
+        self._dbh.commit()
+        dbc.close()
+        self._gid = 0
+        self._directory = self._qlimit = self._transport = None
+        self._services = None
+        self._new = True
+
+    def update_quotalimit(self, quotalimit, force=False):
+        """Update the quota limit of the Domain.
+
+        If *force* is `True`, accounts-specific overrides will be reset
+        for all existing accounts of the domain. Otherwise, the limit
+        will only affect accounts that use the default.
+
+        Arguments:
+
+        `quotalimit` : VirtualMailManager.quotalimit.QuotaLimit
+          the new quota limit of the domain.
+        `force` : bool
+          enforce new quota limit for all accounts, default `False`
+        """
+        if cfg_dget('misc.dovecot_version') < 0x10102f00:
+            raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
+                             u'Dovecot >= v1.1.2.'), VMM_ERROR)
+        self._chk_state()
+        assert isinstance(quotalimit, QuotaLimit)
+        if not force and quotalimit == self._qlimit:
+            return
+        self._update_tables_ref('qid', quotalimit.qid, force)
+        self._qlimit = quotalimit
+
+    def update_serviceset(self, serviceset, force=False):
+        """Assign a different set of services to the Domain,
+
+        If *force* is `True`, accounts-specific overrides will be reset
+        for all existing accounts of the domain. Otherwise, the service
+        set will only affect accounts that use the default.
+
+        Arguments:
+        `serviceset` : VirtualMailManager.serviceset.ServiceSet
+          the new set of services
+        `force`
+          enforce the serviceset for all accounts, default `False`
+        """
+        self._chk_state()
+        assert isinstance(serviceset, ServiceSet)
+        if not force and serviceset == self._services:
+            return
+        self._update_tables_ref('ssid', serviceset.ssid, force)
+        self._services = serviceset
+
+    def update_transport(self, transport, force=False):
+        """Sets a new transport for the Domain.
+
+        If *force* is `True`, accounts-specific overrides will be reset
+        for all existing accounts of the domain. Otherwise, the transport
+        setting will only affect accounts that use the default.
+
+        Arguments:
+
+        `transport` : VirtualMailManager.Transport
+          the new transport
+        `force` : bool
+          enforce new transport setting for all accounts, default `False`
+        """
+        self._chk_state()
+        assert isinstance(transport, Transport)
+        if not force and transport == self._transport:
+            return
+        self._update_tables_ref('tid', transport.tid, force)
+        self._transport = transport
+
+    def update_note(self, note):
+        """Sets a new note for the Domain.
+
+        Arguments:
+
+        `transport` : basestring or None
+          the new note
+        """
+        self._chk_state()
+        assert note is None or isinstance(note, basestring)
+        if note == self._note:
+            return
+        self._update_tables('note', note)
+        self._note = note
+
+    def get_info(self):
+        """Returns a dictionary with information about the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT aliasdomains "alias domains", accounts, aliases, '
+                    'relocated, catchall "catch-all dests" '
+                    'FROM vmm_domain_info WHERE gid = %s', (self._gid,))
+        info = dbc.fetchone()
+        dbc.close()
+        keys = ('alias domains', 'accounts', 'aliases', 'relocated',
+                'catch-all dests')
+        info = dict(zip(keys, info))
+        info['gid'] = self._gid
+        info['domain name'] = self._name
+        info['transport'] = self._transport.transport
+        info['domain directory'] = self._directory
+        info['bytes'] = self._qlimit.bytes
+        info['messages'] = self._qlimit.messages
+        services = self._services.services
+        services = [s.upper() for s in services if services[s]]
+        if services:
+            services.sort()
+        else:
+            services.append('None')
+        info['active services'] = ' '.join(services)
+        info['note'] = self._note
+        return info
+
+    def get_accounts(self):
+        """Returns a list with all accounts of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT local_part from users where gid = %s ORDER BY '
+                    'local_part', (self._gid,))
+        users = dbc.fetchall()
+        dbc.close()
+        accounts = []
+        if users:
+            addr = u'@'.join
+            _dom = self._name
+            accounts = [addr((account[0], _dom)) for account in users]
+        return accounts
+
+    def get_aliases(self):
+        """Returns a list with all aliases e-mail addresses of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT DISTINCT address FROM alias WHERE gid = %s ORDER '
+                    'BY address', (self._gid,))
+        addresses = dbc.fetchall()
+        dbc.close()
+        aliases = []
+        if addresses:
+            addr = u'@'.join
+            _dom = self._name
+            aliases = [addr((alias[0], _dom)) for alias in addresses]
+        return aliases
+
+    def get_relocated(self):
+        """Returns a list with all addresses of relocated users."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT address FROM relocated WHERE gid = %s ORDER BY '
+                    'address', (self._gid,))
+        addresses = dbc.fetchall()
+        dbc.close()
+        relocated = []
+        if addresses:
+            addr = u'@'.join
+            _dom = self._name
+            relocated = [addr((address[0], _dom)) for address in addresses]
+        return relocated
+
+    def get_catchall(self):
+        """Returns a list with all catchall e-mail addresses of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT DISTINCT destination FROM catchall WHERE gid = %s ORDER '
+                    'BY destination', (self._gid,))
+        addresses = dbc.fetchall()
+        dbc.close()
+        return addresses
+
+    def get_aliase_names(self):
+        """Returns a list with all alias domain names of the domain."""
+        self._chk_state()
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
+                    'NOT is_primary ORDER BY domainname', (self._gid,))
+        anames = dbc.fetchall()
+        dbc.close()
+        aliasdomains = []
+        if anames:
+            aliasdomains = [aname[0] for aname in anames]
+        return aliasdomains
+
+
+def check_domainname(domainname):
+    """Returns the validated domain name `domainname`.
+
+    Throws an `DomainError`, if the domain name is too long or doesn't
+    look like a valid domain name (label.label.label).
+
+    """
+    if not RE_DOMAIN.match(domainname):
+        domainname = domainname.encode('idna')
+    if len(domainname) > 255:
+        raise DomErr(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
+    if not RE_DOMAIN.match(domainname):
+        raise DomErr(_(u"The domain name '%s' is invalid") % domainname,
+                     DOMAIN_INVALID)
+    return domainname
+
+
+def get_gid(dbh, domainname):
+    """Returns the group id of the domain *domainname*.
+
+    If the domain couldn't be found in the database 0 will be returned.
+    """
+    domainname = check_domainname(domainname)
+    dbc = dbh.cursor()
+    dbc.execute('SELECT gid FROM domain_name WHERE domainname = %s',
+                (domainname,))
+    gid = dbc.fetchone()
+    dbc.close()
+    if gid:
+        return gid[0]
+    return 0
+
+
+def search(dbh, pattern=None, like=False):
+    """'Search' for domains by *pattern* in the database.
+
+    *pattern* may be a domain name or a partial domain name - starting
+    and/or ending with a '%' sign.  When the *pattern* starts or ends with
+    a '%' sign *like* has to be `True` to perform a wildcard search.
+    To retrieve all available domains use the arguments' default values.
+
+    This function returns a tuple with a list and a dict: (order, domains).
+    The order list contains the domains' gid, alphabetical sorted by the
+    primary domain name.  The domains dict's keys are the gids of the
+    domains. The value of item is a list.  The first list element contains
+    the primary domain name or `None`.  The elements [1:] contains the
+    names of alias domains.
+
+    Arguments:
+
+    `pattern` : basestring
+      a (partial) domain name (starting and/or ending with a "%" sign)
+    `like` : bool
+      should be `True` when *pattern* starts/ends with a "%" sign
+    """
+    if pattern and not like:
+        pattern = check_domainname(pattern)
+    sql = 'SELECT gid, domainname, is_primary FROM domain_name'
+    if pattern:
+        if like:
+            sql += " WHERE domainname LIKE '%s'" % pattern
+        else:
+            sql += " WHERE domainname = '%s'" % pattern
+    sql += ' ORDER BY is_primary DESC, domainname'
+    dbc = dbh.cursor()
+    dbc.execute(sql)
+    result = dbc.fetchall()
+    dbc.close()
+
+    gids = [domain[0] for domain in result if domain[2]]
+    domains = {}
+    for gid, domain, is_primary in result:
+        if is_primary:
+            if not gid in domains:
+                domains[gid] = [domain]
+            else:
+                domains[gid].insert(0, domain)
+        else:
+            if gid in gids:
+                if gid in domains:
+                    domains[gid].append(domain)
+                else:
+                    domains[gid] = [domain]
+            else:
+                gids.append(gid)
+                domains[gid] = [None, domain]
+    return gids, domains
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/emailaddress.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,155 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.emailaddress
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's EmailAddress class to handle e-mail addresses.
+"""
+import re
+
+from VirtualMailManager.domain import check_domainname, get_gid
+from VirtualMailManager.constants import \
+     DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG, \
+     DOMAIN_INVALID
+from VirtualMailManager.errors import DomainError, EmailAddressError as EAErr
+
+
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+_ = lambda msg: msg
+
+
+class EmailAddress(object):
+    """Simple class for validated e-mail addresses."""
+    __slots__ = ('_localpart', '_domainname')
+
+    def __init__(self, address, _validate=True):
+        """Creates a new instance from the string/unicode ``address``."""
+        assert isinstance(address, basestring)
+        self._localpart = None
+        self._domainname = None
+        if _validate:
+            self._chk_address(address)
+
+    @property
+    def localpart(self):
+        """The local-part of the address *local-part@domain*"""
+        return self._localpart
+
+    @property
+    def domainname(self):
+        """The domain part of the address *local-part@domain*"""
+        return self._domainname
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart == other._localpart and \
+                    self._domainname == other._domainname
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart != other._localpart or \
+                    self._domainname != other._domainname
+        return NotImplemented
+
+    def __hash__(self):
+        return hash((self._localpart.lower(), self._domainname.lower()))
+
+    def __repr__(self):
+        return "EmailAddress('%s@%s')" % (self._localpart, self._domainname)
+
+    def __str__(self):
+        return '%s@%s' % (self._localpart, self._domainname)
+
+    def _chk_address(self, address):
+        """Checks if the string ``address`` could be used for an e-mail
+        address.  If so, it will assign the corresponding values to the
+        attributes `_localpart` and `_domainname`."""
+        parts = address.split('@')
+        p_len = len(parts)
+        if p_len < 2:
+            raise EAErr(_(u"Missing the '@' sign in address: '%s'") % address,
+                        INVALID_ADDRESS)
+        elif p_len > 2:
+            raise EAErr(_(u"Too many '@' signs in address: '%s'") % address,
+                        INVALID_ADDRESS)
+        if not parts[0]:
+            raise EAErr(_(u"Missing local-part in address: '%s'") % address,
+                        LOCALPART_INVALID)
+        if not parts[1]:
+            raise EAErr(_(u"Missing domain name in address: '%s'") % address,
+                        DOMAIN_NO_NAME)
+        self._localpart = check_localpart(parts[0])
+        self._domainname = check_domainname(parts[1])
+
+
+class DestinationEmailAddress(EmailAddress):
+    """Provides additionally the domains group ID - when the domain is known
+    in the database."""
+    __slots__ = ('_gid', '_localhost')
+
+    def __init__(self, address, dbh, _validate=False):
+        """Creates a new DestinationEmailAddress instance
+
+        Arguments:
+
+        `address`: string/unicode
+          a e-mail address like user@example.com
+        `dbh`: pyPgSQL.PgSQL.Connection/pyPgSQL.PgSQL.connection
+          a database connection for the database access
+        """
+        super(DestinationEmailAddress, self).__init__(address, _validate)
+        self._localhost = False
+        if not _validate:
+            try:
+                self._chk_address(address)
+            except DomainError, err:
+                if err.code is DOMAIN_INVALID and \
+                   address.split('@')[1] == 'localhost':
+                    self._localhost = True
+                    self._domainname = 'localhost'
+                else:
+                    raise
+        self._gid = 0
+        if not self._localhost:
+            self._find_domain(dbh)
+        else:
+            self._localpart = self._localpart.lower()
+
+    def _find_domain(self, dbh):
+        """Checks if the domain is known"""
+        self._gid = get_gid(dbh, self._domainname)
+        if self._gid:
+            self._localpart = self._localpart.lower()
+
+    @property
+    def at_localhost(self):
+        """True when the address is something@localhost."""
+        return self._localhost
+
+    @property
+    def gid(self):
+        """The domains group ID. 0 if the domain is not known."""
+        return self._gid
+
+
+def check_localpart(localpart):
+    """Returns the validated local-part `localpart`.
+
+    Throws a `EmailAddressError` if the local-part is too long or contains
+    invalid characters.
+    """
+    if len(localpart) > 64:
+        raise EAErr(_(u"The local-part '%s' is too long.") % localpart,
+                    LOCALPART_TOO_LONG)
+    invalid_chars = set(RE_LOCALPART.findall(localpart))
+    if invalid_chars:
+        i_chars = u''.join((u'"%s" ' % c for c in invalid_chars))
+        raise EAErr(_(u"The local-part '%(l_part)s' contains invalid "
+                      u"characters: %(i_chars)s") % {'l_part': localpart,
+                    'i_chars': i_chars}, LOCALPART_INVALID)
+    return localpart
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/errors.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,71 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.errors
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VMM's Exception classes
+"""
+
+
+class VMMError(Exception):
+    """Exception base class for VirtualMailManager exceptions"""
+
+    def __init__(self, msg, code):
+        Exception.__init__(self, msg)
+        self.msg = msg
+        self.code = int(code)
+
+    def __repr__(self):
+        return '%s(%r, %r)' % (self.__class__.__name__, self.msg, self.code)
+
+
+class ConfigError(VMMError):
+    """Exception class for configuration exceptions"""
+    pass
+
+
+class PermissionError(VMMError):
+    """Exception class for permissions exceptions"""
+    pass
+
+
+class NotRootError(VMMError):
+    """Exception class for non-root exceptions"""
+    pass
+
+
+class DomainError(VMMError):
+    """Exception class for Domain exceptions"""
+    pass
+
+
+class AliasDomainError(VMMError):
+    """Exception class for AliasDomain exceptions"""
+    pass
+
+
+class AccountError(VMMError):
+    """Exception class for Account exceptions"""
+    pass
+
+
+class AliasError(VMMError):
+    """Exception class for Alias exceptions"""
+    pass
+
+
+class EmailAddressError(VMMError):
+    """Exception class for EmailAddress exceptions"""
+    pass
+
+
+class MailLocationError(VMMError):
+    """Exception class for MailLocation exceptions"""
+    pass
+
+
+class RelocatedError(VMMError):
+    """Exception class for Relocated exceptions"""
+    pass
--- a/VirtualMailManager/ext/Postconf.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-"""A small - r/o - wrapper class for Postfix' postconf."""
-
-from subprocess import Popen, PIPE
-
-from __main__ import re, ERR
-from VirtualMailManager.Exceptions import VMMException
-
-RE_PC_PARAMS = """^\w+$"""
-RE_PC_VARIABLES = r"""\$\b\w+\b"""
-
-class Postconf(object):
-    __slots__ = ('__bin', '__val', '__varFinder')
-    def __init__(self, postconf_bin):
-        """Creates a new Postconf instance.
-
-        Keyword arguments:
-        postconf_bin -- absolute path to the Postfix postconf binary (str)
-        """
-        self.__bin = postconf_bin
-        self.__val = ''
-        self.__varFinder = re.compile(RE_PC_VARIABLES)
-
-    def read(self, parameter, expand_vars=True):
-        """Returns the parameters value.
-
-        If expand_vars is True (default), all variables in the value will be
-        expanded:
-        e.g. mydestination -> mail.example.com, localhost.example.com, localhost
-        Otherwise the value may contain one or more variables.
-        e.g. mydestination -> $myhostname, localhost.$mydomain, localhost
-
-        Keyword arguments:
-        parameter -- the name of a Postfix configuration parameter (str)
-        expand_vars -- default True (bool)
-        """
-        if not re.match(RE_PC_PARAMS, parameter):
-            raise VMMException(_(u'The value “%s” doesn\'t look like a valid\
- postfix configuration parameter name.') % parameter, ERR.VMM_ERROR)
-        self.__val = self.__read(parameter)
-        if expand_vars:
-            self.__expandVars()
-        return self.__val
-
-    def __expandVars(self):
-        while True:
-            pvars = set(self.__varFinder.findall(self.__val))
-            pvars_len = len(pvars)
-            if pvars_len < 1:
-                break
-            if pvars_len > 1:
-                self.__expandMultiVars(self.__readMulti(pvars))
-                continue
-            pvars = pvars.pop()
-            self.__val = self.__val.replace(pvars, self.__read(pvars[1:]))
-
-    def __expandMultiVars(self, old_new):
-        for old, new in old_new.items():
-            self.__val = self.__val.replace('$'+old, new)
-
-    def __read(self, parameter):
-        out, err = Popen([self.__bin, '-h', parameter], stdout=PIPE,
-                stderr=PIPE).communicate()
-        if len(err):
-            raise VMMException(err.strip(), ERR.VMM_ERROR)
-        return out.strip()
-
-    def __readMulti(self, parameters):
-        cmd = [self.__bin]
-        for parameter in parameters:
-            cmd.append(parameter[1:])
-        out, err = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
-        if len(err):
-            raise VMMException(err.strip(), ERR.VMM_ERROR)
-        par_val = {}
-        for line in out.splitlines():
-            par, val = line.split(' = ')
-            par_val[par] = val
-        return par_val
-
--- a/VirtualMailManager/ext/__init__.py	Mon Nov 07 03:22:15 2011 +0000
+++ b/VirtualMailManager/ext/__init__.py	Thu Jun 28 19:26:50 2012 +0000
@@ -1,5 +1,5 @@
 # -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
+# Copyright (c) 2008 - 2012, Pascal Volk
 # See COPYING for distribution information.
 # package placeholder
 #
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/ext/postconf.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,127 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.ext.postconf
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Wrapper class for Postfix's postconf.
+    Postconf instances can be used to read actual values of configuration
+    parameters or edit the value of a configuration parameter.
+
+    postconf.read(parameter) -> value
+    postconf.edit(parameter, value)
+"""
+
+import re
+from subprocess import Popen, PIPE
+
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.constants import VMM_ERROR
+
+_ = lambda msg: msg
+
+
+class Postconf(object):
+    """Wrapper class for Postfix's postconf."""
+    __slots__ = ('_bin', '_val')
+    _parameter_re = re.compile(r'^\w+$')
+    _variables_re = re.compile(r'\$\b\w+\b')
+
+    def __init__(self, postconf_bin):
+        """Creates a new Postconf instance.
+
+        Argument:
+
+        `postconf_bin` : str
+          absolute path to the Postfix postconf binary.
+        """
+        self._bin = postconf_bin
+        self._val = ''
+
+    def edit(self, parameter, value):
+        """Set the `parameter`'s value to `value`.
+
+        Arguments:
+
+        `parameter` : str
+          the name of a Postfix configuration parameter
+        `value` : str
+          the parameter's new value.
+        """
+        self._check_parameter(parameter)
+        stderr = Popen((self._bin, '-e', parameter + '=' + str(value)),
+                       stderr=PIPE).communicate()[1]
+        if stderr:
+            raise VMMError(stderr.strip(), VMM_ERROR)
+
+    def read(self, parameter, expand_vars=True):
+        """Returns the parameters value.
+
+        If expand_vars is True (default), all variables in the value will be
+        expanded:
+        e.g. mydestination: mail.example.com, localhost.example.com, localhost
+        Otherwise the value may contain one or more variables.
+        e.g. mydestination: $myhostname, localhost.$mydomain, localhost
+
+        Arguments:
+
+        `parameter` : str
+          the name of a Postfix configuration parameter.
+        `expand_vars` : bool
+          indicates if variables should be expanded or not, default True
+        """
+        self._check_parameter(parameter)
+        self._val = self._read(parameter)
+        if expand_vars:
+            self._expand_vars()
+        return self._val
+
+    def _check_parameter(self, parameter):
+        """Check that the `parameter` looks like a configuration parameter.
+        If not, a VMMError will be raised."""
+        if not self.__class__._parameter_re.match(parameter):
+            raise VMMError(_(u"The value '%s' does not look like a valid "
+                             u"postfix configuration parameter name.") %
+                           parameter, VMM_ERROR)
+
+    def _expand_vars(self):
+        """Expand the $variables in self._val to their values."""
+        while True:
+            pvars = set(self.__class__._variables_re.findall(self._val))
+            if not pvars:
+                break
+            if len(pvars) > 1:
+                self._expand_multi_vars(self._read_multi(pvars))
+                continue
+            pvars = pvars.pop()
+            self._val = self._val.replace(pvars, self._read(pvars[1:]))
+
+    def _expand_multi_vars(self, old_new):
+        """Replace all $vars in self._val with their values."""
+        for old, new in old_new.iteritems():
+            self._val = self._val.replace('$' + old, new)
+
+    def _read(self, parameter):
+        """Ask postconf for the value of a single configuration parameter."""
+        stdout, stderr = Popen([self._bin, '-h', parameter], stdout=PIPE,
+                               stderr=PIPE).communicate()
+        if stderr:
+            raise VMMError(stderr.strip(), VMM_ERROR)
+        return stdout.strip()
+
+    def _read_multi(self, parameters):
+        """Ask postconf for multiple configuration parameters. Returns a dict
+        parameter: value items."""
+        cmd = [self._bin]
+        cmd.extend(parameter[1:] for parameter in parameters)
+        stdout, stderr = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
+        if stderr:
+            raise VMMError(stderr.strip(), VMM_ERROR)
+        par_val = {}
+        for line in stdout.splitlines():
+            par, val = line.split(' = ')
+            par_val[par] = val
+        return par_val
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/handler.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,889 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+   VirtualMailManager.handler
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+   A wrapper class. It wraps round all other classes and does some
+   dependencies checks.
+
+   Additionally it communicates with the PostgreSQL database, creates
+   or deletes directories of domains or users.
+"""
+
+import os
+import re
+
+from shutil import rmtree
+from subprocess import Popen, PIPE
+
+from VirtualMailManager.account import Account
+from VirtualMailManager.alias import Alias
+from VirtualMailManager.aliasdomain import AliasDomain
+from VirtualMailManager.catchall import CatchallAlias
+from VirtualMailManager.common import exec_ok, lisdir
+from VirtualMailManager.config import Config as Cfg
+from VirtualMailManager.constants import MIN_GID, MIN_UID, \
+     ACCOUNT_EXISTS, ALIAS_EXISTS, CONF_NOFILE, CONF_NOPERM, CONF_WRONGPERM, \
+     DATABASE_ERROR, DOMAINDIR_GROUP_MISMATCH, DOMAIN_INVALID, \
+     FOUND_DOTS_IN_PATH, INVALID_ARGUMENT, MAILDIR_PERM_MISMATCH, \
+     NOT_EXECUTABLE, NO_SUCH_ACCOUNT, NO_SUCH_ALIAS, NO_SUCH_BINARY, \
+     NO_SUCH_DIRECTORY, NO_SUCH_RELOCATED, RELOCATED_EXISTS, UNKNOWN_SERVICE, \
+     VMM_ERROR, LOCALPART_INVALID, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
+from VirtualMailManager.domain import Domain
+from VirtualMailManager.emailaddress import DestinationEmailAddress, \
+     EmailAddress, RE_LOCALPART
+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
+from VirtualMailManager.transport import Transport
+
+
+_ = lambda msg: msg
+_db_mod = None
+
+CFG_FILE = 'vmm.cfg'
+CFG_PATH = '/root:/usr/local/etc:/etc'
+RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
+OTHER_TYPES = {
+    TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
+    TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
+    TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
+}
+
+class Handler(object):
+    """Wrapper class to simplify the access on all the stuff from
+    VirtualMailManager"""
+    __slots__ = ('_cfg', '_cfg_fname', '_db_connect', '_dbh', '_warnings')
+
+    def __init__(self, skip_some_checks=False):
+        """Creates a new Handler instance.
+
+        ``skip_some_checks`` : bool
+            When a derived class knows how to handle all checks this
+            argument may be ``True``. By default it is ``False`` and
+            all checks will be performed.
+
+        Throws a NotRootError if your uid is greater 0.
+        """
+        self._cfg_fname = ''
+        self._warnings = []
+        self._cfg = None
+        self._dbh = None
+        self._db_connect = None
+
+        if os.geteuid():
+            raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
+                               CONF_NOPERM)
+        if self._check_cfg_file():
+            self._cfg = Cfg(self._cfg_fname)
+            self._cfg.load()
+        if not skip_some_checks:
+            self._cfg.check()
+            self._chkenv()
+            self._set_db_connect()
+
+    def _find_cfg_file(self):
+        """Search the CFG_FILE in CFG_PATH.
+        Raise a VMMError when no vmm.cfg could be found.
+        """
+        for path in CFG_PATH.split(':'):
+            tmp = os.path.join(path, CFG_FILE)
+            if os.path.isfile(tmp):
+                self._cfg_fname = tmp
+                break
+        if not self._cfg_fname:
+            raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
+                             u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
+                           'cfg_path': CFG_PATH}, CONF_NOFILE)
+
+    def _check_cfg_file(self):
+        """Checks the configuration file, returns bool"""
+        self._find_cfg_file()
+        fstat = os.stat(self._cfg_fname)
+        fmode = int(oct(fstat.st_mode & 0777))
+        if fmode % 100 and fstat.st_uid != fstat.st_gid or \
+           fmode % 10 and fstat.st_uid == fstat.st_gid:
+            # 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)
+        else:
+            return True
+
+    def _chkenv(self):
+        """Make sure our base_directory is a directory and that all
+        required executables exists and are executable.
+        If not, a VMMError will be raised"""
+        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)
+            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')") %
+                           {'path': basedir, 'cfg_file': self._cfg_fname},
+                           NO_SUCH_DIRECTORY)
+        for opt, val in self._cfg.items('bin'):
+            try:
+                exec_ok(val)
+            except VMMError, err:
+                if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
+                    raise VMMError(err.msg + _(u"\n(%(cfg_file)s: section "
+                                   u"'bin', option '%(option)s')") %
+                                   {'cfg_file': self._cfg_fname,
+                                    'option': opt}, err.code)
+                else:
+                    raise
+
+    def _set_db_connect(self):
+        """check which module to use and set self._db_connect"""
+        global _db_mod
+        if self._cfg.dget('database.module').lower() == 'psycopg2':
+            try:
+                _db_mod = __import__('psycopg2')
+            except ImportError:
+                raise VMMError(_(u"Unable to import database module '%s'.") %
+                               'psycopg2', VMM_ERROR)
+            self._db_connect = self._psycopg2_connect
+        else:
+            try:
+                tmp = __import__('pyPgSQL', globals(), locals(), ['PgSQL'])
+            except ImportError:
+                raise VMMError(_(u"Unable to import database module '%s'.") %
+                               'pyPgSQL', VMM_ERROR)
+            _db_mod = tmp.PgSQL
+            self._db_connect = self._pypgsql_connect
+
+    def _pypgsql_connect(self):
+        """Creates a pyPgSQL.PgSQL.connection instance."""
+        if self._dbh is None or (isinstance(self._dbh, _db_mod.Connection) and
+                                  not self._dbh._isOpen):
+            try:
+                self._dbh = _db_mod.connect(
+                        database=self._cfg.dget('database.name'),
+                        user=self._cfg.pget('database.user'),
+                        host=self._cfg.dget('database.host'),
+                        port=self._cfg.dget('database.port'),
+                        password=self._cfg.pget('database.pass'),
+                        client_encoding='utf8', unicode_results=True)
+                dbc = self._dbh.cursor()
+                dbc.execute("SET NAMES 'UTF8'")
+                dbc.close()
+            except _db_mod.libpq.DatabaseError, err:
+                raise VMMError(str(err), DATABASE_ERROR)
+
+    def _psycopg2_connect(self):
+        """Return a new psycopg2 connection object."""
+        if self._dbh is None or \
+          (isinstance(self._dbh, _db_mod.extensions.connection) and
+           self._dbh.closed):
+            try:
+                self._dbh = _db_mod.connect(
+                        host=self._cfg.dget('database.host'),
+                        sslmode=self._cfg.dget('database.sslmode'),
+                        port=self._cfg.dget('database.port'),
+                        database=self._cfg.dget('database.name'),
+                        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:
+                raise VMMError(str(err), DATABASE_ERROR)
+
+    def _chk_other_address_types(self, address, exclude):
+        """Checks if the EmailAddress *address* is known as `TYPE_ACCOUNT`,
+        `TYPE_ALIAS` or `TYPE_RELOCATED`, but not as the `TYPE_*` specified
+        by *exclude*.  If the *address* is known as one of the `TYPE_*`s
+        the according `TYPE_*` constant will be returned.  Otherwise 0 will
+        be returned."""
+        assert exclude in (TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED) and \
+                isinstance(address, EmailAddress)
+        if exclude is not TYPE_ACCOUNT:
+            account = Account(self._dbh, address)
+            if account:
+                return TYPE_ACCOUNT
+        if exclude is not TYPE_ALIAS:
+            alias = Alias(self._dbh, address)
+            if alias:
+                return TYPE_ALIAS
+        if exclude is not TYPE_RELOCATED:
+            relocated = Relocated(self._dbh, address)
+            if relocated:
+                return TYPE_RELOCATED
+        return 0
+
+    def _is_other_address(self, address, exclude):
+        """Checks if *address* is known for an Account (TYPE_ACCOUNT),
+        Alias (TYPE_ALIAS) or Relocated (TYPE_RELOCATED), except for
+        *exclude*.  Returns `False` if the address is not known for other
+        types.
+
+        Raises a `VMMError` if the address is known.
+        """
+        other = self._chk_other_address_types(address, exclude)
+        if not other:
+            return False
+        # 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'.")
+        raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
+                              'address': address}, OTHER_TYPES[other][1])
+
+    def _get_account(self, address):
+        """Return an Account instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Account(self._dbh, address)
+
+    def _get_alias(self, address):
+        """Return an Alias instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Alias(self._dbh, address)
+
+    def _get_catchall(self, domain):
+        """Return a CatchallAlias instances for the given domain (str)."""
+        self._db_connect()
+        return CatchallAlias(self._dbh, domain)
+
+    def _get_relocated(self, address):
+        """Return a Relocated instances for the given address (str)."""
+        address = EmailAddress(address)
+        self._db_connect()
+        return Relocated(self._dbh, address)
+
+    def _get_domain(self, domainname):
+        """Return a Domain instances for the given domain name (str)."""
+        self._db_connect()
+        return Domain(self._dbh, domainname)
+
+    def _get_disk_usage(self, directory):
+        """Estimate file space usage for the given directory.
+
+        Arguments:
+
+        `directory` : basestring
+          The directory to summarize recursively disk usage for
+        """
+        if lisdir(directory):
+            return Popen([self._cfg.dget('bin.du'), "-hs", directory],
+                         stdout=PIPE).communicate()[0].split('\t')[0]
+        else:
+            self._warnings.append(_('No such directory: %s') % directory)
+            return 0
+
+    def _make_domain_dir(self, domain):
+        """Create a directory for the `domain` and its accounts."""
+        cwd = os.getcwd()
+        hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
+        dir_created = False
+        os.chdir(self._cfg.dget('misc.base_directory'))
+        old_umask = os.umask(0022)
+        if not os.path.exists(hashdir):
+            os.mkdir(hashdir, 0711)
+            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,
+                           NO_SUCH_DIRECTORY)
+        if os.path.exists(domain.directory):
+            raise VMMError(_(u"The file/directory '%s' already exists.") %
+                           domain.directory, VMM_ERROR)
+        os.mkdir(os.path.join(hashdir, domdir),
+                 self._cfg.dget('domain.directory_mode'))
+        os.chown(domain.directory, 0, domain.gid)
+        os.umask(old_umask)
+        os.chdir(cwd)
+
+    def _make_home(self, account):
+        """Create a home directory for the new Account *account*."""
+        domdir = account.domain.directory
+        if not lisdir(domdir):
+            self._make_domain_dir(account.domain)
+        os.umask(0007)
+        uid = account.uid
+        os.chdir(domdir)
+        os.mkdir('%s' % uid, self._cfg.dget('account.directory_mode'))
+        os.chown('%s' % uid, uid, account.gid)
+
+    def _make_account_dirs(self, account):
+        """Create all necessary directories for the account."""
+        oldpwd = os.getcwd()
+        self._make_home(account)
+        mailbox = new_mailbox(account)
+        mailbox.create()
+        folders = self._cfg.dget('mailbox.folders').split(':')
+        if any(folders):
+            bad = mailbox.add_boxes(folders,
+                                    self._cfg.dget('mailbox.subscribe'))
+            if bad:
+                self._warnings.append(_(u"Skipped mailbox folders:") +
+                                      '\n\t- ' + '\n\t- '.join(bad))
+        os.chdir(oldpwd)
+
+    def _delete_home(self, domdir, uid, gid):
+        """Delete a user's home directory.
+
+        Arguments:
+
+        `domdir` : basestring
+          The directory of the domain the user belongs to
+          (commonly AccountObj.domain.directory)
+        `uid` : int/long
+          The user's UID (commonly AccountObj.uid)
+        `gid` : int/long
+          The user's GID (commonly AccountObj.gid)
+        """
+        assert all(isinstance(xid, (long, int)) for xid in (uid, gid)) and \
+                isinstance(domdir, basestring)
+        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,
+                           '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') %
+                           domdir, FOUND_DOTS_IN_PATH)
+        if not lisdir(domdir):
+            raise VMMError(_(u"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") %
+                                  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)
+        rmtree(userdir, ignore_errors=True)
+
+    def _delete_domain_dir(self, domdir, gid):
+        """Delete a domain's directory.
+
+        Arguments:
+
+        `domdir` : basestring
+          The domain's directory (commonly DomainObj.directory)
+        `gid` : int/long
+          The domain's GID (commonly DomainObj.gid)
+        """
+        assert isinstance(domdir, basestring) and isinstance(gid, (long, int))
+        if gid < MIN_GID:
+            raise VMMError(_(u"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') %
+                           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)
+        rmtree(domdir, ignore_errors=True)
+
+    def has_warnings(self):
+        """Checks if warnings are present, returns bool."""
+        return bool(len(self._warnings))
+
+    def get_warnings(self):
+        """Returns a list with all available warnings and resets all
+        warnings.
+        """
+        ret_val = self._warnings[:]
+        del self._warnings[:]
+        return ret_val
+
+    def cfg_dget(self, option):
+        """Get the configured value of the *option* (section.option).
+        When the option was not configured its default value will be
+        returned."""
+        return self._cfg.dget(option)
+
+    def cfg_pget(self, option):
+        """Get the configured value of the *option* (section.option)."""
+        return self._cfg.pget(option)
+
+    def cfg_install(self):
+        """Installs the cfg_dget method as ``cfg_dget`` into the built-in
+        namespace."""
+        import __builtin__
+        assert 'cfg_dget' not in __builtin__.__dict__
+        __builtin__.__dict__['cfg_dget'] = self._cfg.dget
+
+    def domain_add(self, domainname, transport=None):
+        """Wrapper around Domain's set_quotalimit, set_transport and save."""
+        dom = self._get_domain(domainname)
+        if transport is None:
+            dom.set_transport(Transport(self._dbh,
+                              transport=self._cfg.dget('domain.transport')))
+        else:
+            dom.set_transport(Transport(self._dbh, transport=transport))
+        dom.set_quotalimit(QuotaLimit(self._dbh,
+                           bytes=long(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'),
+                                      pop3=self._cfg.dget('domain.pop3'),
+                                      sieve=self._cfg.dget('domain.sieve'),
+                                      smtp=self._cfg.dget('domain.smtp')))
+        dom.set_directory(self._cfg.dget('misc.base_directory'))
+        dom.save()
+        self._make_domain_dir(dom)
+
+    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)):
+            raise TypeError("'bytes_' and 'messages' have to be "
+                            "integers or longs.")
+        if force is not None and force != 'force':
+            raise DomainError(_(u"Invalid argument: '%s'") % force,
+                              INVALID_ARGUMENT)
+        dom = self._get_domain(domainname)
+        quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
+        if force is None:
+            dom.update_quotalimit(quotalimit)
+        else:
+            dom.update_quotalimit(quotalimit, force=True)
+
+    def domain_services(self, domainname, force=None, *services):
+        """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,
+                              INVALID_ARGUMENT)
+        for service in set(services):
+            if service not in SERVICES:
+                raise DomainError(_(u"Unknown service: '%s'") % service,
+                                  UNKNOWN_SERVICE)
+            kwargs[service] = True
+
+        dom = self._get_domain(domainname)
+        serviceset = ServiceSet(self._dbh, **kwargs)
+        dom.update_serviceset(serviceset, (True, False)[not force])
+
+    def domain_transport(self, domainname, transport, force=None):
+        """Wrapper around Domain.update_transport()"""
+        if force is not None and force != 'force':
+            raise DomainError(_(u"Invalid argument: '%s'") % force,
+                              INVALID_ARGUMENT)
+        dom = self._get_domain(domainname)
+        trsp = Transport(self._dbh, transport=transport)
+        if force is None:
+            dom.update_transport(trsp)
+        else:
+            dom.update_transport(trsp, force=True)
+
+    def domain_note(self, domainname, note):
+        """Wrapper around Domain.update_note()"""
+        dom = self._get_domain(domainname)
+        dom.update_note(note)
+
+    def domain_delete(self, domainname, force=False):
+        """Wrapper around Domain.delete()"""
+        if not isinstance(force, bool):
+            raise TypeError('force must be a bool')
+        dom = self._get_domain(domainname)
+        gid = dom.gid
+        domdir = dom.directory
+        if self._cfg.dget('domain.force_deletion') or force:
+            dom.delete(True)
+        else:
+            dom.delete(False)
+        if self._cfg.dget('domain.delete_directory'):
+            self._delete_domain_dir(domdir, gid)
+
+    def domain_info(self, domainname, details=None):
+        """Wrapper around Domain.get_info(), Domain.get_accounts(),
+        Domain.get_aliase_names(), Domain.get_aliases() and
+        Domain.get_relocated."""
+        if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
+                           'relocated', 'catchall']:
+            raise VMMError(_(u"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')
+        if details is None:
+            return dominfo
+        elif details == 'accounts':
+            return (dominfo, dom.get_accounts())
+        elif details == 'aliasdomains':
+            return (dominfo, dom.get_aliase_names())
+        elif details == 'aliases':
+            return (dominfo, dom.get_aliases())
+        elif details == 'relocated':
+            return(dominfo, dom.get_relocated())
+        elif details == 'catchall':
+            return(dominfo, dom.get_catchall())
+        else:
+            return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
+                    dom.get_aliases(), dom.get_relocated(), dom.get_catchall())
+
+    def aliasdomain_add(self, aliasname, domainname):
+        """Adds an alias domain to the domain.
+
+        Arguments:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        `domainname` : basestring
+          The name of the target domain
+        """
+        dom = self._get_domain(domainname)
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.set_destination(dom)
+        alias_dom.save()
+
+    def aliasdomain_info(self, aliasname):
+        """Returns a dict (keys: "alias" and "domain") with the names of
+        the alias domain and its primary domain."""
+        self._db_connect()
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        return alias_dom.info()
+
+    def aliasdomain_switch(self, aliasname, domainname):
+        """Modifies the target domain of an existing alias domain.
+
+        Arguments:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        `domainname` : basestring
+          The name of the new target domain
+        """
+        dom = self._get_domain(domainname)
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.set_destination(dom)
+        alias_dom.switch()
+
+    def aliasdomain_delete(self, aliasname):
+        """Deletes the given alias domain.
+
+        Argument:
+
+        `aliasname` : basestring
+          The name of the alias domain
+        """
+        self._db_connect()
+        alias_dom = AliasDomain(self._dbh, aliasname)
+        alias_dom.delete()
+
+    def domain_list(self, pattern=None):
+        """Wrapper around function search() from module Domain."""
+        from VirtualMailManager.domain import search
+        like = False
+        if pattern and (pattern.startswith('%') or pattern.endswith('%')):
+            like = True
+            if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
+                raise VMMError(_(u"The pattern '%s' contains invalid "
+                                 u"characters.") % pattern, DOMAIN_INVALID)
+        self._db_connect()
+        return search(self._dbh, pattern=pattern, like=like)
+
+    def address_list(self, typelimit, pattern=None):
+        """TODO"""
+        llike = dlike = False
+        lpattern = dpattern = None
+        if pattern:
+            parts = pattern.split('@', 2)
+            if len(parts) == 2:
+                # The pattern includes '@', so let's treat the
+                # parts separately to allow for pattern search like %@domain.%
+                lpattern = parts[0]
+                llike = lpattern.startswith('%') or lpattern.endswith('%')
+                dpattern = parts[1]
+                dlike = dpattern.startswith('%') or dpattern.endswith('%')
+
+                if llike:
+                    checkp = lpattern.strip('%')
+                else:
+                    checkp = lpattern
+                if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
+                    raise VMMError(_(u"The pattern '%s' contains invalid "
+                                     u"characters.") % pattern, LOCALPART_INVALID)
+            else:
+                # else just match on domains
+                # (or should that be local part, I don't know…)
+                dpattern = parts[0]
+                dlike = dpattern.startswith('%') or dpattern.endswith('%')
+
+            if dlike:
+                checkp = dpattern.strip('%')
+            else:
+                checkp = 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)
+        self._db_connect()
+        from VirtualMailManager.common import search_addresses
+        return search_addresses(self._dbh, typelimit=typelimit,
+                                lpattern=lpattern, llike=llike,
+                                dpattern=dpattern, dlike=dlike)
+
+    def user_add(self, emailaddress, password):
+        """Wrapper around Account.set_password() and Account.save()."""
+        acc = self._get_account(emailaddress)
+        if acc:
+            raise VMMError(_(u"The account '%s' already exists.") %
+                           acc.address, ACCOUNT_EXISTS)
+        self._is_other_address(acc.address, TYPE_ACCOUNT)
+        acc.set_password(password)
+        acc.save()
+        self._make_account_dirs(acc)
+
+    def alias_add(self, aliasaddress, *targetaddresses):
+        """Creates a new `Alias` entry for the given *aliasaddress* with
+        the given *targetaddresses*."""
+        alias = self._get_alias(aliasaddress)
+        if not alias:
+            self._is_other_address(alias.address, TYPE_ALIAS)
+        destinations = [DestinationEmailAddress(addr, self._dbh) \
+                for addr in targetaddresses]
+        warnings = []
+        destinations = alias.add_destinations(destinations, warnings)
+        if warnings:
+            self._warnings.append(_('Ignored destination addresses:'))
+            self._warnings.extend(('  * %s' % w for w in warnings))
+        for destination in destinations:
+            if 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)
+
+    def user_delete(self, emailaddress, force=False):
+        """Wrapper around Account.delete(...)"""
+        if not isinstance(force, bool):
+            raise TypeError('force must be a bool')
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        uid = acc.uid
+        gid = acc.gid
+        dom_dir = acc.domain.directory
+        acc_dir = acc.home
+        acc.delete(force)
+        if self._cfg.dget('account.delete_directory'):
+            try:
+                self._delete_home(dom_dir, uid, gid)
+            except VMMError, err:
+                if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
+                                NO_SUCH_DIRECTORY):
+                    warning = _(u"""\
+The account has been successfully deleted from the database.
+    But an error occurred while deleting the following directory:
+    '%(directory)s'
+    Reason: %(reason)s""") % {'directory': acc_dir, 'reason': err.msg}
+                    self._warnings.append(warning)
+                else:
+                    raise
+
+    def alias_info(self, aliasaddress):
+        """Returns an iterator object for all destinations (`EmailAddress`
+        instances) for the `Alias` with the given *aliasaddress*."""
+        alias = self._get_alias(aliasaddress)
+        if alias:
+            return alias.get_destinations()
+        if not self._is_other_address(alias.address, TYPE_ALIAS):
+            raise VMMError(_(u"The alias '%s' does not exist.") %
+                           alias.address, NO_SUCH_ALIAS)
+
+    def alias_delete(self, aliasaddress, targetaddress=None):
+        """Deletes the `Alias` *aliasaddress* with all its destinations from
+        the database. If *targetaddress* is not ``None``, only this
+        destination will be removed from the alias."""
+        alias = self._get_alias(aliasaddress)
+        if targetaddress is None:
+            alias.delete()
+        else:
+            alias.del_destination(DestinationEmailAddress(targetaddress,
+                                                          self._dbh))
+
+    def catchall_add(self, domain, *targetaddresses):
+        """Creates a new `CatchallAlias` entry for the given *domain* with
+        the given *targetaddresses*."""
+        catchall = self._get_catchall(domain)
+        destinations = [DestinationEmailAddress(addr, self._dbh) \
+                for addr in targetaddresses]
+        warnings = []
+        destinations = catchall.add_destinations(destinations, warnings)
+        if warnings:
+            self._warnings.append(_('Ignored destination addresses:'))
+            self._warnings.extend(('  * %s' % w for w in warnings))
+        for destination in destinations:
+            if 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)
+
+    def catchall_info(self, domain):
+        """Returns an iterator object for all destinations (`EmailAddress`
+        instances) for the `CatchallAlias` with the given *domain*."""
+        return self._get_catchall(domain).get_destinations()
+
+    def catchall_delete(self, domain, targetaddress=None):
+        """Deletes the `CatchallAlias` for domain *domain* with all its
+        destinations from the database. If *targetaddress* is not ``None``,
+        only this destination will be removed from the alias."""
+        catchall = self._get_catchall(domain)
+        if targetaddress is None:
+            catchall.delete()
+        else:
+            catchall.del_destination(DestinationEmailAddress(targetaddress,
+                                                             self._dbh))
+
+    def user_info(self, emailaddress, details=None):
+        """Wrapper around Account.get_info(...)"""
+        if details not in (None, 'du', 'aliases', 'full'):
+            raise VMMError(_(u"Invalid argument: '%s'") % details,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            if not self._is_other_address(acc.address, TYPE_ACCOUNT):
+                raise VMMError(_(u"The account '%s' does not exist.") %
+                               acc.address, NO_SUCH_ACCOUNT)
+        info = acc.get_info()
+        if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
+            path = os.path.join(acc.home, acc.mail_location.directory)
+            info['disk usage'] = self._get_disk_usage(path)
+            if details in (None, 'du'):
+                return info
+        if details in ('aliases', 'full'):
+            return (info, acc.get_aliases())
+        return info
+
+    def user_by_uid(self, uid):
+        """Search for an Account by its *uid*.
+        Returns a dict (address, uid and gid) if a user could be found."""
+        from VirtualMailManager.account import get_account_by_uid
+        self._db_connect()
+        return get_account_by_uid(uid, self._dbh)
+
+    def user_password(self, emailaddress, password):
+        """Wrapper for Account.modify('password' ...)."""
+        if not isinstance(password, basestring) or not password:
+            raise VMMError(_(u"Could not accept password: '%s'") % password,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('password', password)
+
+    def user_name(self, emailaddress, name):
+        """Wrapper for Account.modify('name', ...)."""
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('name', name)
+
+    def user_note(self, emailaddress, note):
+        """Wrapper for Account.modify('note', ...)."""
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        acc.modify('note', note)
+
+    def user_quotalimit(self, emailaddress, bytes_, messages=0):
+        """Wrapper for Account.update_quotalimit(QuotaLimit)."""
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                        acc.address, NO_SUCH_ACCOUNT)
+        if bytes_ == 'default':
+            quotalimit = None
+        else:
+            if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
+                raise TypeError("'bytes_' and 'messages' have to be "
+                                "integers or longs.")
+            quotalimit = QuotaLimit(self._dbh, bytes=bytes_,
+                                    messages=messages)
+        acc.update_quotalimit(quotalimit)
+
+    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,
+                           INVALID_ARGUMENT)
+        acc = self._get_account(emailaddress)
+        if not acc:
+            raise VMMError(_(u"The account '%s' does not exist.") %
+                           acc.address, NO_SUCH_ACCOUNT)
+        if transport == 'default':
+            transport = None
+        else:
+            transport = 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.") %
+                        acc.address, NO_SUCH_ACCOUNT)
+        if len(services) == 1 and services[0] == 'default':
+            serviceset = None
+        else:
+            kwargs = dict.fromkeys(SERVICES, False)
+            for service in set(services):
+                if service not in SERVICES:
+                    raise VMMError(_(u"Unknown service: '%s'") % service,
+                                UNKNOWN_SERVICE)
+                kwargs[service] = True
+            serviceset = ServiceSet(self._dbh, **kwargs)
+        acc.update_serviceset(serviceset)
+
+    def relocated_add(self, emailaddress, targetaddress):
+        """Creates a new `Relocated` entry in the database. If there is
+        already a relocated user with the given *emailaddress*, only the
+        *targetaddress* for the relocated user will be updated."""
+        relocated = self._get_relocated(emailaddress)
+        if not relocated:
+            self._is_other_address(relocated.address, TYPE_RELOCATED)
+        destination = DestinationEmailAddress(targetaddress, self._dbh)
+        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)
+
+    def relocated_info(self, emailaddress):
+        """Returns the target address of the relocated user with the given
+        *emailaddress*."""
+        relocated = self._get_relocated(emailaddress)
+        if relocated:
+            return relocated.get_info()
+        if not self._is_other_address(relocated.address, TYPE_RELOCATED):
+            raise VMMError(_(u"The relocated user '%s' does not exist.") %
+                           relocated.address, NO_SUCH_RELOCATED)
+
+    def relocated_delete(self, emailaddress):
+        """Deletes the relocated user with the given *emailaddress* from
+        the database."""
+        relocated = self._get_relocated(emailaddress)
+        relocated.delete()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/mailbox.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,294 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.mailbox
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's mailbox classes for the Maildir, single dbox
+    (sdbox) and multi dbox (mdbox) mailbox formats.
+"""
+
+import os
+import re
+from binascii import a2b_base64, b2a_base64
+from subprocess import Popen, PIPE
+
+from VirtualMailManager.account import Account
+from VirtualMailManager.common import lisdir
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.constants import VMM_ERROR
+
+
+__all__ = ('new', 'Maildir', 'SingleDbox', 'MultiDbox',
+           'utf8_to_mutf7', 'mutf7_to_utf8')
+
+_ = lambda msg: msg
+cfg_dget = lambda option: None
+
+
+def _mbase64_encode(inp, dest):
+    if inp:
+        mb64 = b2a_base64(''.join(inp).encode('utf-16be'))
+        dest.append('&%s-' % mb64.rstrip('\n=').replace('/', ','))
+        del inp[:]
+
+
+def _mbase64_to_unicode(mb64):
+    return unicode(a2b_base64(mb64.replace(',', '/') + '==='), 'utf-16be')
+
+
+def utf8_to_mutf7(src):
+    """
+    Converts the international mailbox name `src` into a modified
+    version version of the UTF-7 encoding.
+    """
+    ret = []
+    tmp = []
+    for c in src:
+        ordc = ord(c)
+        if 0x20 <= ordc <= 0x25 or 0x27 <= ordc <= 0x7E:
+            _mbase64_encode(tmp, ret)
+            ret.append(c)
+        elif ordc == 0x26:
+            _mbase64_encode(tmp, ret)
+            ret.append('&-')
+        else:
+            tmp.append(c)
+    _mbase64_encode(tmp, ret)
+    return ''.join(ret)
+
+
+def mutf7_to_utf8(src):
+    """
+    Converts the mailbox name `src` from modified UTF-7 encoding to UTF-8.
+    """
+    ret = []
+    tmp = []
+    for c in src:
+        if c == '&' and not tmp:
+            tmp.append(c)
+        elif c == '-' and tmp:
+            if len(tmp) is 1:
+                ret.append('&')
+            else:
+                ret.append(_mbase64_to_unicode(''.join(tmp[1:])))
+            tmp = []
+        elif tmp:
+            tmp.append(c)
+        else:
+            ret.append(c)
+    if tmp:
+        ret.append(_mbase64_to_unicode(''.join(tmp[1:])))
+    return ''.join(ret)
+
+
+class Mailbox(object):
+    """Base class of all mailbox classes."""
+    __slots__ = ('_boxes', '_root', '_sep', '_user')
+    FILE_MODE = 0600
+    _ctrl_chr_re = re.compile('[\x00-\x1F\x7F-\x9F]')
+    _box_name_re = re.compile('^[\x20-\x25\x27-\x7E]+$')
+
+    def __init__(self, account):
+        """
+        Creates a new mailbox instance.
+        Use one of the `Maildir`, `SingleDbox` or `MultiDbox` classes.
+        """
+        assert isinstance(account, Account) and lisdir(account.home)
+        self._user = account
+        self._boxes = []
+        self._root = self._user.mail_location.directory
+        self._sep = '/'
+        os.chdir(self._user.home)
+
+    def _add_boxes(self, mailboxes, subscribe):
+        """Create all mailboxes from the `mailboxes` list.
+        If `subscribe` is *True*, the mailboxes will be listed in the
+        subscriptions file."""
+        raise NotImplementedError
+
+    def _validate_box_name(self, name, good, bad):
+        """
+        Validates the mailboxes name `name`.  When the name is valid, it
+        will be added to the `good` set.  Invalid mailbox names will be
+        appended to the `bad` list.
+        """
+        name = name.strip()
+        if not name:
+            return
+        if self.__class__._ctrl_chr_re.search(name):  # no control chars
+            bad.append(name)
+            return
+        if name[0] in (self._sep, '~'):
+            bad.append(name)
+            return
+        if self._sep == '/':
+            if '//' in name or '/./' in name or '/../' in name or \
+               name.startswith('../'):
+                bad.append(name)
+                return
+        elif '/' in name or '..' in name:
+            bad.append(name)
+            return
+        if not self.__class__._box_name_re.match(name):
+            tmp = utf8_to_mutf7(name)
+            if name == mutf7_to_utf8(tmp):
+                if self._user.mail_location.mbformat == 'maildir':
+                    good.add(tmp)
+                else:
+                    good.add(name)
+                return
+            else:
+                bad.append(name)
+                return
+        good.add(name)
+
+    def add_boxes(self, mailboxes, subscribe):
+        """
+        Create all mailboxes from the `mailboxes` list in the user's
+        mail directory.  When `subscribe` is ``True`` all created mailboxes
+        will be listed in the subscriptions file.
+        Returns a list of invalid mailbox names, if any.
+        """
+        assert isinstance(mailboxes, list) and isinstance(subscribe, bool)
+        good = set()
+        bad = []
+        for box in mailboxes:
+            if self._sep == '/':
+                box = box.replace('.', self._sep)
+            self._validate_box_name(box, good, bad)
+        self._add_boxes(good, subscribe)
+        return bad
+
+    def create(self):
+        """Create the INBOX in the user's mail directory."""
+        raise NotImplementedError
+
+
+class Maildir(Mailbox):
+    """Class for Maildir++ mailboxes."""
+
+    __slots__ = ('_subdirs')
+
+    def __init__(self, account):
+        """
+        Create a new Maildir++ instance.
+        Call the instance's create() method, in order to create the INBOX.
+        For additional mailboxes use the add_boxes() method.
+        """
+        super(self.__class__, self).__init__(account)
+        self._sep = '.'
+        self._subdirs = ('cur', 'new', 'tmp')
+
+    def _create_maildirfolder_file(self, path):
+        """Mark the Maildir++ folder as Maildir folder."""
+        maildirfolder_file = os.path.join(self._sep + path, 'maildirfolder')
+        os.close(os.open(maildirfolder_file, os.O_CREAT | os.O_WRONLY,
+                         self.__class__.FILE_MODE))
+        os.chown(maildirfolder_file, self._user.uid, self._user.gid)
+
+    def _make_maildir(self, path):
+        """
+        Create Maildir++ folders with the cur, new and tmp subdirectories.
+        """
+        mode = cfg_dget('account.directory_mode')
+        uid = self._user.uid
+        gid = self._user.gid
+        os.mkdir(path, mode)
+        os.chown(path, uid, gid)
+        for subdir in self._subdirs:
+            dir_ = os.path.join(path, subdir)
+            os.mkdir(dir_, mode)
+            os.chown(dir_, uid, gid)
+
+    def _subscribe_boxes(self):
+        """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()
+        os.chown('subscriptions', self._user.uid, self._user.gid)
+        os.chmod('subscriptions', self.__class__.FILE_MODE)
+        del self._boxes[:]
+
+    def _add_boxes(self, mailboxes, subscribe):
+        for mailbox in mailboxes:
+            self._make_maildir(self._sep + mailbox)
+            self._create_maildirfolder_file(mailbox)
+            self._boxes.append(mailbox)
+        if subscribe:
+            self._subscribe_boxes()
+
+    def create(self):
+        """Creates a Maildir++ INBOX."""
+        self._make_maildir(self._root)
+        os.chdir(self._root)
+
+
+class SingleDbox(Mailbox):
+    """
+    Class for (single) dbox mailboxes.
+    See http://wiki.dovecot.org/MailboxFormat/dbox for details.
+    """
+
+    __slots__ = ()
+
+    def __init__(self, account):
+        """
+        Create a new dbox instance.
+        Call the instance's create() method, in order to create the INBOX.
+        For additional mailboxes use the add_boxes() method.
+        """
+        assert cfg_dget('misc.dovecot_version') >= \
+                account.mail_location.dovecot_version
+        super(SingleDbox, self).__init__(account)
+
+    def _doveadm_create(self, mailboxes, subscribe):
+        """Wrap around Dovecot's doveadm"""
+        cmd_args = [cfg_dget('bin.dovecotpw'), 'mailbox', 'create', '-u',
+                    str(self._user.address)]
+        if subscribe:
+            cmd_args.append('-s')
+        cmd_args.extend(mailboxes)
+        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)
+
+    def create(self):
+        """Create a dbox INBOX"""
+        os.mkdir(self._root, cfg_dget('account.directory_mode'))
+        os.chown(self._root, self._user.uid, self._user.gid)
+        self._doveadm_create(('INBOX',), False)
+        os.chdir(self._root)
+
+    def _add_boxes(self, mailboxes, subscribe):
+        self._doveadm_create(mailboxes, subscribe)
+
+
+class MultiDbox(SingleDbox):
+    """
+    Class for multi dbox mailboxes.
+    See http://wiki.dovecot.org/MailboxFormat/dbox#Multi-dbox for details.
+    """
+
+    __slots__ = ()
+
+
+def new(account):
+    """Create a new Mailbox instance for the given Account."""
+    mbfmt = account.mail_location.mbformat
+    if mbfmt == 'maildir':
+        return Maildir(account)
+    elif mbfmt == 'mdbox':
+        return MultiDbox(account)
+    elif mbfmt == 'sdbox':
+        return SingleDbox(account)
+    raise ValueError('unsupported mailbox format: %r' % mbfmt)
+
+del _, cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/maillocation.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,161 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.maillocation
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's maillocation module to handle Dovecot's
+    mail_location setting for accounts.
+
+"""
+
+from VirtualMailManager.constants import MAILLOCATION_INIT
+from VirtualMailManager.errors import MailLocationError as MLErr
+from VirtualMailManager.pycompat import all
+
+
+__all__ = ('MailLocation', 'known_format')
+
+_ = lambda msg: msg
+_format_info = {
+    'maildir': dict(dovecot_version=0x10000f00, postfix=True),
+    'mdbox': dict(dovecot_version=0x20000b05, postfix=False),
+    'sdbox': dict(dovecot_version=0x20000c03, postfix=False),
+}
+
+
+class MailLocation(object):
+    """Class to handle mail_location relevant information."""
+    __slots__ = ('_directory', '_mbfmt', '_mid', '_dbh')
+    _kwargs = ('mid', 'mbfmt', 'directory')
+
+    def __init__(self, dbh, **kwargs):
+        """Creates a new MailLocation instance.
+
+        Either the mid keyword or the mbfmt and directory keywords must be
+        specified.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection
+          A database connection for the database access.
+
+        Keyword arguments:
+
+        `mid` : int
+          the id of a mail_location
+        `mbfmt` : str
+          the mailbox format of the mail_location. One out of: ``maildir``,
+          ``sdbox`` and ``mdbox``.
+        `directory` : str
+          name of the mailbox root directory.
+        """
+        self._dbh = dbh
+        self._directory = None
+        self._mbfmt = None
+        self._mid = 0
+
+        for key in kwargs.iterkeys():
+            if key not in self.__class__._kwargs:
+                raise ValueError('unrecognized keyword: %r' % key)
+        mid = kwargs.get('mid')
+        if mid:
+            assert isinstance(mid, (int, long))
+            self._load_by_mid(mid)
+        else:
+            args = kwargs.get('mbfmt'), kwargs.get('directory')
+            assert all(isinstance(arg, basestring) for arg in args)
+            if args[0].lower() not in _format_info:
+                raise MLErr(_(u"Unsupported mailbox format: '%s'") % args[0],
+                            MAILLOCATION_INIT)
+            directory = args[1].strip()
+            if not directory:
+                raise MLErr(_(u"Empty directory name"), MAILLOCATION_INIT)
+            if len(directory) > 20:
+                raise MLErr(_(u"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)
+
+    @property
+    def directory(self):
+        """The mail_location's directory name."""
+        return self._directory
+
+    @property
+    def dovecot_version(self):
+        """The required Dovecot version for this mailbox format."""
+        return _format_info[self._mbfmt]['dovecot_version']
+
+    @property
+    def postfix(self):
+        """`True` if Postfix supports this mailbox format, else `False`."""
+        return _format_info[self._mbfmt]['postfix']
+
+    @property
+    def mbformat(self):
+        """The mail_location's mailbox format."""
+        return self._mbfmt
+
+    @property
+    def mail_location(self):
+        """The mail_location, e.g. ``maildir:~/Maildir``"""
+        return self.__str__()
+
+    @property
+    def mid(self):
+        """The mail_location's unique ID."""
+        return self._mid
+
+    def _load_by_mid(self, mid):
+        """Load mail_location relevant information by *mid*"""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT format, directory FROM mailboxformat, '
+                    'maillocation WHERE mid = %u AND '
+                    'maillocation.fid = mailboxformat.fid' % mid)
+        result = dbc.fetchone()
+        dbc.close()
+        if not result:
+            raise ValueError('Unknown mail_location id specified: %r' % mid)
+        self._mid = mid
+        self._mbfmt, self._directory = result
+
+    def _load_by_names(self, mbfmt, directory):
+        """Try to load mail_location relevant information by *mbfmt* and
+        *directory* name. If it fails goto _save()."""
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT mid FROM maillocation WHERE fid = (SELECT fid "
+                    "FROM mailboxformat WHERE format = %s) AND directory = %s",
+                    (mbfmt, directory))
+        result = dbc.fetchone()
+        dbc.close()
+        if not result:
+            self._save(mbfmt, directory)
+        else:
+            self._mid = result[0]
+            self._mbfmt = mbfmt
+            self._directory = directory
+
+    def _save(self, mbfmt, directory):
+        """Save a new mail_location in the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('maillocation_id')")
+        mid = dbc.fetchone()[0]
+        dbc.execute("INSERT INTO maillocation (fid, mid, directory) VALUES ("
+                    "(SELECT fid FROM mailboxformat WHERE format = %s), %s, "
+                    "%s)",  (mbfmt, mid, directory))
+        self._dbh.commit()
+        dbc.close()
+        self._mid = mid
+        self._mbfmt = mbfmt
+        self._directory = directory
+
+
+def known_format(mbfmt):
+    """Checks if the mailbox format *mbfmt* is known, returns bool."""
+    return mbfmt.lower() in _format_info
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/network.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,100 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2011 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.network
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Network/IP address related class and function
+"""
+
+import socket
+
+
+class NetInfo(object):
+    """Simple class for CIDR network addresses an IP addresses."""
+    __slots__ = ('_addr', '_prefix', '_bits_max', '_family', '_nw_addr')
+
+    def __init__(self, nw_address):
+        """Creates a new `NetInfo` instance.
+
+        Argument:
+
+        `nw_address` : basestring
+          string representation of an IPv4/IPv6 address or network address.
+          E.g. 192.0.2.13, 192.0.2.0/24, 2001:db8::/32 or ::1
+          When the address has no netmask the prefix length will be set to
+          32 for IPv4 addresses and 128 for IPv6 addresses.
+        """
+        self._addr = None
+        self._prefix = 0
+        self._bits_max = 0
+        self._family = 0
+        self._nw_addr = nw_address
+        self._parse_net_range()
+
+    def __hash__(self):
+        return hash((self._addr, self._family, self._prefix))
+
+    def __repr__(self):
+        return "NetInfo('%s')" % self._nw_addr
+
+    def _parse_net_range(self):
+        """Parse the network range of `self._nw_addr and assign values
+        to the class attributes.
+        `"""
+        sep = '/'
+        if self._nw_addr.count(sep):
+            ip_address, sep, self._prefix = self._nw_addr.partition(sep)
+            self._family, self._addr = get_ip_addr_info(ip_address)
+        else:
+            self._family, self._addr = get_ip_addr_info(self._nw_addr)
+        self._bits_max = (128, 32)[self._family is socket.AF_INET]
+        if self._prefix is 0:
+            self._prefix = self._bits_max
+        else:
+            try:
+                self._prefix = int(self._prefix)
+            except ValueError:
+                raise ValueError('Invalid prefix length: %r' % self._prefix)
+        if self._prefix > self._bits_max or self._prefix < 0:
+            raise ValueError('Invalid prefix length: %r' % self._prefix)
+
+    @property
+    def family(self):
+        """Address family: `socket.AF_INET` or `socket.AF_INET6`"""
+        return self._family
+
+    def address_in_net(self, ip_address):
+        """Checks if the `ip_address` belongs to the same subnet."""
+        family, address = get_ip_addr_info(ip_address)
+        if family != self._family:
+            return False
+        return address >> self._bits_max - self._prefix == \
+               self._addr >> self._bits_max - self._prefix
+
+
+def get_ip_addr_info(ip_address):
+    """Checks if the string `ip_address` is a valid IPv4 or IPv6 address.
+
+    When the `ip_address` could be validated successfully a tuple
+    `(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:
+        raise TypeError('ip_address must be a non empty string.')
+    if not ip_address.count(':'):
+        family = socket.AF_INET
+        try:
+            address = socket.inet_aton(ip_address)
+        except socket.error:
+            raise ValueError('Not a valid IPv4 address: %r' % ip_address)
+    elif not socket.has_ipv6:
+        raise ValueError('Unsupported IP address (IPv6): %r' % ip_address)
+    else:
+        family = socket.AF_INET6
+        try:
+            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))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/password.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,459 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.password
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's password module to generate password hashes from
+    passwords or random passwords. This module provides following
+    functions:
+
+        hashed_password = pwhash(password[, scheme][, user])
+        random_password = randompw()
+        scheme, encoding = verify_scheme(scheme)
+        schemes, encodings = list_schemes()
+"""
+
+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')
+DEFAULT_HEX = (None, 'HEX')
+CRYPT_ID_MD5 = 1
+CRYPT_ID_BLF = '2a'
+CRYPT_ID_SHA256 = 5
+CRYPT_ID_SHA512 = 6
+CRYPT_SALT_LEN = 2
+CRYPT_BLF_ROUNDS_MIN = 4
+CRYPT_BLF_ROUNDS_MAX = 31
+CRYPT_BLF_SALT_LEN = 22
+CRYPT_MD5_SALT_LEN = 8
+CRYPT_SHA2_ROUNDS_DEFAULT = 5000
+CRYPT_SHA2_ROUNDS_MIN = 1000
+CRYPT_SHA2_ROUNDS_MAX = 999999999
+CRYPT_SHA2_SALT_LEN = 16
+SALTED_ALGO_SALT_LEN = 4
+
+
+_ = lambda msg: msg
+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))
+
+
+def _dovecotpw(password, scheme, encoding):
+    """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
+    the hashed password: {scheme[.encoding]}hash
+    """
+    if encoding:
+        scheme = '.'.join((scheme, encoding))
+    cmd_args = [cfg_dget('bin.dovecotpw'), '-s', scheme, '-p',
+                get_unicode(password)]
+    if cfg_dget('misc.dovecot_version') >= 0x20000a01:
+        cmd_args.insert(1, 'pw')
+    process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
+    stdout, stderr = process.communicate()
+    if process.returncode:
+        raise VMMError(stderr.strip(), VMM_ERROR)
+    hashed = stdout.strip()
+    if not hashed.startswith('{%s}' % scheme):
+        raise VMMError('Unexpected result from %s: %s' %
+                       (cfg_dget('bin.dovecotpw'), hashed), VMM_ERROR)
+    return hashed
+
+
+def _md4_new():
+    """Returns an new MD4-hash object if supported by the hashlib or
+    provided by PyCrypto - other `None`.
+    """
+    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':
+            return None
+        else:
+            raise
+
+
+def _format_digest(digest, scheme, encoding):
+    """Formats the arguments to a string: {scheme[.encoding]}digest."""
+    if not encoding:
+        return '{%s}%s' % (scheme, digest)
+    return '{%s.%s}%s' % (scheme, encoding, digest)
+
+
+def _clear_hash(password, scheme, encoding):
+    """Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""
+    if encoding:
+        if encoding == 'HEX':
+            password = password.encode('hex')
+        else:
+            password = password.encode('base64').replace('\n', '')
+        return _format_digest(password, scheme, encoding)
+    return get_unicode('{%s}%s' % (scheme, password))
+
+
+def _get_crypt_blowfish_salt():
+    """Generates a salt for Blowfish crypt."""
+    rounds = cfg_dget('misc.crypt_blowfish_rounds')
+    if rounds < CRYPT_BLF_ROUNDS_MIN:
+        rounds = CRYPT_BLF_ROUNDS_MIN
+    elif rounds > CRYPT_BLF_ROUNDS_MAX:
+        rounds = CRYPT_BLF_ROUNDS_MAX
+    return '$%s$%02d$%s' % (CRYPT_ID_BLF, rounds,
+                            _get_salt(CRYPT_BLF_SALT_LEN))
+
+
+def _get_crypt_sha2_salt(crypt_id):
+    """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
+    method.
+    *crypt_id* must be either `5` (SHA-256) or `6` (SHA-512).
+    """
+    assert crypt_id in (CRYPT_ID_SHA256, CRYPT_ID_SHA512), 'invalid crypt ' \
+           'id: %r' % crypt_id
+    if crypt_id is CRYPT_ID_SHA512:
+        rounds = cfg_dget('misc.crypt_sha512_rounds')
+    else:
+        rounds = cfg_dget('misc.crypt_sha256_rounds')
+    if rounds < CRYPT_SHA2_ROUNDS_MIN:
+        rounds = CRYPT_SHA2_ROUNDS_MIN
+    elif rounds > CRYPT_SHA2_ROUNDS_MAX:
+        rounds = CRYPT_SHA2_ROUNDS_MAX
+    if rounds == CRYPT_SHA2_ROUNDS_DEFAULT:
+        return '$%d$%s' % (crypt_id, _get_salt(CRYPT_SHA2_SALT_LEN))
+    return '$%d$rounds=%d$%s' % (crypt_id, rounds,
+                                 _get_salt(CRYPT_SHA2_SALT_LEN))
+
+
+def _crypt_hash(password, scheme, encoding):
+    """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes."""
+    if scheme == 'CRYPT':
+        salt = _get_salt(CRYPT_SALT_LEN)
+    elif scheme == 'BLF-CRYPT':
+        salt = _get_crypt_blowfish_salt()
+    elif scheme in ('MD5-CRYPT', 'MD5'):
+        salt = '$%d$%s' % (CRYPT_ID_MD5, _get_salt(CRYPT_MD5_SALT_LEN))
+    elif scheme == 'SHA256-CRYPT':
+        salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
+    else:
+        salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
+    encrypted = crypt(password, salt)
+    if encoding:
+        if encoding == 'HEX':
+            encrypted = encrypted.encode('hex')
+        else:
+            encrypted = encrypted.encode('base64').replace('\n', '')
+    if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
+       cfg_dget('misc.dovecot_version') < 0x20000b06:
+        scheme = 'CRYPT'
+    return _format_digest(encrypted, scheme, encoding)
+
+
+def _md4_hash(password, scheme, encoding):
+    """Generates encoded PLAIN-MD4 hashes."""
+    md4 = _md4_new()
+    if md4:
+        md4.update(password)
+        if encoding in DEFAULT_HEX:
+            digest = md4.hexdigest()
+        else:
+            digest = md4.digest().encode('base64').rstrip()
+        return _format_digest(digest, scheme, encoding)
+    return _dovecotpw(password, scheme, encoding)
+
+
+def _md5_hash(password, scheme, encoding, user=None):
+    """Generates DIGEST-MD5 aka PLAIN-MD5 and LDAP-MD5 hashes."""
+    md5 = hashlib.md5()
+    if scheme == 'DIGEST-MD5':
+        #  Prior to Dovecot v1.1.12/v1.2.beta2 there was a problem with a
+        #  empty auth_realms setting in dovecot.conf and user@domain.tld
+        #  usernames. So we have to generate different hashes for different
+        #  versions. See also:
+        #       http://dovecot.org/list/dovecot-news/2009-March/000103.html
+        #       http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae
+        if cfg_dget('misc.dovecot_version') >= 0x1010cf00:
+            md5.update('%s:%s:' % (user.localpart, user.domainname))
+        else:
+            md5.update('%s::' % user)
+    md5.update(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()
+    return _format_digest(digest, scheme, encoding)
+
+
+def _ntlm_hash(password, scheme, encoding):
+    """Generates NTLM hashes."""
+    md4 = _md4_new()
+    if md4:
+        password = ''.join('%s\x00' % c for c in password)
+        md4.update(password)
+        if encoding in DEFAULT_HEX:
+            digest = md4.hexdigest()
+        else:
+            digest = md4.digest().encode('base64').rstrip()
+        return _format_digest(digest, scheme, encoding)
+    return _dovecotpw(password, scheme, encoding)
+
+
+def _sha1_hash(password, scheme, encoding):
+    """Generates SHA1 aka SHA hashes."""
+    sha1 = hashlib.sha1(password)
+    if encoding in DEFAULT_B64:
+        digest = sha1.digest().encode('base64').rstrip()
+    else:
+        digest = sha1.hexdigest()
+    return _format_digest(digest, scheme, encoding)
+
+
+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)
+
+
+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)
+
+
+def _smd5_hash(password, scheme, encoding):
+    """Generates SMD5 (salted PLAIN-MD5) hashes."""
+    md5 = hashlib.md5(password)
+    salt = _get_salt(SALTED_ALGO_SALT_LEN)
+    md5.update(salt)
+    if encoding in DEFAULT_B64:
+        digest = (md5.digest() + salt).encode('base64').rstrip()
+    else:
+        digest = md5.hexdigest() + salt.encode('hex')
+    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)
+    sha1.update(salt)
+    if encoding in DEFAULT_B64:
+        digest = (sha1.digest() + salt).encode('base64').rstrip()
+    else:
+        digest = sha1.hexdigest() + salt.encode('hex')
+    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)
+
+
+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)
+
+_scheme_info = {
+    'CLEARTEXT': (_clear_hash, 0x10000f00),
+    'CRAM-MD5': (_dovecotpw, 0x10000f00),
+    'CRYPT': (_crypt_hash, 0x10000f00),
+    'DIGEST-MD5': (_md5_hash, 0x10000f00),
+    'HMAC-MD5': (_dovecotpw, 0x10000f00),
+    'LANMAN': (_dovecotpw, 0x10000f00),
+    'LDAP-MD5': (_md5_hash, 0x10000f00),
+    'MD5': (_crypt_hash, 0x10000f00),
+    'MD5-CRYPT': (_crypt_hash, 0x10000f00),
+    'NTLM': (_ntlm_hash, 0x10000f00),
+    'OTP': (_dovecotpw, 0x10100a01),
+    'PLAIN': (_clear_hash, 0x10000f00),
+    'PLAIN-MD4': (_md4_hash, 0x10000f00),
+    'PLAIN-MD5': (_md5_hash, 0x10000f00),
+    'RPA': (_dovecotpw, 0x10000f00),
+    'SHA': (_sha1_hash, 0x10000f00),
+    'SHA1': (_sha1_hash, 0x10000f00),
+    'SHA256': (_sha256_hash, 0x10100a01),
+    'SHA512': (_sha512_hash, 0x20000b03),
+    'SKEY': (_dovecotpw, 0x10100a01),
+    'SMD5': (_smd5_hash, 0x10000f00),
+    'SSHA': (_ssha1_hash, 0x10000f00),
+    'SSHA256': (_ssha256_hash, 0x10200a04),
+    'SSHA512': (_ssha512_hash, 0x20000b03),
+}
+
+
+def list_schemes():
+    """Returns the tuple (schemes, encodings).
+
+    `schemes` is an iterator for all supported password schemes (depends on
+    the used Dovecot version and features of the libc).
+    `encodings` is a tuple with all usable encoding suffixes. The tuple may
+    be empty.
+    """
+    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 = ()
+    return schemes, encodings
+
+
+def verify_scheme(scheme):
+    """Checks if the password scheme *scheme* is known and supported by the
+    configured `misc.dovecot_version`.
+
+    The *scheme* maybe a password scheme's name (e.g.: 'PLAIN') or a scheme
+    name with a encoding suffix (e.g. 'PLAIN.BASE64').  If the scheme is
+    known and supported by the used Dovecot version,
+    a tuple ``(scheme, encoding)`` will be returned.
+    The `encoding` in the tuple may be `None`.
+
+    Raises a `VMMError` if the password scheme:
+      * is unknown
+      * depends on a newer Dovecot version
+      * has a unknown encoding suffix
+    """
+    assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme
+    scheme_encoding = scheme.upper().split('.')
+    scheme = scheme_encoding[0]
+    if scheme not in _scheme_info:
+        raise VMMError(_(u"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,
+                       'version': version_str(_scheme_info[scheme][1])},
+                       VMM_ERROR)
+    if len(scheme_encoding) > 1:
+        if cfg_dget('misc.dovecot_version') < 0x10100a01:
+            raise VMMError(_(u'Encoding suffixes for password schemes require '
+                             u'Dovecot >= v1.1.alpha1.'), VMM_ERROR)
+        if scheme_encoding[1] not in ('B64', 'BASE64', 'HEX'):
+            raise VMMError(_(u"Unsupported password encoding: '%s'") %
+                           scheme_encoding[1], VMM_ERROR)
+        encoding = scheme_encoding[1]
+    else:
+        encoding = None
+    return scheme, encoding
+
+
+def pwhash(password, scheme=None, user=None):
+    """Generates a password hash from the plain text *password* string.
+
+    If no *scheme* is given the password scheme from the configuration will
+    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):
+        raise TypeError('Password is not a string: %r' % password)
+    if isinstance(password, unicode):
+        password = password.encode(ENCODING)
+    password = password.strip()
+    if not password:
+        raise ValueError("Could not accept empty password.")
+    if scheme is None:
+        scheme = cfg_dget('misc.password_scheme')
+    scheme, encoding = verify_scheme(scheme)
+    if scheme == 'DIGEST-MD5':
+        assert isinstance(user, EmailAddress)
+        return _md5_hash(password, scheme, encoding, user)
+    return _scheme_info[scheme][0](password, scheme, encoding)
+
+
+def randompw():
+    """Generates a plain text random password.
+
+    The length of the password can be configured in the ``vmm.cfg``
+    (account.password_length).
+    """
+    pw_len = cfg_dget('account.password_length')
+    if pw_len < 8:
+        pw_len = 8
+    return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len))
+
+
+def _test_crypt_algorithms():
+    """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt()."""
+    _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru'
+    _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\
+9qrb1jehe7hn4'
+    _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\
+JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1'
+
+    if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish:
+        _scheme_info['BLF-CRYPT'] = (_crypt_hash, 0x10000f00)
+    if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256:
+        _scheme_info['SHA256-CRYPT'] = (_crypt_hash, 0x10000f00)
+    if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512:
+        _scheme_info['SHA512-CRYPT'] = (_crypt_hash, 0x10000f00)
+
+_test_crypt_algorithms()
+del _, cfg_dget, _test_crypt_algorithms
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/pycompat/__init__.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,38 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/pycompat/hashlib.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,58 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010 - 2012, 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')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/quotalimit.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,126 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2011 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.quotalimit
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's QuotaLimit class to manage quota limits
+    for domains and accounts.
+"""
+
+from VirtualMailManager.pycompat import all
+
+_ = lambda msg: msg
+
+
+class QuotaLimit(object):
+    """Class to handle quota limit specific data."""
+    __slots__ = ('_dbh', '_qid', '_bytes', '_messages')
+    _kwargs = ('qid', 'bytes', 'messages')
+
+    def __init__(self, dbh, **kwargs):
+        """Create a new QuotaLimit instance.
+
+        Either the `qid` keyword or the `bytes` and `messages` keywords
+        must be specified.
+
+        Arguments:
+
+        `dbh` : pyPgSQL.PgSQL.Connection || psycopg2._psycopg.connection
+          A database connection for the database access.
+
+        Keyword arguments:
+
+        `qid` : int
+          The id of a quota limit
+        `bytes` : long
+          The quota limit in bytes.
+        `messages` : int
+          The quota limit in number of messages
+        """
+        self._dbh = dbh
+        self._qid = 0
+        self._bytes = 0
+        self._messages = 0
+
+        for key in kwargs.iterkeys():
+            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))
+            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
+            self._load_by_limit()
+
+    @property
+    def bytes(self):
+        """Quota limit in bytes."""
+        return self._bytes
+
+    @property
+    def messages(self):
+        """Quota limit in number of messages."""
+        return self._messages
+
+    @property
+    def qid(self):
+        """The quota limit's unique ID."""
+        return self._qid
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._qid == other._qid
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._qid != other._qid
+        return NotImplemented
+
+    def _load_by_limit(self):
+        """Load the quota limit by limit values from the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT qid FROM quotalimit WHERE bytes = %s AND '
+                    'messages = %s', (self._bytes, self._messages))
+        res = dbc.fetchone()
+        dbc.close()
+        if res:
+            self._qid = res[0]
+        else:
+            self._save()
+
+    def _load_by_qid(self, qid):
+        """Load the quota limit by its unique ID from the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT bytes, messages FROM quotalimit WHERE qid = %s',
+                    (qid,))
+        res = dbc.fetchone()
+        dbc.close()
+        if not res:
+            raise ValueError('Unknown quota limit id specified: %r' % qid)
+        self._qid = qid
+        self._bytes, self._messages = res
+
+    def _save(self):
+        """Store a new quota limit in the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('quotalimit_id')")
+        self._qid = dbc.fetchone()[0]
+        dbc.execute('INSERT INTO quotalimit (qid, bytes, messages) VALUES '
+                    '(%s, %s, %s)', (self._qid, self._bytes, self._messages))
+        self._dbh.commit()
+        dbc.close()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/relocated.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,123 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.relocated
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Relocated class to handle relocated users.
+"""
+
+from VirtualMailManager.domain import get_gid
+from VirtualMailManager.emailaddress import EmailAddress, \
+     DestinationEmailAddress
+from VirtualMailManager.errors import RelocatedError as RErr
+from VirtualMailManager.constants import DOMAIN_INVALID, NO_SUCH_DOMAIN, \
+     NO_SUCH_RELOCATED, RELOCATED_ADDR_DEST_IDENTICAL, RELOCATED_EXISTS
+
+
+_ = lambda msg: msg
+
+
+class Relocated(object):
+    """Class to handle e-mail addresses of relocated users."""
+    __slots__ = ('_addr', '_dest', '_gid', '_dbh')
+
+    def __init__(self, dbh, address):
+        """Creates a new *Relocated* instance.  The ``address`` is the
+        old e-mail address of the user.
+
+        Use `setDestination()` to set/update the new address, where the
+        user has moved to.
+
+        """
+        assert isinstance(address, EmailAddress)
+        self._addr = address
+        self._dbh = dbh
+        self._gid = get_gid(self._dbh, self._addr.domainname)
+        if not self._gid:
+            raise RErr(_(u"The domain '%s' does not exist.") %
+                       self._addr.domainname, NO_SUCH_DOMAIN)
+        self._dest = None
+
+        self._load()
+
+    def __nonzero__(self):
+        """Returns `True` if the Relocated is known, `False` if it's new."""
+        return self._dest is not None
+
+    def _load(self):
+        """Loads the destination address from the database into the
+        `_dest` attribute.
+
+        """
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT destination FROM relocated WHERE gid = %s AND '
+                    'address = %s', (self._gid, self._addr.localpart))
+        destination = dbc.fetchone()
+        dbc.close()
+        if destination:
+            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)
+            self._dest = destination
+
+    @property
+    def address(self):
+        """The Relocated's EmailAddress instance."""
+        return self._addr
+
+    def set_destination(self, destination):
+        """Sets/updates the new address of the relocated user."""
+        update = False
+        assert isinstance(destination, DestinationEmailAddress)
+        if destination.at_localhost:
+            raise RErr(_(u"The destination address' domain name must not be "
+                         u"localhost."), DOMAIN_INVALID)
+        if self._addr == destination:
+            raise RErr(_(u'Address and destination are identical.'),
+                       RELOCATED_ADDR_DEST_IDENTICAL)
+        if self._dest:
+            if self._dest == destination:
+                raise RErr(_(u"The relocated user '%s' already exists.") %
+                           self._addr, RELOCATED_EXISTS)
+            else:
+                self._dest = destination
+                update = True
+        else:
+            self._dest = destination
+
+        dbc = self._dbh.cursor()
+        if not update:
+            dbc.execute('INSERT INTO relocated (gid, address, destination) '
+                        'VALUES (%s, %s, %s)',
+                        (self._gid, self._addr.localpart, str(self._dest)))
+        else:
+            dbc.execute('UPDATE relocated SET destination = %s WHERE gid = %s '
+                        'AND address = %s',
+                        (str(self._dest), self._gid, self._addr.localpart))
+        self._dbh.commit()
+        dbc.close()
+
+    def get_info(self):
+        """Returns the address to which mails should be sent."""
+        if not self._dest:
+            raise RErr(_(u"The relocated user '%s' 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.") %
+                       self._addr, NO_SUCH_RELOCATED)
+        dbc = self._dbh.cursor()
+        dbc.execute('DELETE FROM relocated WHERE gid = %s AND address = %s',
+                    (self._gid, self._addr.localpart))
+        if dbc.rowcount > 0:
+            self._dbh.commit()
+        dbc.close()
+        self._dest = None
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/serviceset.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,168 @@
+# coding: utf-8
+# Copyright (c) 2011 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.serviceset
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's ServiceSet class for simplified database access
+    to the service_set table.
+"""
+
+SERVICES = ('smtp', 'pop3', 'imap', 'sieve')
+
+cfg_dget = lambda option: None
+
+
+class ServiceSet(object):
+    """A wrapper class that provides access to the service_set table.
+
+    Each ServiceSet object provides following - read only - attributes:
+
+    `ssid` : long
+      The id of the service set
+    `smtp` : bool
+      Boolean flag for service smtp
+    `pop3` : bool
+      Boolean flag for service pop3
+    `imap` : bool
+      Boolean flag for service imap
+    `sieve` : bool
+      Boolean flag for service sieve
+    `services` : dict
+      The four services above with boolean values
+    """
+    __slots__ = ('_ssid', '_services', '_sieve_col', '_dbh')
+    _kwargs = (('ssid',) + SERVICES)
+
+    def __init__(self, dbh, **kwargs):
+        """Creates a new ServiceSet instance.
+
+        Either the 'ssid' keyword argument or one or more of the service
+        arguments ('smtp', 'pop3',  'imap', 'sieve') must be provided.
+
+        Arguments:
+        `dbh` : pyPgSQL.PgSQL.Connection or psycopg2.extensions.connection
+          A database connection for the database access.
+
+        Keyword arguments:
+        `ssid` : int
+          The id of the service set (>0)
+        `smtp` : bool
+          Boolean flag for service smtp - default `True`
+        `pop3` : bool
+          Boolean flag for service pop3 - default `True`
+        `imap` : bool
+          Boolean flag for service imap - default `True`
+        `sieve` : bool
+          Boolean flag for service sieve - default `True`
+        """
+        self._dbh = dbh
+        self._ssid = 0
+        self._services = dict.fromkeys(SERVICES, True)
+        if cfg_dget('misc.dovecot_version') < 0x10200b02:
+            self._sieve_col = 'managesieve'
+        else:
+            self._sieve_col = 'sieve'
+
+        for key in kwargs.iterkeys():
+            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
+                self._load_by_ssid(kwargs[key])
+                break
+            else:
+                assert isinstance(kwargs[key], bool)
+                if not kwargs[key]:
+                    self._services[key] = kwargs[key]
+        if not self._ssid:
+            self._load_by_services()
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._ssid == other._ssid
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._ssid != other._ssid
+        return NotImplemented
+
+    def __getattr__(self, name):
+        if name not in self.__class__._kwargs:
+            raise AttributeError('%r object has no attribute %r' % (
+                                 self.__class__.__name__, name))
+        if name == 'ssid':
+            return self._ssid
+        else:
+            return self._services[name]
+
+    def __repr__(self):
+        return '%s(%s, %s)' % (self.__class__.__name__, self._dbh,
+                  ', '.join('%s=%r' % s for s in self._services.iteritems()))
+
+    def _load_by_services(self):
+        """Try to load the service_set by it's service combination."""
+        sql = ('SELECT ssid FROM service_set WHERE %s' %
+               ' AND '.join('%s = %s' %
+               (k, str(v).upper()) for k, v in self._services.iteritems()))
+        if self._sieve_col == 'managesieve':
+            sql.replace('sieve', self._sieve_col)
+        dbc = self._dbh.cursor()
+        dbc.execute(sql)
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._ssid = result[0]
+        else:
+            self._save()
+
+    def _load_by_ssid(self, ssid):
+        """Try to load the service_set by it's primary key."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT ssid, smtp, pop3, imap, %s' % (self._sieve_col,) +
+                    ' FROM service_set WHERE ssid = %s', (ssid,))
+        result = dbc.fetchone()
+        dbc.close()
+        if not result:
+            raise ValueError('Unknown service_set id specified: %r' % ssid)
+        self._ssid = result[0]
+        #self._services.update(zip(SERVICES, result[1:]))
+        for key, value in zip(SERVICES, result[1:]):  # pyPgSQL compatible
+            if value:
+                self._services[key] = True
+            else:
+                self._services[key] = False
+
+    def _save(self):
+        """Store a new service_set in the database."""
+        sql = ('INSERT INTO service_set (ssid, smtp, pop3, imap, %s) ' %
+               (self._sieve_col,) +
+               'VALUES (%(ssid)s, %(smtp)s, %(pop3)s, %(imap)s, %(sieve)s)')
+        if self._sieve_col == 'managesieve':
+            sql.replace('sieve', self._sieve_col)
+        self._set_ssid()
+        values = {'ssid': self._ssid}
+        values.update(self._services)
+        dbc = self._dbh.cursor()
+        dbc.execute(sql, values)
+        self._dbh.commit()
+        dbc.close()
+
+    def _set_ssid(self):
+        """Set the unique ID for the new service_set."""
+        assert self._ssid == 0
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('service_set_id')")
+        self._ssid = dbc.fetchone()[0]
+        dbc.close()
+
+    @property
+    def services(self):
+        """A dictionary: Keys: `smtp`, `pop3`, `imap` and `sieve` with
+        boolean values."""
+        return self._services.copy()
+
+del cfg_dget
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/transport.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,100 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.transport
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's Transport class to manage the transport for
+    domains and accounts.
+"""
+
+from VirtualMailManager.pycompat import any
+
+_ = lambda msg: msg
+
+
+class Transport(object):
+    """A wrapper class that provides access to the transport table"""
+    __slots__ = ('_tid', '_transport', '_dbh')
+
+    def __init__(self, dbh, tid=None, transport=None):
+        """Creates a new Transport instance.
+
+        Either tid or transport must be specified. When both arguments
+        are given, tid will be used.
+
+        Keyword arguments:
+        dbh -- a pyPgSQL.PgSQL.connection
+        tid -- the id of a transport (int/long)
+        transport -- the value of the transport (str)
+
+        """
+        self._dbh = dbh
+        self._tid = 0
+        assert any((tid, transport))
+        if tid:
+            assert not isinstance(tid, bool) and isinstance(tid, (int, long))
+            self._load_by_id(tid)
+        else:
+            assert isinstance(transport, basestring)
+            self._transport = transport
+            self._load_by_name()
+
+    @property
+    def tid(self):
+        """The transport's unique ID."""
+        return self._tid
+
+    @property
+    def transport(self):
+        """The transport's value, ex: 'dovecot:'"""
+        return self._transport
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._tid == other._tid
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._tid != other._tid
+        return NotImplemented
+
+    def __str__(self):
+        return self._transport
+
+    def _load_by_id(self, tid):
+        """load a transport by its id from the database"""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT transport FROM transport WHERE tid = %s', (tid,))
+        result = dbc.fetchone()
+        dbc.close()
+        if not result:
+            raise ValueError('Unknown transport id specified: %r' % tid)
+        self._transport = result[0]
+        self._tid = tid
+
+    def _load_by_name(self):
+        """Load a transport by its transport name from the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute('SELECT tid FROM transport WHERE transport = %s',
+                    (self._transport,))
+        result = dbc.fetchone()
+        dbc.close()
+        if result:
+            self._tid = result[0]
+        else:
+            self._save()
+
+    def _save(self):
+        """Save the new transport in the database."""
+        dbc = self._dbh.cursor()
+        dbc.execute("SELECT nextval('transport_id')")
+        self._tid = dbc.fetchone()[0]
+        dbc.execute('INSERT INTO transport (tid, transport) VALUES (%s, %s)',
+                    (self._tid, self._transport))
+        self._dbh.commit()
+        dbc.close()
+
+del _
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/Makefile	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/vmm.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/vmm.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/conf.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+#
+# vmm documentation build configuration file, created by
+# sphinx-quickstart on Sun Feb 14 00:08:08 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.intersphinx', 'sphinx.ext.todo']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'vmm'
+copyright = u'2010, Pascal Volk'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.6'
+# The full version, including alpha/beta/rc tags.
+release = '0.6.x'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+#html_theme = 'sphinxdoc'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'vmmdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'vmm.tex', u'vmm Documentation',
+   u'Pascal Volk', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+todo_include_todos = True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/index.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,29 @@
+======================
+VirtualMailManager API
+======================
+
+:Author:  Pascal Volk <neverseen@users.sourceforge.net>
+:Date:    |today|
+:Release: |version|
+
+Contents:
+
+.. toctree::
+   :maxdepth: 1
+   :numbered:
+
+   vmm.rst
+   vmm_config.rst
+   vmm_emailaddress.rst
+   vmm_alias.rst
+   vmm_relocated.rst
+   vmm_errors.rst
+   vmm_constants_error.rst
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,132 @@
+:mod:`VirtualMailManager` ---  Initialization code and some functions
+=====================================================================
+
+.. module:: VirtualMailManager
+  :synopsis: Initialization code and some functions
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+When the VirtualMailManager module, or one of its sub modules, is imported,
+the following actions will be performed:
+
+  - :func:`locale.setlocale` (with :const:`locale.LC_ALL`) is called, to set
+    :const:`ENCODING`
+  - :func:`gettext.install` is called, to have 18N support.
+
+Constants and data
+------------------
+
+.. data:: ENCODING
+
+  The systems current character encoding, e.g. ``'UTF-8'`` or
+  ``'ANSI_X3.4-1968'`` (aka ASCII).
+
+
+Functions
+---------
+
+.. function:: ace2idna(domainname)
+
+  Converts the idn domain name *domainname* into punycode.
+
+  :param domainname: the domain-ace representation (``xn--…``)
+  :type domainname: str
+  :rtype: unicode
+
+.. function:: check_domainname(domainname)
+
+  Returns the validated domain name *domainname*.
+
+  It also converts the name of the domain from IDN to ASCII, if necessary.
+
+  :param domainname: the name of the domain
+  :type domainname: :obj:`basestring`
+  :rtype: str
+  :raise VirtualMailManager.errors.VMMError: if the domain name is
+    too long or doesn't look like a valid domain name (label.label.label).
+
+.. function:: check_localpart(localpart)
+
+  Returns the validated local-part *localpart* of an e-mail address.
+
+  :param localpart: The local-part of an e-mail address.
+  :type localpart: str
+  :rtype: str
+  :raise VirtualMailManager.errors.VMMError: if the local-part is too
+    long or contains invalid characters.
+
+.. function:: exec_ok(binary)
+
+  Checks if the *binary* exists and if it is executable.
+
+  :param binary: path to the binary
+  :type binary: str
+  :rtype: str
+  :raise VirtualMailManager.errors.VMMError: if *binary* isn't a file
+    or is not executable.
+
+.. function:: expand_path(path)
+
+  Expands paths, starting with ``.`` or ``~``, to an absolute path.
+
+  :param path: Path to a file or directory
+  :type path: str
+  :rtype: str
+
+.. function:: get_unicode(string)
+
+  Converts `string` to `unicode`, if necessary.
+
+  :param string: The string taht should be converted
+  :type string: str
+  :rtype: unicode
+
+.. function:: idn2ascii(domainname)
+
+  Converts the idn domain name *domainname* into punycode.
+
+  :param domainname: the unicode representation of the domain name
+  :type domainname: unicode
+  :rtype: str
+
+.. function:: is_dir(path)
+
+  Checks if *path* is a directory.
+
+  :param path: Path to a directory
+  :type path: str
+  :rtype: str
+  :raise VirtualMailManager.errors.VMMError: if *path* is not a directory.
+
+
+Examples
+--------
+
+  >>> from VirtualMailManager import *
+  >>> ace2idna('xn--pypal-4ve.tld')
+  u'p\u0430ypal.tld'
+  >>> idn2ascii(u'öko.de')
+  'xn--ko-eka.de'
+  >>> check_domainname(u'pаypal.tld')
+  'xn--pypal-4ve.tld'
+  >>> check_localpart('john.doe')
+  'john.doe'
+  >>> exec_ok('usr/bin/vim')
+  Traceback (most recent call last):
+    File "<stdin>", line 1, in <module>
+    File "./VirtualMailManager/__init__.py", line 93, in exec_ok
+      NO_SUCH_BINARY)
+  VirtualMailManager.errors.VMMError: 'usr/bin/vim' is not a file
+  >>> exec_ok('/usr/bin/vim')
+  '/usr/bin/vim'
+  >>> expand_path('.')
+  '/home/user/hg/vmm'
+  >>> get_unicode('hello world')
+  u'hello world'
+  >>> is_dir('~/hg')
+  '/home/user/hg'
+  >>> 
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_alias.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,87 @@
+:mod:`VirtualMailManager.Alias` --- Handling of alias e-mail addresses
+======================================================================
+
+.. module:: VirtualMailManager.Alias
+  :synopsis: Handling of alias e-mail addresses
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+
+This module provides the :class:`Alias` class. The data are read from/stored
+in the ``alias`` table. This table is used by Postfix to rewrite recipient
+addresses.
+
+
+Alias
+---------
+.. class:: Alias(dbh, address)
+  
+  Creates a new *Alias* instance. Alias instances provides the :func:`__len__`
+  method. So the existence of an alias in the database can be tested with a
+  simple if condition.
+  
+  :param dbh: a database connection
+  :type dbh: :class:`pyPgSQL.PgSQL.Connection`
+  :param address: the alias e-mail address.
+  :type address: :class:`VirtualMailManager.EmailAddress.EmailAddress`
+
+  .. method:: add_destinations(destinations, expansion_limit [, warnings=None])
+
+    Adds the *destinations* to the destinations of the alias. This method
+    returns a ``set`` of all addresses which successfully were stored into the
+    database.
+
+    If one of the e-mail addresses in *destinations* is the same as the alias
+    address, it will be silently discarded. Destination addresses, that are
+    already assigned to the alias, will be also ignored.
+
+    When the optional *warnings* list is given, all ignored addresses will be
+    appended to it.
+
+    :param destinations: The destination addresses of the alias
+    :type destinations: :obj:`list` of
+      :class:`VirtualMailManager.EmailAddress.EmailAddress` instances
+    :param expansion_limit: The maximal number of destinations (see also:
+      `virtual_alias_expansion_limit
+      <http://www.postfix.org/postconf.5.html#virtual_alias_expansion_limit>`_)
+    :type expansion_limit: :obj:`int`
+    :param warnings: A optional list, to record all ignored addresses
+    :type warnings: :obj:`list`
+    :rtype: :obj:`set`
+    :raise VirtualMailManager.errors.AliasError: if the additional
+      *destinations* will exceed the *expansion_limit* or if the alias
+      already exceeds its *expansion_limit*.
+
+    .. seealso:: :mod:`VirtualMailManager.ext.postconf` -- to read actual
+      values of Postfix configuration parameters.
+
+
+  .. method:: del_destination(destination)
+
+    Deletes the given *destination* address from the alias.
+
+    :param destination: a destination address of the alias
+    :type destination: :class:`VirtualMailManager.EmailAddress.EmailAddress`
+    :rtype: :obj:`None`
+    :raise VirtualMailManager.errors.AliasError: if the destination wasn't
+      assigned to the alias or the alias doesn't exist.
+
+
+  .. method:: delete()
+    
+    Deletes the alias with all its destinations.
+
+    :rtype: :obj:`None`
+    :raise VirtualMailManager.errors.AliasError: if the alias doesn't exist.
+
+
+  .. method:: get_destinations()
+
+    Returns an iterator for all destinations (``EmailAddress`` instances) of
+    the alias.
+
+    :rtype: :obj:`listiterator`
+    :raise VirtualMailManager.errors.AliasError: if the alias doesn't exist.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_config.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,275 @@
+:mod:`VirtualMailManager.Config` ---  Simplified configuration access
+======================================================================
+
+.. module:: VirtualMailManager.Config
+  :synopsis: Simplified configuration access
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+
+This module provides a few classes for simplified configuration handling
+and the validation of the setting's  *type* and *value*.
+
+:class:`LazyConfig` is derived from Python's
+:class:`ConfigParser.RawConfigParser`. It doesn't use ``RawConfigParser``'s
+``DEFAULT`` section. All settings and their defaults, if supposed, are
+handled by :class:`LazyConfigOption` objects in the :attr:`LazyConfig._cfg`
+*dict*.
+
+``LazyConfig``'s setters and getters for options are taking a single string
+for the *section* and *option* argument, e.g. ``config.pget('database.user')``
+instead of ``config.get('database', 'user')``.
+
+
+
+LazyConfig
+----------
+.. class:: LazyConfig
+
+  Bases: :class:`ConfigParser.RawConfigParser`
+
+  .. versionadded:: 0.6.0
+
+  .. attribute:: _cfg
+
+    a multi dimensional :class:`dict`, containing *sections* and *options*,
+    represented by :class:`LazyConfigOption` objects.
+
+    For example::
+
+      from VirtualMailManager.Config import LazyConfig, LazyConfigOption
+
+      class FooConfig(LazyConfig):
+          def __init__(self, ...):
+              LazyConfig.__init__(self)
+              ...
+              LCO = LazyConfigOption
+              self._cfg = {
+                  'database': {# section database:
+                      'host': LCO(str, '::1', self.get), # options of the
+                      'name': LCO(str, 'dbx', self.get), # database section.
+                      'pass': LCO(str, None, self.get),  # No defaults for the
+                      'user': LCO(str, None, self.get),  # user and pass options
+                  }
+              }
+
+          ...
+
+
+  .. method:: bool_new(value)
+
+    Converts the string *value* into a `bool` and returns it.
+
+    | ``'1'``, ``'on'``, ``'yes'`` and ``'true'`` will become :const:`True`
+    | ``'0'``, ``'off'``, ``'no'`` and ``'false'`` will become :const:`False`
+
+    :param value: one of the above mentioned strings
+    :type value: :obj:`basestring`
+    :rtype: bool
+    :raise ConfigValueError: for all other values, except ``bool``\ s
+
+  .. method:: dget(option)
+
+    Like :meth:`pget`, but returns the *option*'s default value, from
+    :attr:`_cfg` (defined by :attr:`LazyConfigOption.default`) if the *option*
+    is not configured in a ini-like configuration file.
+
+    :param option: the section.option combination
+    :type option: :obj:`basestring`
+    :raise NoDefaultError: if the *option* couldn't be found in the
+      configuration file and no default value was passed to
+      :class:`LazyConfigOption`'s constructor for the requested *option*.
+
+  .. method:: getboolean(section, option)
+
+    Returns the boolean value of the *option*, in the given *section*.
+
+    For a boolean :const:`True`, the value must be set to ``'1'``, ``'on'``,
+    ``'yes'``, ``'true'`` or :const:`True`. For a boolean :const:`False`, the
+    value must set to ``'0'``, ``'off'``, ``'no'``, ``'false'`` or
+    :const:`False`.
+
+    :param section: The section's name
+    :type section: :obj:`basestring`
+    :param option: The option's name
+    :type option: :obj:`basestring`
+    :rtype: bool
+    :raise ValueError: if the option has an other value than the values
+      mentioned above.
+
+  .. method:: has_option(option)
+
+    Checks if the *option* (section\ **.**\ option) is a known configuration
+    option.
+
+    :param option: The option's name
+    :type option: :obj:`basestring`
+    :rtype: bool
+
+  .. method:: has_section(section)
+
+    Checks if *section* is a known configuration section.
+
+    :param section: The section's name
+    :type section: :obj:`basestring`
+    :rtype: bool
+
+  .. method:: items(section)
+
+    Returns an iterator for ``key, value`` :obj:`tuple`\ s for each option in
+    the given *section*.
+
+    :param section: The section's name
+    :type section: :obj:`basestring`
+    :raise NoSectionError: if the given *section* is not known.
+
+  .. method:: pget(option)
+
+    Polymorphic getter which returns the *option*'s value (by calling
+    :attr:`LazyConfigOption.getter`) with the appropriate type, defined by
+    :attr:`LazyConfigOption.cls`.
+
+    :param option: the section.option combination
+    :type option: :obj:`basestring`
+
+  .. method:: sections()
+
+    Returns an iterator object for all configuration sections from the
+    :attr:`_cfg` dictionary.
+
+    :rtype: :obj:`dictionary-keyiterator`
+
+  .. method:: set(option, value)
+
+    Like :meth:`ConfigParser.RawConfigParser.set`, but converts the *option*'s
+    new *value* (by calling :attr:`LazyConfigOption.cls`) to the appropriate
+    type/class. When the ``LazyConfigOption``'s optional parameter *validate*
+    was not :const:`None`, the new *value* will be also validated.
+
+    :param option: the section.option combination
+    :type option: :obj:`basestring`
+    :param value: the new value to be set
+    :type value: :obj:`basestring`
+    :rtype: :const:`None`
+    :raise ConfigValueError: if a boolean value shout be set (:meth:`bool_new`)
+      and it fails
+    :raise ValueError: if an other setter (:attr:`LazyConfigOption.cls`) or
+      validator (:attr:`LazyConfigOption.validate`) fails.
+    :raise VirtualMailManager.errors.VMMError: if
+      :attr:`LazyConfigOption.validate` is set to
+      :func:`VirtualMailManager.exec_ok` or :func:`VirtualMailManager.is_dir`.
+
+
+LazyConfigOption
+----------------
+LazyConfigOption instances are required by :class:`LazyConfig` instances, and
+instances of classes derived from `LazyConfig`, like the :class:`Config`
+class.
+
+.. class:: LazyConfigOption (cls, default, getter[, validate=None])
+
+  .. versionadded:: 0.6.0
+
+  The constructor's parameters are:
+
+  ``cls`` : :obj:`type`
+    The class/type of the option's value.
+  ``default`` : :obj:`str` or the one defined by ``cls``
+    Default value of the option. Use :const:`None` if the option shouldn't
+    have a default value.
+  ``getter``: :obj:`callable`
+    A method's name of :class:`ConfigParser.RawConfigParser` and derived
+    classes, to get a option's value, e.g. `self.getint`.
+  ``validate`` : :obj:`callable` or :const:`None`
+    :const:`None` or any function, which takes one argument and returns the
+    validated argument with the appropriate type (for example:
+    :meth:`LazyConfig.bool_new`). The function should raise a 
+    :exc:`ConfigValueError` if the validation fails. This function checks the
+    new value when :meth:`LazyConfig.set()` is called.
+
+  Each LazyConfigOption object has the following read-only attributes:
+
+  .. attribute:: cls
+
+    The class of the option's value e.g. `str`, `unicode` or `bool`. Used as
+    setter method when :meth:`LazyConfig.set` (or the ``set()`` method of a
+    derived class) is called.
+
+  .. attribute:: default
+
+    The option's default value, may be ``None``
+
+  .. attribute:: getter
+
+    A method's name of :class:`ConfigParser.RawConfigParser` and derived
+    classes, to get a option's value, e.g. ``self.getint``.
+
+  .. attribute:: validate
+
+    A method or function to validate the option's new value.
+
+
+Config
+------
+The final configuration class of the virtual mail manager.
+
+.. class:: Config (filename)
+
+  Bases: :class:`LazyConfig`
+
+  :param filename: absolute path to the configuration file.
+  :type filename: :obj:`basestring`
+
+  .. attribute:: _cfg
+
+    The configuration ``dict``, containing all configuration sections and
+    options, as described in :attr:`LazyConfig._cfg`.
+
+  .. method:: check()
+
+    Checks all section's options for settings w/o a default value.
+
+    :raise VirtualMailManager.errors.ConfigError: if the check fails
+
+  .. method:: load()
+
+    Loads the configuration read-only.
+
+    :raise VirtualMailManager.errors.ConfigError: if the
+      configuration syntax is invalid
+
+  .. method:: unicode(section, option)
+
+    Returns the value of the *option* from *section*, converted to Unicode.
+    This method is intended for the :attr:`LazyConfigOption.getter`.
+
+    :param section: The name of the configuration section
+    :type section: :obj:`basestring`
+    :param option: The name of the configuration option
+    :type option: :obj:`basestring`
+    :rtype: :obj:`unicode`
+
+
+Exceptions
+----------
+
+.. exception:: BadOptionError(msg)
+
+  Bases: :exc:`ConfigParser.Error`
+
+  Raised when a option isn't in the format 'section.option'.
+
+.. exception:: ConfigValueError(msg)
+
+  Bases: :exc:`ConfigParser.Error`
+
+  Raised when creating or validating of new values fails.
+
+.. exception:: NoDefaultError(section, option)
+
+  Bases: :exc:`ConfigParser.Error`
+
+  Raised when the requested option has no default value.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_constants_error.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,227 @@
+:mod:`VirtualMailManager.constants.ERROR` --- Error codes
+=========================================================
+
+.. module:: VirtualMailManager.constants.ERROR
+  :synopsis: VirtualMailManager's error codes
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+Error codes, used by all :mod:`VirtualMailManager.errors`.
+
+.. data:: ACCOUNT_AND_ALIAS_PRESENT
+
+  Can't delete the  Domain - there are accounts and aliases assigned
+
+.. data:: ACCOUNT_EXISTS
+
+  The Account exists already
+
+.. data:: ACCOUNT_PRESENT
+
+  Can't delete the Domain - there are accounts
+
+.. data:: ALIASDOMAIN_EXISTS
+
+  Can't save/switch the destination of the AliasDomain - old and new destination
+  are the same.
+
+.. data:: ALIASDOMAIN_ISDOMAIN
+
+  Can't create AliasDomain - there is already a Domain with the given name
+
+  .. todo:: Move the related check to the Handler class
+
+.. data:: ALIASDOMAIN_NO_DOMDEST
+
+  Can't save/switch the destination of an AliasDomain if the destination was
+  omitted 
+
+.. data:: ALIAS_ADDR_DEST_IDENTICAL
+
+  The alias address and its destination are the same
+
+  obsolete?
+
+.. data:: ALIAS_EXCEEDS_EXPANSION_LIMIT
+
+  The Alias has reached or exceeds its expansion limit
+
+.. data:: ALIAS_EXISTS
+
+  Alias with the given destination exists already
+
+  obsolete?
+
+.. data:: ALIAS_MISSING_DEST
+
+  obsolete?
+
+.. data:: ALIAS_PRESENT
+
+  Can't delete Domain or Account - there are aliases assigned
+
+.. data:: CONF_ERROR
+
+  Syntax error in the configuration file or missing settings w/o a default value
+
+.. data:: CONF_NOFILE
+
+  The configuration file couldn't be found
+
+.. data:: CONF_NOPERM
+
+  The user's permissions are insufficient
+
+.. data:: CONF_WRONGPERM
+
+  Configuration file has the wrong access mode
+
+.. data:: DATABASE_ERROR
+
+  A database error occurred
+
+.. data:: DOMAINDIR_GROUP_MISMATCH
+
+  Domain directory is owned by the wrong group
+
+.. data:: DOMAIN_ALIAS_EXISTS
+
+  Can't create Domain - there is already an AliasDomain with the same name
+
+  .. todo:: Move the related check to the Handler class
+
+.. data:: DOMAIN_EXISTS
+
+  The Domain is already available in the database
+
+.. data:: DOMAIN_INVALID
+
+  The domain name is invalid
+
+.. data:: DOMAIN_NO_NAME
+
+  Missing the domain name
+
+.. data:: DOMAIN_TOO_LONG
+
+  The length of domain is > 255
+
+.. data:: FOUND_DOTS_IN_PATH
+
+  Can't delete directory with ``.`` or ``..`` in path
+
+  .. todo:: check if we can solve this issue with expand_path()
+
+.. data:: INVALID_ADDRESS
+
+  The specified value doesn't look like a e-mail address
+
+.. data:: INVALID_AGUMENT
+
+  The given argument is invalid
+
+.. data:: INVALID_OPTION
+
+  The given option is invalid
+
+.. data:: INVALID_SECTION
+
+  The section is not a known configuration section
+
+.. data:: LOCALPART_INVALID
+
+  The local-part of an e-mail address was omitted or is invalid
+
+.. data:: LOCALPART_TOO_LONG
+
+  The local-part (w/o a extension) is too long (> 64)
+
+.. data:: MAILDIR_PERM_MISMATCH
+
+  The Maildir is owned by the wrong user/group
+
+.. data:: MAILLOCATION_INIT
+
+  Can't create a new MailLocation instance
+
+  obsolete?
+
+.. data:: NOT_EXECUTABLE
+
+  The binary is not executable
+
+.. data:: NO_SUCH_ACCOUNT
+
+  No Account with the given e-mail address
+
+.. data:: NO_SUCH_ALIAS
+
+  No Alias with the given e-mail address
+
+.. data:: NO_SUCH_ALIASDOMAIN
+
+  The given domain is not an AliasDomain
+
+.. data:: NO_SUCH_BINARY
+
+  Can't find the file at the specified location
+
+.. data:: NO_SUCH_DIRECTORY
+
+  There is no directory with the given path
+
+.. data:: NO_SUCH_DOMAIN
+
+  No Domain with the given name
+
+.. data:: NO_SUCH_RELOCATED
+
+  There is no Relocated user with the given e-mail address
+
+.. data:: RELOCATED_ADDR_DEST_IDENTICAL
+
+  The e-mail address of the Relocated user an its destination are the same
+
+.. data:: RELOCATED_EXISTS
+
+  Can't create Account or Alias, there is already a Relocated user with the
+  given e-mail address
+
+.. data:: RELOCATED_MISSING_DEST
+
+  obsolete?
+
+.. data:: TRANSPORT_INIT
+
+  Can't initialize a new Transport instance
+
+  obsolete?
+
+.. data:: UNKNOWN_MAILLOCATION_ID
+
+  There is no MailLocation entry with the given ID
+
+  obsolete?
+
+.. data:: UNKNOWN_SERVICE
+
+  The specified service is unknown
+
+.. data:: UNKNOWN_TRANSPORT_ID
+
+  There is no Transport entry with the given ID
+
+.. data:: UNKNOWN_MAILLOCATION_NAME
+
+  The given mail_location directory couldn't be accepted
+
+.. data:: VMM_ERROR
+
+  Internal error
+
+.. data:: VMM_TOO_MANY_FAILURES
+
+  Too many errors in interactive mode
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_emailaddress.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,61 @@
+:mod:`VirtualMailManager.EmailAddress` --- Handling of e-mail addresses
+=======================================================================
+
+.. module:: VirtualMailManager.EmailAddress
+  :synopsis: Handling of e-mail addresses
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+
+This module provides the :class:`EmailAddress` class to handle validated e-mail
+addresses.
+
+
+EmailAddress
+------------
+
+.. class:: EmailAddress(address)
+
+  Creates a new EmailAddress instance.
+
+  :param address: string representation of an e-mail addresses
+  :type address: :obj:`basestring`
+  :raise VirtualMailManager.errors.EmailAddressError: if the
+    *address* is syntactically wrong.
+  :raise VirtualMailManager.errors.VMMError: if the validation of the
+    local-part or domain name fails.
+
+  An EmailAddress instance has the both read-only attributes:
+
+  .. attribute:: localpart
+
+    The local-part of the address *local-part@domain*
+
+
+  .. attribute:: domainname
+
+    The domain part of the address *local-part@domain*
+
+
+Examples
+--------
+
+  >>> from VirtualMailManager.EmailAddress import EmailAddress
+  >>> john = EmailAddress('john.doe@example.com')
+  >>> john.localpart
+  'john.doe'
+  >>> john.domainname
+  'example.com'
+  >>> jane = EmailAddress('jane.doe@example.com')
+  >>> jane != john
+  True
+  >>> EmailAddress('info@xn--pypal-4ve.tld') == EmailAddress(u'info@pаypal.tld')
+  True
+  >>> jane
+  EmailAddress('jane.doe@example.com')
+  >>> print john
+  john.doe@example.com
+  >>> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_errors.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,122 @@
+:mod:`VirtualMailManager.errors` --- Exception classes
+======================================================
+
+.. module:: VirtualMailManager.errors
+  :synopsis: Exception classes
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+Exceptions, used by VirtualMailManager's classes.
+
+
+Exceptions
+----------
+
+.. exception:: VMMError(msg, code)
+
+  Bases: :exc:`exceptions.Exception`
+
+  :param msg: the error message
+  :type msg: :obj:`basestring`
+  :param code: the error code (one of :mod:`VirtualMailManager.constants.ERROR`)
+  :type code: :obj:`int`
+
+  Base class for all other Exceptions in the VirtualMailManager package.
+
+  The *msg* and *code* are accessible via the both attributes:
+
+  .. attribute:: msg
+
+    The error message of the exception.
+
+
+  .. attribute:: code
+
+    The numerical error code of the exception.
+
+
+.. exception:: ConfigError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for configuration (:mod:`VirtualMailManager.Config`)
+  exceptions.
+
+
+.. exception:: PermissionError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for file permission exceptions.
+
+
+.. exception:: NotRootError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for non-root exceptions.
+
+
+.. exception:: DomainError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for Domain (:mod:`VirtualMailManager.Domain`) exceptions.
+
+
+.. exception:: AliasDomainError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for AliasDomain (:mod:`VirtualMailManager.AliasDomain`)
+  exceptions.
+
+
+.. exception:: AccountError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for Account (:mod:`VirtualMailManager.Account`) exceptions.
+
+
+.. exception:: AliasError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for Alias (:mod:`VirtualMailManager.Alias`) exceptions.
+
+
+.. exception:: EmailAddressError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for EmailAddress (:mod:`VirtualMailManager.EmailAddress`)
+  exceptions.
+
+
+.. exception:: MailLocationError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for MailLocation (:mod:`VirtualMailManager.MailLocation`)
+  exceptions.
+
+
+.. exception:: RelocatedError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for Relocated (:mod:`VirtualMailManager.Relocated`)
+  exceptions.
+
+
+.. exception:: TransportError(msg, code)
+
+  Bases: :exc:`VirtualMailManager.errors.VMMError`
+
+  Exception class for Transport (:mod:`VirtualMailManager.Transport`)
+  exceptions.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/source/vmm_relocated.rst	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,60 @@
+:mod:`VirtualMailManager.Relocated` --- Handling of relocated users
+===================================================================
+
+.. module:: VirtualMailManager.Relocated
+  :synopsis: Handling of relocated users
+
+.. moduleauthor:: Pascal Volk <neverseen@users.sourceforge.net>
+
+.. toctree::
+   :maxdepth: 2
+
+
+This module provides the :class:`Relocated` class. The data are read
+from/stored in the ``relocated`` table. An optional lookup table, used
+by Postfix for the "``user has moved to new_location``" reject/bounce message.
+
+
+Relocated
+---------
+.. class:: Relocated(dbh, address)
+
+  Creates a new *Relocated* instance. If the relocated user with the given
+  *address* is already stored in the database use :meth:`get_info` to get the
+  destination address of the relocated user. To set or update the destination
+  of the relocated user use :meth:`set_destination`. Use :meth:`delete` in
+  order to delete the relocated user from the database.
+  
+  :param dbh: a database connection
+  :type dbh: :class:`pyPgSQL.PgSQL.Connection`
+  :param address: the e-mail address of the relocated user.
+  :type address: :class:`VirtualMailManager.EmailAddress.EmailAddress`
+
+
+  .. method:: delete()
+  
+    :rtype: :obj:`None`
+    :raise VirtualMailManager.errors.RelocatedError: if the relocated user
+      doesn't exist.
+
+    Deletes the relocated user from the database.
+
+
+  .. method:: get_info()
+
+    :rtype: :class:`VirtualMailManager.EmailAddress.EmailAddress`
+    :raise VirtualMailManager.errors.RelocatedError: if the relocated user
+      doesn't exist.
+
+    Returns the destination e-mail address of the relocated user.
+
+
+  .. method:: set_destination(destination)
+
+    :param destination: the new address where the relocated user has moved to
+    :type destination: :class:`VirtualMailManager.EmailAddress.EmailAddress`
+    :rtype: :obj:`None`
+    :raise VirtualMailManager.errors.RelocatedError: if the *destination*
+      address is already saved or is the same as the relocated user's address.
+
+    Sets or updates the *destination* address of the relocated user.
--- a/install.sh	Mon Nov 07 03:22:15 2011 +0000
+++ b/install.sh	Thu Jun 28 19:26:50 2012 +0000
@@ -16,7 +16,7 @@
 else
     MANDIR=${PREFIX}/man
 fi
-DOCS="ChangeLog COPYING INSTALL README"
+DOCS="ChangeLog Configure.Dovecot_2 COPYING INSTALL NEWS README"
 
 INSTALL_OPTS="-g 0 -o 0 -p"
 INSTALL_OPTS_CF="-b -m 0640 -g ${PF_GID} -o 0 -p"
@@ -26,7 +26,7 @@
     exit 1
 fi
 
-python setup.py -q install --prefix ${PREFIX}
+python setup.py -q install --force --prefix ${PREFIX}
 python setup.py clean --all >/dev/null
 
 install -b -m 0600 ${INSTALL_OPTS} vmm.cfg ${PREFIX}/etc/
@@ -50,7 +50,7 @@
 [ -d ${MANDIR}/man5 ] || mkdir -m 0755 -p ${MANDIR}/man5
 install -m 0644 ${INSTALL_OPTS} man5/vmm.cfg.5 ${MANDIR}/man5
 
-for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\? \! -name .svn)
+for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\?)
 do
     for s in man1 man5; do
         [ -d ${MANDIR}/${l}/${s} ] || mkdir -m 0755 -p ${MANDIR}/${l}/${s}
--- a/man/de/man1/vmm.1	Mon Nov 07 03:22:15 2011 +0000
+++ b/man/de/man1/vmm.1	Thu Jun 28 19:26:50 2012 +0000
@@ -1,447 +1,1015 @@
-.TH "VMM" "1" "17 Aug 2009" "Pascal Volk"
+.TH "VMM" "1" "2012-04-15" "vmm 0.6" "vmm"
 .SH NAME
-vmm \- Programm für die Kommandozeile, um E-Mail-Domains, -Konten und -Aliase zu
-verwalten.
-.SH SYNOPSIS
+vmm \- Kommandozeilenprogramm zur Verwaltung von E\-Mail\-Domains/\-Konten
+und \-Aliase.
+.\" -----------------------------------------------------------------------
+.SH ÜBERSICHT
 .B vmm
-\fIUnterbefehl\fP \fIObjekt\fP [ \fIArgumente\fP ]
+.IR Unterbefehl " [" "Argument ..." ]
+.\" -----------------------------------------------------------------------
 .SH BESCHREIBUNG
-\fBvmm\fP (Virtual Mail Manager) ist ein Kommandozeilen-Werkzeug für
-Administratoren/Postmaster zur Verwaltung von Domains, Konten und Aliase. Es
-wurde entwickelt für Dovecot und Postfix mit einem PostgreSQL-Backend.
-.SH UNTERBEFEHLE
-Von jedem Unterbefehl gibt es jeweils eine lange und kurze Variante. Bei beiden
-Formen ist die Groß-/Kleinschreibung zu berücksichtigen.
-.SS ALLGEMEINE UNTERBEFEHLE
+.B vmm
+(a virtual mail manager) ist das einfach zu bedienende
+Kommandozeilenprogramm für Administratoren und Postmaster, zur Verwaltung
+von (Alias\-) Domains, Konten, Alias\-Adressen und sogenannten Relocated
+Users.
+Es ermöglicht die schnelle und einfache Verwaltung des Mailservers.
+.br
+Es wurde für Dovecot und Postfix mit einem PostgreSQL\-Backend entwickelt.
+.PP
+Von jedem
+.I Unterbefehl
+gibt es jeweils eine lange und kurze Variante.
+Die Kurzform ist in Klammern geschrieben.
+Bei beiden Formen ist die Groß\-/Kleinschreibung zu berücksichtigen.
+.PP
+Die meisten
+.IR Unterbefehl e
+erwarten ein oder mehrere
+.IR Argument e.
+.\" -----------------------------------------------------------------------
+.SH ARGUMENTE
+.TP 12
+.I address
+Die komplette E\-Mail\-Adresse
+.RI ( local\-part @ fqdn )
+eines Kontos, Aliases oder Relocated Users.
+.\" --------------------------
 .TP
-\fBconfigure\fP (\fBcf\fP) [ \fIAbschnitt\fP ]
-Startet den interaktiven Konfiguration-Modus für alle Konfigurations-Abschnitte.
-.br
-Wurde das optionale Argument \fIAbschnitt\fP angegeben, werden nur die Optionen
-des angegebenen Abschnitts angezeigt und können geändert werden. Folgende
-Abschnitte sind vorhanden:
-.RS
-.PD 0
+.I destination
+Ist entweder eine E\-Mail\-Adresse, wenn sie in Verbindung mit
+.I "ALIAS UNTERBEFEHLEN"
+verwendet wird, oder ein
+.I fqdn
+in Verbindung mit
+.IR "ALIASDOMAIN UNTERBEFEHLEN" .
+.\" --------------------------
+.TP
+.I fqdn
+Der voll qualifizierten Domain\-Namen \(em ohne den abschließenden Punkt
+\(em einer Domain oder Alias\-Domain.
+.\" --------------------------
 .TP
--
-.B
-database
+.I messages
+Ein Integer\-Wert, der das maximal nutzbare Kontingent als Anzahl von
+Nachrichten festlegt.
+.br
+Der Wert
+.B 0
+(null) bedeutet unbegrenzt \(em kein Quota\-Limit als Anzahl von
+Nachrichten.
+.\" --------------------------
 .TP
--
-.B
-maildir
+.I option
+ist der Name einer Konfigurationsoption mit vorangestellter
+Konfigurations\-Sektion, getrennt durch einen Punkt.
+Zum Beispiel:
+.IB misc . transport
+.br
+Alle Konfigurationsoptionen werden in
+.BR vmm.cfg (5)
+beschrieben.
+.\" --------------------------
 .TP
--
-.B
-services
+.I service
+Der Name eines Services, der gewöhnlicherweise in Verbindung mit Dovecot
+genutzt wird.
+Folgende Services werden unterstützt:
+.BR imap ", " pop3 ", " sieve " und " smtp .
+.\" --------------------------
 .TP
--
-.B
-domdir
+.I storage
+Bestimmt das maximal nutzbare Kontingent in Bytes.
+Eines der folgenden Präfixe kann dem dem ganzzahligen Wert angehängt
+werden:
+.BR b " (Bytes), " k " (Kilobytes), " M " (Megabytes) oder " G
+(Gigabytes).
+.br
+Der Wert
+.B 0
+(null) bedeutet unbegrenzt \(em kein Quota\-Limit in Bytes.
+.\" --------------------------
 .TP
--
-.B
-bin
-.TP
--
-.B
-misc
-.PD
-.RE
-.LP
+.I transport
+ein Transport für Postfix, angegeben in der Form:
+.IB transport :
+oder
+.IB transport :\c
+.IR nexthop .
+Siehe
+.BR transport (5)
+für weitere Details.
+.\" -----------------------------------------------------------------------
+.SH ALLGEMEINE UNTERBEFEHLE
+.SS configget (cg)
+.BI "vmm configget" " option"
+.PP
+Dieser Unterbefehl wird verwendet, um den aktuellen Wert der übergebenen
+.I option
+anzuzeigen.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm configure services\fP
-        Verwende Konfigurationsdatei: /usr/local/etc/vmm.cfg
-
-        * Konfigurations Abschnitt: „services“
-        Neuer Wert für Option pop3 [True]: 
-        Neuer Wert für Option smtp [True]: 
-        Neuer Wert für Option imap [True]: 
-        Neuer Wert für Option sieve [True]: false
+.B vmm configget misc.crypt_sha512_rounds
+misc.crypt_sha512_rounds = 5000
+.fi
+.\" --------------------------
+.SS configset (cs)
+.B vmm configset
+.I option value
+.PP
+Verwenden Sie diesen Unterbefehl, um einer einzelnen Konfigurationsoption
+einen neuen Wert zuzuweisen.
+.I option
+ist der Name der Konfigurationsoption,
+.I value
+ist der Wert, der der Konfigurationsoption zugewiesen wird.
+.IP Hinweis:
+Diese Unterbefehl erstellt eine neue
+.IR vmm.cfg ,
+ohne Kommentare.
+Die aktuelle Konfigurationsdatei wird als
+.IR vmm.cfg.bak
+gesichert.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm configget domain.transport
+domain.transport = dovecot:
+.B vmm configset domain.transport lmtp:unix:private/dovecot\-lmtp
+.B vmm cg domain.transport
+domain.transport = lmtp:unix:private/dovecot\-lmtp
 .fi
+.\" ------------------------------------
+.SS configure (cf)
+.B vmm configure
+.RI [ section ]
 .PP
+Startet die interaktiven Konfiguration für alle Konfigurationssektionen.
+.PP
+Dabei wird der aktuell konfigurierte Wert einer jeden Option in eckigen
+Klammern ausgegeben.
+Sollte kein Wert konfiguriert sein, wird der Vorgabewert der jeweiligen
+Option in in eckigen Klammern angezeigt.
+Um den angezeigten Wert unverändert zu übernehmen, ist dieser mit der
+Eingabe\-Taste zu bestätigen.
+.PP
+Wurde das optionale Argument
+.I section
+angegeben, werden nur die Optionen der angegebenen Sektion angezeigt und
+können geändert werden.
+Folgende Sektionen sind vorhanden:
+.RS
+.TP 10
+.B account
+Konto Einstellungen
 .TP
-\fBgetuser\fP (\fBgu\fP) \fIuserid\fP
-Wenn nur eine UserID vorhanden ist, z. B. aus der Prozessliste, kann mit dem
-Unterbefehl \fBgetuser\fP die E-Mail-Adresse des Users ermittelt werden.
+.B bin
+Pfade zu externen Binär\-Dateien
+.TP
+.B database
+Datenbank Einstellungen
+.TP
+.B domain
+Domain Einstellungen
+.TP
+.B mailbox
+Mailbox Einstellungen
+.TP
+.B misc
+Verschiedene Einstellungen
+.RE
+.PP
+Die Konfigurationsoptionen werden in
+.BR vmm.cfg (5)
+beschrieben.
+.IP Hinweis:
+Diese Unterbefehl erstellt eine neue
+.IR vmm.cfg ,
+ohne Kommentare.
+Die aktuelle Konfigurationsdatei wird als
+.IR vmm.cfg.bak
+gesichert.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
+.B vmm configure mailbox
+Konfigurationsdatei wird verwendet: /root/vmm.cfg
 
-        \fBvmm getuser 70004\fP
-        Account Informationen
-        ---------------------
-                UID............: 70004
-                GID............: 70000
-                Address........: c.user@example.com
+* Konfigurationsabschnitt: »mailbox«
+Neuer Wert für Option folders [Drafts:Sent:Templates:Trash]:
+Neuer Wert für Option format [maildir]: mdbox
+Neuer Wert für Option subscribe [True]:
+Neuer Wert für Option root [Maildir]: mdbox
+.fi
+.\" ------------------------------------
+.SS getuser (gu)
+.BI "vmm getuser" " uid"
+.PP
+Wenn nur der
+.I uid
+eines Benutzers vorhanden ist, zum Beispiel aus der Prozessliste, kann mit
+dem Unterbefehl
+.B getuser
+die E\-Mail\-Adresse des Benutzers ermittelt werden.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm getuser 79876
+Konto Informationen
+-------------------
+        UID............: 79876
+        GID............: 70704
+        Address........: a.user@example.com
 .fi
-.\"
-.TP
-\fBlistdomains\fP (\fBld\fP) [ \fIMuster\fP ]
-Dieser Unterbefehl listet alle verfügbaren Domains auf. Allen Domains wird ein
-Präfix vorangestellt. Entweder ein '[+]', falls es sich um eine primäre Domain
-handelt, oder ein '[-]', falls es sich um eine Alias-Domain handelt.  Die
-Ausgabe kann reduziert werden, indem ein optionales \fIMuster\fP angegeben wird.
+.\" ------------------------------------
+.SS help (h)
+.B vmm help
+.RI [ subcommand ]
+.PP
+Gibt ein Liste aller vorhandenen Unterbefehle mit einer kurzen Beschreibung
+aus.
+Wurde ein
+.I subcommand
+angegeben, wird Hilfe zu diesem Unterbefehl ausgegeben.
+Danach wird
+.B vmm
+beendet.
+.\" ------------------------------------
+.SS listdomains (ld)
+.B vmm listdomains
+.RI [ pattern ]
+.PP
+Dieser Unterbefehl listet alle angelegten Domains auf.
+Allen Domains wird ein Präfix vorangestellt.
+Entweder ein `[+]', falls es sich um eine primäre Domain handelt, oder ein
+`[-]', falls es sich um eine Alias\-Domain handelt.
+Die Ausgabe kann reduziert werden, indem ein optionales Muster angegeben
+wird.
+.PP
+Um eine Wildcard\-Suche durchzuführen kann das %\-Zeichen am Anfang
+und/oder Ende des Musters verwendet werden.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm listdomains %example%
+Übereinstimmende Domains
+------------------------
+        [+] example.com
+        [\-]     e.g.example.com
+        [\-]     example.name
+        [+] example.org
+        [+] sales.example.com
+.fi
+.\" ------------------------------------
+.SS listpwschemes (lp)
+.B vmm listpwschemes
+.PP
+Dieser Unterbefehl listet alle unterstützte Passwort\-Schemen, die als Wert
+für
+.I misc.password_scheme
+in der
+.I vmm.cfg
+verwendet werden können.
+Die Ausgabe variiert, je nach eingesetzter Dovecot Version und der libc des
+Systems.
 .br
-Um eine Wildcard-Suche durchzuführen kann das %-Zeichen am Anfang und/oder Ende
-des \fIMusters\fP verwendet werden.
+Sollte Ihre Dovecot\-Installation nicht zu alt sein, werden zusätzlich
+die verwendbaren Encoding\-Suffixe ausgegeben.
+Eines dieser Suffixe kann an das Passwort\-Schema angefügt werden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
+.B vmm listpwschemes
+Verfügbare Passwort-Schemata
+----------------------------
+        CRYPT SHA512-CRYPT LDAP-MD5 DIGEST-MD5 SHA256 SHA512 SSHA512
+        SKEY SSHA NTLM RPA MD5-CRYPT HMAC-MD5 SHA1 PLAIN SHA CRAM-MD5
+        SSHA256 MD5 LANMAN CLEARTEXT PLAIN-MD5 PLAIN-MD4 OTP SMD5
+        SHA256-CRYPT
 
-        \fBvmm listdomains %example%\fP
-        Übereinstimmende Domains
-        ------------------------
-                [+] example.com
-                [-]     e.g.example.com
-                [-]     example.name
-                [+] example.net
-                [+] example.org
+Verwendbare Encoding-Suffixe
+----------------------------
+        .B64 .BASE64 .HEX
 .fi
-.\"
+.\" ------------------------------------
+.SS version (v)
+.B vmm version
+.PP
+Gibt Versions\- und Copyright\-Informationen zu
+.B vmm
+aus.
+Danach wird
+.B vmm
+beendet.
+.\" -----------------------------------------------------------------------
+.SH DOMAIN UNTERBEFEHLE
+.SS domainadd (da)
+.B vmm domainadd
+.IR fqdn " [" transport ]
+.PP
+Fügt eine neue Domain in die Datenbank ein und erstellt das
+Domain\-Verzeichnis.
+.PP
+Wurde das optional Argument
+.I transport
+angegeben, ersetzt der angegebene Transport den konfigurierten Transport
+.RI ( misc.transport ") aus " vmm.cfg .
+Der angegebene
+.I transport
+ist der Vorgabe\-Transport für alle Konten, die dieser Domain zugeordnet
+werden.
+.PP
+Konfigurationsbezogenes Verhalten:
+.RS
 .TP
-\fBhelp\fP (\fBh\fP)
-Dieser Unterbefehl gibt alle verfügbaren Kommandos auf stdout aus. Danach
-beendet sich \fBvmm\fP.
-.TP
-\fBversion\fP (\fBv\fP)
-Gibt Versions-Informationen zu \fBvmm\fP aus.
-.\"
-.SS DOMAIN UNTERBEFEHLE
+.I domain.auto_postmaster
+Wenn diese Option den Wert
+.B true
+(Vorgabe) hat, wird
+.B vmm
+nach erfolgreichem Anlegen der Domain auch das Konto für
+.BI postmaster@ fqdn
+erstellen.
 .TP
-\fBdomainadd\fP (\fBda\fP) \fIDomain\fP [ \fITransport\fP ]
-Fügt eine neue \fIDomain\fP in die Datenbank ein.
-.br
-Ist das optionale Argument \fITransport\fP angegeben, wird der Vorgabe-Transport
-aus \fBvmm.cfg\fP (misc/transport) für diese \fIDomain\fP ignoriert und der
-angegebene \fITransport\fP verwendet. Der angegebene \fITransport\fP ist
-gleichzeitig der Vorgabe-Transport für alle neuen Konten, die unter dieser
-Domain eingerichtet werden.
+.I account.random_password
+Sollte dieser Option ebenfalls der Wert
+.B true
+zugewiesen sein, wird ein zufällig generiertes Passwort für den
+Postmaster\-Account gesetzt und auf stdout ausgegeben.
+.RE
+.PP
+Beispiele:
 .PP
 .nf
-        Beispiele:
-
-        \fBvmm domainadd support.example.com smtp:mx1.example.com
-        vmm domainadd sales.example.com\fP
+.B vmm domainadd support.example.com smtp:[mx1.example.com]:2025
+Konto für postmaster@support.example.com wird angelegt
+Neues Passwort eingeben:
+Neues Passwort wiederholen:
+.B vmm cs account.random_password true
+.B vmm domainadd vertrieb.example.com
+Konto für postmaster@vertrieb.example.com wird angelegt
+Erzeugtes Passwort: YoG3Uw*5aH
 .fi
-.TP
-\fBdomaininfo\fP (\fBdi\fP) \fIDomain\fP [ \fIdetails\fP ]
-Dieser Unterbefehl zeigt Information zur angegeben \fIDomain\fP an.
-.br
-Um detaillierte Informationen über die \fIDomain\fP zu erhalten, kann das
-optionale Argument \fIdetails\fP angegeben werden. Ein möglicher Wert für
-\fIdetails\fP kann eines der folgenden fünf Schlüsselwörter sein:
+.\" ------------------------------------
+.SS domaindelete (dd)
+.BI "vmm domaindelete " fqdn
+.RB [ force ]
+.PP
+Dieser Unterbefehl löscht die Domain mit dem angegebenen
+.IR fqdn .
+.PP
+Sollten der zu löschenden Domain Konten, Aliase und/oder Relocated User
+zugeordnet sein, wird
+.B vmm
+die Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden.
+Sollten Sie sich Ihres Vorhabens sicher sein, so kann optional das
+Schlüsselwort
+.B force
+angegeben werden.
+.PP
+Sollten Sie wirklich immer wissen was Sie tun, so editieren Sie Ihre
+.I vmm.cfg
+und setzen den Wert der Option
+.I domain.force_deletion
+auf
+.BR true .
+Dann werden Sie zukünftig beim Löschen von Domains nicht mehr wegen
+vorhanden Konten, Aliase und/oder Relocated User gewarnt.
+.\" ------------------------------------
+.SS domaininfo (di)
+.B vmm domaininfo
+.IR fqdn \ [ details ]
+.PP
+Dieser Unterbefehl zeigt Informationen zur Domain mit dem angegebenen
+.I fqdn
+an.
+.PP
+Um detaillierte Informationen über die Domain zu erhalten, kann das
+optionale Argument
+.I details
+angegeben werden.
+Ein möglicher Wert für
+.I details
+kann eines der folgenden fünf Schlüsselwörter sein:
 .RS
-.PD 0
-.TP
+.TP 13
 .B accounts
-um alle existierenden Konten aufzulisten
+um alle eingerichteten Konten aufzulisten
 .TP
 .B aliasdomains
-um alle zugeordneten Alias-Domains aufzulisten
+um alle zugeordneten Alias\-Domains aufzulisten
 .TP
 .B aliases
-um alle verfügbaren Alias-Adressen aufzulisten
+um alle vorhandenen Alias\-Adressen aufzulisten
 .TP
 .B relocated
-um alle Adressen der relocated Users aufzulisten
+um alle Adressen der Relocated Users aufzulisten
+.TP
+.B catchall
+um alle Catch\-all\-Ziele aufzulisten
 .TP
 .B full
 um alle oben genannten Informationen aufzulisten
-.PD
 .RE
-.LP
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm domaininfo sales.example.com
+Domain Informationen
+--------------------
+        Domain Name......: sales.example.com
+        GID..............: 70708
+        Domain Directory.: /srv/mail/c/70708
+        Quota Limit/User.: Storage: 500,00 MiB; Messages: 10.000
+        Active Services..: IMAP SIEVE
+        Transport........: lmtp:unix:private/dovecot-lmtp
+        Alias Domains....: 0
+        Accounts.........: 1
+        Aliases..........: 0
+        Relocated........: 0
+        Catch-All Dests..: 1
+.fi
+.\" ------------------------------------
+.SS domainquota (dq)
+.B vmm domainquota
+.IR "fqdn storage" " [" messages ]
+.RB [ force ]
+.PP
+Dieser Unterbefehl wird verwendet, um für die Konten der Domain ein neues
+Quota\-Limit festzulegen.
+.PP
+Standardmäßig gilt für Konten das Quota\-Limit der
+.IR vmm.cfg " (" domain.quota_bytes " und " domain.quota_messages ).
+Das neue Quota\-Limit gilt für für alle bestehenden Konten, die nicht selbst
+ein Quota\-Limit definieren. Soll das neue Quota\-Limit auch für Konten mit
+eigenen Limiten angewendet werden, so ist das optionale Schlüsselwort
+.B force
+anzugeben.
+.br
+Wenn der Wert für das Argument
+.I messages
+ausgelassen wurde, wird der Vorgabewert
+.B 0
+(null) als Anzahl von Nachrichten angewendet werden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm domaininfo sales.example.com\fP
-        Domain Informationen
-        --------------------
-                Domainname.....: sales.example.com
-                GID............: 70002
-                Transport......: dovecot:
-                Domaindir......: /home/mail/5/70002
-                Aliasdomains...: 0
-                Accounts.......: 0
-                Aliases........: 0
-                Relocated......: 0
-
+.B vmm domainquota example.com 1g force
 .fi
-.TP
-\fBdomaintransport\fP (\fBdt\fP) \fIDomain\fP \fITransport\fP [ \fIforce\fP ]
-Ein neuer \fITransport\fP für die angegebene \fIDomain\fP kann mit diesem
-Unterbefehl festgelegt werden.
+.\" ------------------------------------
+.SS domainservices (ds)
+.B vmm domainservices
+.IR fqdn " [" "service ..." ]
+.RB [ force ]
+.PP
+Um festzulegen, welche Services für die Anwender der Domain \(em mit dem
+angegebenen
+.I fqdn
+\(em nutzbar sein sollen, wird dieser Unterbefehl verwendet.
+.PP
+Der Zugriff auf alle genannten Services wird den Anwender gestattet.
+Der Zugriff auf nicht genannte Services wird verweigert werden.
+Verwendbare
+.IR service \-Namen
+sind:
+.BR  imap ", " pop3 ", " sieve " und " smtp .
 .br
-Wurde das optionale Schlüsselwort '\fBforce\fP' angegeben, so werden alle
-bisherigen Transport-Einstellungen, der in dieser Domain vorhandenen Konten,
-mit dem neuen \fITransport\fP überschrieben.
-.br
-Andernfalls gilt der neue \fITransport\fP nur für Konten, die neu erstellt
-werden.
+Wird das Schlüsselwort
+.B force
+angegeben, so werden alle kontospezifischen Einstellungen gelöscht und es
+gelten fortan die Service\-Einstellungen der Domain für alle Konten. Ohne
+dieses Schlüsselwort gelten die neuen Einstellungen nur für Konten, bei denen
+die Service\-Einstellungen nicht individuell geändert wurden.
+.\" ------------------------------------
+.SS domaintransport (dt)
+.BI "vmm domaintransport" " fqdn transport"
+.RB [ force ]
+.PP
+Ein neuer
+.I transport
+für die Domain mit dem angegebenen
+.I fqdn
+kann mit diesem Unterbefehl festgelegt werden.
+.PP
+Wird das Schlüsselwort
+.B force
+angegeben, so werden alle kontospezifischen Einstellungen gelöscht und es
+gelten fortan die Transport\-Einstellungen der Domain für alle Konten. Ohne
+dieses Schlüsselwort gelten die neuen Einstellungen nur für Konten, bei denen
+die Transport\-Einstellungen nicht individuell geändert wurden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm domaintransport support.example.com dovecot:\fP
+.B vmm domaintransport support.example.com dovecot:
 .fi
-.TP
-\fBdomaindelete\fP (\fBdd\fP) \fIDomain\fP [ \fIdelalias\fP | \fIdeluser\fP | \fIdelall\fP ]
-Mit diesem Unterbefehl wird die angegebene \fIDomain\fP gelöscht.
-.br
-Sollten der \fIDomain\fP Konten und/oder Aliase zugeordnet sein, wird \fBvmm\fP
-die Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden.
-
-Sollten Sie sich Ihres Vorhabens sicher sein, so kann optional eines der
-folgenden Schlüsselwörter angegeben werden: '\fBdelalias\fP', '\fBdeluser\fP' oder '\fBdelall\fP'
-
-Sollten Sie wirklich immer wissen was Sie tun, so editieren Sie Ihre
-\fBvmm.cfg\fP und setzen den Wert der Option \fIforcedel\fP, im Abschnitt
-\fImisc\fP, auf true. Dann werden Sie beim Löschen von Domains nicht mehr wegen
-vorhanden Konten/Aliase gewarnt.
-.\"
-.SS ALIAS-DOMAIN UNTERBEFEHLE
-.TP
-\fBaliasdomainaddd\fP (\fBada\fP) \fIAliasdomain\fP \fIZieldomain\fP
-Mit diesem Unterbefehl wird der \fIZieldomain\fP die Alias-Domain
-\fIAliasdomain\fP zugewiesen.
+.\" ------------------------------------
+.SS domainnote (do)
+.BI "vmm domainnote" " fqdn"
+.RI [ note ]
+.PP
+Mit diesem Unterbefehl kann eine Domain mit einer Notiz versehen werden. Um
+die Notiz wieder zu löschen, läßt man sie einfach weg.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm aliasdomainadd example.name example.com\fP
+.B vmm do example.com Gehört Robert
 .fi
-.TP
-\fBaliasdomaininfo (\fBadi\fP) \fIAliasdomain\fP
-Dieser Unterbefehl informiert darüber, welcher Domain die Alias-Domain
-\fIAliasdomain\fP zugeordnet ist.
+.\" -----------------------------------------------------------------------
+.SH ALIAS\-DOMAIN UNTERBEFEHLE
+Eine Alias\-Domain ist ein Alias für eine Domain, die zuvor mit dem
+Unterbefehl
+.B domainadd
+erstellt wurde.
+Alle Konten, Aliase und Relocated Users der Domain sind ebenfalls unter der
+Alias\-Domain verfügbar.
+.br
+Im Folgenden wird angenommen, example.net sei ein Alias für example.com.
+.PP
+Postfix wird nicht erst fälschlicherweise E\-Mails für
+unbekannten.user@example.net annehmen und später an den \(em oftmals
+gefälschten \(em Absender bouncen.
+Postfix wird E\-Mails an unbekannte Empfänger sofort ablehnen.
+.br
+Dieses Verhalten ist sichergestellt, solange die empfohlenen
+Datenbankabfragen in
+.I $config_directory/pgsql\-*.cf
+konfiguriert sind.
+.\" ------------------------------------
+.SS aliasdomainadd (ada)
+.BI "vmm aliasdomainadd" " fqdn destination"
+.PP
+Dieser Unterbefehl legt die Alias\-Domain
+.RI ( fqdn )
+als Alias für eine bestehende Domain
+.RI ( destination ") an."
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm aliasdomaininfo example.name\fP
-        Alias-Domain Informationen
-        --------------------------
-                Die Alias-Domain example.name gehört zu:
-                    * example.com
+.B vmm aliasdomainadd example.net example.com
 .fi
-.TP
-\fBaliasdomainswitch\fP (\fBads\fP) \fIAliasdomain\fP \fIZieldomain\fP
-Wenn das Ziel der vorhandenen \fIAliasdomain\fP auf eine andere \fIZieldomain\fP
-geändert werden soll, ist dieser Unterbefehl zu verwenden.
+.\" ------------------------------------
+.SS aliasdomaindelete (add)
+.BI "vmm aliasdomaindelete" " fqdn"
+.PP
+Verwenden Sie diesen Unterbefehl, um die Alias\-Domain
+.I fqdn
+zu löschen.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm aliasdomainswitch example.name example.org\fP
+.B vmm aliasdomaindelete e.g.example.com
 .fi
-.TP
-\fBaliasdomaindelete\fP (\fBadd\fP) \fIAliasdomain\fP
-Wenn die Alias-Domain mit dem Namen \fIAliasdomain\fP gelöscht werden soll, ist
-dieser Unterbefehl zu verwenden.
+.\" ------------------------------------
+.SS aliasdomaininfo (adi)
+.BI "vmm aliasdomaininfo" " fqdn"
+.PP
+Dieser Unterbefehl gibt Informationen darüber aus, welcher Domain die
+Alias\-Domain
+.I fqdn
+aktuell zugeordnet ist.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm adi example.net
+Alias\-Domain Informationen
+--------------------------
+        Die Alias\-Domain example.net gehört zu:
+            * example.com
+.fi
+.\" ------------------------------------
+.SS aliasdomainswitch (ads)
+.BI "vmm aliasdomainswitch" " fqdn destination"
+.PP
+Wenn Sie die bereits vorhandene Alias\-Domain
+.I fqdn
+einer anderen Ziel\-Domain zuordnen wollen, verwenden Sie diesen
+Unterbefehl.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm aliasdomaindelete e.g.example.com\fP
+.B vmm aliasdomainswitch example.net example.org
 .fi
-.\"
-.SS KONTO UNTERBEFEHLE
-.TP
-\fBuseradd\fP (\fBua\fP) \fIAdresse\fP [ \fIPasswort\fP ]
-Mit diesem Unterbefehl wird ein neues Konto für die angegebene \fIAdresse\fP
+.\" -----------------------------------------------------------------------
+.SH KONTO UNTERBEFEHLE
+.SS useradd (ua)
+.B vmm useradd
+.IR address " [" password ]
+.PP
+Mit diesem Unterbefehl wird ein neues Konto für die angegebene Adresse
 angelegt.
-.br
-Wurde kein \fIPasswort\fP angegeben wird \fBvmm\fP dieses im interaktiven
-Modus erfragen.
+.PP
+Wurde kein Passwort angegeben wird
+.B vmm
+dieses im interaktiven Modus erfragen.
+Falls kein Passwort angegeben wurde und
+.I account.random_password
+den Wert
+.B true
+hat, wird
+.B vmm
+ein zufälliges Passwort generieren und auf stdout ausgeben, nachdem das
+Konto angelegt wurde.
+.PP
+Beispiele:
 .PP
 .nf
-        Beispiele:
-
-        \fBvmm ua d.user@example.com 'A 5ecR3t P4s5\\/\\/0rd'\fP
-        \fBvmm ua e.user@example.com\fP
-        Neues Passwort eingeben:
-        Neues Passwort wiederholen:
+.B vmm ua d.user@example.com \(dqA 5ecR3t P4s5\(rs/\(rs/0rd\(dq
+.B vmm useradd e.user@example.com
+Neues Passwort eingeben:
+Neues Passwort wiederholen:
 .fi
-.TP
-\fBuserinfo\fP (\fBui\fP) \fIAdresse\fP [ \fIdetails\fP ]
-Dieser Unterbefehl zeigt einige Informationen über das Konto mit der angegebenen
-\fIAdresse\fP an.
-.br
-Wurde das optionale Argument \fIdetails\fP angegeben, werden weitere
-Informationen ausgegeben.
-.br
-Mögliche Werte für \fIdetails\fP sind:
+.\" ------------------------------------
+.SS userdelete (ud)
+.BI "vmm userdelete" " address"
+.RB [ force ]
+.PP
+Verwenden Sie diesen Unterbefehl, um das Konto mit der angegebenen Adresse
+zu löschen.
+.PP
+Sollte es einen oder mehrere Aliase geben, deren Ziel\-Adresse mit der
+Adresse des zu löschenden Kontos identisch ist, wird
+.B vmm
+die Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden.
+Um dieses zu umgehen, kann das optionale Schlüsselwort
+.B force
+angegebenen werden.
+.\" ------------------------------------
+.SS userinfo (ui)
+.B "vmm userinfo"
+.IR address " [" details ]
+.PP
+Dieser Unterbefehl zeigt einige Informationen über das Konto mit der
+angegebenen Adresse an.
+.PP
+Wurde das optionale Argument
+.I details
+angegeben, werden weitere Informationen ausgegeben.
+Mögliche Werte für
+.I details
+sind:
 .RS
-.PD 0
-.TP 
+.TP 8
 .B aliases
-um alle Alias-Adressen, mit dem Ziel \fIAdresse\fP, aufzulisten
+um alle Alias\-Adressen, mit dem Ziel
+.IR address ,
+aufzulisten
 .TP
 .B du
-um zusätzlich die Festplattenbelegung des Kontos anzuzeigen
+um zusätzlich die Festplattenbelegung des Mail\-Verzeichnisses eines Kontos
+anzuzeigen.
+Soll die Festplattenbelegung jedes Mal mit der
+.B userinfo
+ermittelt werden, ist in der
+.I vmm.cfg
+der Wert der Option
+.I account.disk_usage
+auf
+.B true
+zu setzen.
 .TP
 .B full
 um alle oben genannten Informationen anzuzeigen
-.PD
 .RE
-.LP
-.TP
-\fBusername\fP (\fBun\fP) \fIAdresse\fP \fI'Bürgerlicher Name'\fP
-Der Bürgerliche Name des Konto-Inhabers mit der angegebenen \fIAdresse\fP kann
-mit diesem Unterbefehl gesetzt/aktualisiert werden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm un d.user@example.com 'John Doe'\fP
+.B vmm userinfo d.user@example.com
+Konto Informationen
+-------------------
+        Address..........: d.user@example.com
+        Name.............: None
+        UID..............: 79881
+        GID..............: 70704
+        Home.............: /srv/mail/2/70704/79881
+        Mail_Location....: mdbox:~/mdbox
+        Quota Storage....: [  0,00%] 0/500,00 MiB
+        Quota Messages...: [  0,00%] 0/10.000
+        Transport........: lmtp:unix:private/dovecot-lmtp
+        SMTP.............: deaktiviert
+        POP3.............: deaktiviert
+        IMAP.............: aktiviert
+        SIEVE............: aktiviert
 .fi
-.TP
-\fBuserpassword\fP (\fBup\fP) \fIAdresse\fP [ \fIPasswort\fP ]
-Das \fIPasswort\fP eines Kontos kann mit diesem Unterbefehl aktualisiert werden.
-.br
-Wurde kein \fIPasswort\fP angegeben wird \fBvmm\fP dieses im interaktiven
-Modus erfragen.
+.\" ------------------------------------
+.SS username (un)
+.BI "vmm username" " address"
+.RI [ name ]
+.PP
+Der bürgerliche Name des Kontoinhabers mit der angegebenen Adresse kann mit
+diesem Unterbefehl gesetzt/aktualisiert werden.
+.PP
+Wird kein
+.I name
+übergeben, so wird der Wert in den Kontoinformationen gelöscht.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm up d.user@example.com 'A |\\/|0r3 5ecur3 P4s5\\/\\/0rd?'\fP
+.B vmm username d.user@example.com \(dqJohn Doe\(dq
 .fi
-.TP
-\fBusertransport\fP (\fBut\fP) \fIAdresse\fP \fITransport\fP
-Mit diesem Unterbefehl kann ein abweichender \fITransport\fP für das Konto mit
-der angegebenen \fIAdresse\fP festgelegt werden.
+.\" ------------------------------------
+.SS userpassword (up)
+.BI "vmm userpassword" " address"
+.RI [ password ]
+.PP
+Das Passwort eines Kontos kann mit diesem Unterbefehl aktualisiert werden.
+.PP
+Wurde kein Passwort angegeben, wird
+.B vmm
+dieses im interaktiven Modus erfragen.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm up d.user@example.com \(dqA |\(rs/|0r3 5ecur3 P4s5\(rs/\(rs/0rd?\(dq
+.fi
+.\" ------------------------------------
+.SS usernote (uo)
+.BI "vmm usernote" " address"
+.RI [ note ]
+.PP
+Mit diesem Unterbefehl kann ein Konto mit einer Notiz versehen werden. Um die
+Notiz wieder zu löschen, läßt man sie einfach weg.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm ut d.user@example.com smtp:pc105.it.example.com\fP
+.B vmm uo d.user@example.com Wird nur bis Ende Mai 2012 gebraucht
 .fi
-.TP
-\fBuserdisable\fP (\fBu0\fP) \fIAdresse\fP [ \fIService\fP ]
-Soll ein Anwender keinen Zugriff auf einen oder alle Service haben, kann der
-Zugriff mit diesem Unterbefehl beschränkt werden.
-.br
-Wurde weder ein \fIService\fP noch das Schlüsselwort '\fIall\fP' angegeben,
-werden alle Services (\fIsmtp\fP, \fIpop3\fP, \fIimap\fP, und \fIsieve\fP)
-für das Konto mit der angegebenen \fIAdresse\fP deaktiviert.
-.br
-Andernfalls wird nur der Zugriff auf den angegeben \fIService\fP gesperrt.
+.\" ------------------------------------
+.SS userquota (uq)
+.BI "vmm userquota" " address storage"
+.RI [ messages ]
+.PP
+Um ein neues Quota\-Limit für das Konto mit der angegebenen Adresse
+festzulegen, wird dieser Unterbefehl verwendet.
+.PP
+Wenn der Wert für das Argument
+.I messages
+ausgelassen wurde, wird der Vorgabewert
+.B 0
+(null) als Anzahl von Nachrichten angewendet werden.
+.PP
+Anstelle einer Limite, bewirkt das Wort 'domain', daß die Limite des
+Kontos gelöscht wird und somit wieder der in der Domain gespeicherte
+Wert für das Konto gilt.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm userquota d.user@example.com 750m
+.B vmm userquote d.user@example.com domain
+.fi
+.\" ------------------------------------
+.SS userservices (us)
+.B vmm userservices
+.IR address " [" "service ..." ]
+.PP
+Verwenden Sie diesen Unterbefehl, um einem Anwender den Zugriff auf die
+genannten Services zu gestatten.
+.PP
+Der Zugriff auf alle nicht genannten Services wird dem Anwender, mit der
+angegebenen Adresse, verwehrt werden.
+.PP
+Anstelle einer Liste, bewirkt das Wort 'domain', daß die benutzerspezifische
+Liste gelöscht wird und somit wieder die in der Domain gespeicherte
+Liste für das Konto gilt.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiele:
-
-        \fBvmm u0 b.user@example.com imap\fP
-        \fBvmm userdisable c.user@example.com\fP
-.fi
+.B vmm userservices d.user@example.com SMTP IMAP
+.B vmm userservices d.user@example.com domain
+.\" ------------------------------------
+.SS usertransport (ut)
+.BI "vmm usertransport" " address transport"
 .PP
-.TP
-\fBuserenable\fP (\fBu1\fP) \fIAdresse\fP [ \fIService\fP ]
-Um den Zugriff auf bestimmte oder alle gesperrten Service zu gewähren, wird
-dieser Unterbefehl verwendet.
-.br
-Wurde weder ein \fIService\fP noch das Schlüsselwort '\fIall\fP' angegeben,
-werden alle Services (\fIsmtp\fP, \fIpop3\fP, \fIimap\fP, und \fIsieve\fP)
-für das Konto mit der angegebenen  \fIAdresse\fP aktiviert.
+Mit diesem Unterbefehl kann ein abweichender
+.I transport
+für das Konto mit der angegebenen Adresse bestimmt werden.
+.PP
+Wird als
+.I transport
+das Wort 'domain' übergeben, so wird der explizite Transport des Kontos
+wieder gelöscht und der in der Domain gespeicherte Wert benutzt.
+.PP
+Beispiel:
 .br
-Andernfalls wird nur der Zugriff auf den angegeben \fIService\fP gestattet.
+Angenommen, Sie wollen mit Dovecots
+.BR dsync (1)
+die E\-Mails vom Maildir\-Format ins mdbox\-Format konvertieren, dann
+können Sie Postfix, über den Transport, darüber informieren, es später
+nochmals zu versuchen.
 .PP
-.TP
-\fBuserdelete\fP (\fBud\fP) \fIAdresse\fP [ \fIdelalias\fP ]
-Verwenden Sie diesen Unterbefehl um, das Konto mit der angegebenen \fIAdresse\fP
-zu löschen.
-.br
-Sollte es einen oder mehrere Aliase geben, deren Zieladresse mit der des Kontos
-identisch ist, wird \fBvmm\fP die Ausführung des Befehls mit einer
-entsprechenden Fehlermeldung beenden. Um dieses zu umgehen, kann das optionale
-Schlüsselwort '\fIdelalias\fP' angegebenen werden.
-.\"
-.SS ALIAS UNTERBEFEHLE
-.TP
-\fBaliasadd\fP (\fBaa\fP) \fIAlias\fP \fIZiel\fP
-Mit diesem Unterbefehl werden neue Aliase erstellt.
+.nf
+.B vmm ut d.user@example.com \(dqretry:4.0.0 Mailbox being migrated\(dq
+# Konvertieren der Mailbox …
+# … danach den Transport auf den Domainwert setzen
+.B vmm usertransport d.user@example.com domain
+.fi
+.\" -----------------------------------------------------------------------
+.SH ALIAS UNTERBEFEHLE
+.SS aliasadd (aa)
+.BI "vmm aliasadd" " address destination ..."
+.PP
+Mit diesem Unterbefehl werden neue Alias\-Adressen, mit einer oder mehren
+.IR destination (en),
+erstellt.
+.PP
+Innerhalb der Zieladresse werden die Zeichenketten
+.IR %n ,
+.IR %d
+und
+.IR %=
+durch den ursprünglichen lokalen Teil, die Domain bzw. die Emailadresse mit
+'=' anstelle von '@' ersetzt. Dies ermöglicht z.B. in Verbindung mit
+Alias\-Domains domain\-spezifische Empfänger.
+.PP
+Beispiele:
 .PP
 .nf
-        Beispiele:
-
-        \fBvmm aliasadd john.doe@example.com d.user@example.com\fP
-        \fBvmm aa support@example.com d.user@example.com\fP
-        \fBvmm aa support@example.com e.user@example.com\fP
+.B vmm aliasadd john.doe@example.com d.user@example.com
+.B vmm aa support@example.com d.user@example.com e.user@example.com
+.B vmm aa postmaster@example.com postmaster+%d@example.org
 .fi
-.TP
-\fBaliasinfo\fP (\fBai\fP) \fIAlias\fP
-Informationen zu einem Alias können mit diesem Unterbefehl ausgegeben werden.
+.\" ------------------------------------
+.SS aliasdelete (ad)
+.BI "vmm aliasdelete" " address"
+.RI [ destination ]
+.PP
+Verwenden Sie diesen Unterbefehl um den Alias mit der angegebenen Adresse
+zu löschen.
+.PP
+Wurde eine optionale
+.I destination
+angegeben, so wird nur diese
+.I destination
+vom angegebenen Alias entfernt.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm aliasdelete support@example.com d.user@example.com
+.fi
+.\" ------------------------------------
+.SS aliasinfo (ai)
+.BI "vmm aliasinfo" " address"
+.PP
+Informationen zum Alias mit der angegebenen Adresse können mit diesem
+Unterbefehl ausgegeben werden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm aliasinfo support@example.com\fP
-        Alias Informationen
-        -------------------
-                E-Mails für support@example.com werden weitergeleitet an:
-                     * d.user@example.com
-                     * e.user@example.com
+.B vmm aliasinfo support@example.com
+Alias Informationen
+-------------------
+        E\-Mails für support@example.com werden weitergeleitet an:
+             * e.user@example.com
 .fi
-.TP
-\fBaliasdelete\fP (\fBad\fP) \fIAlias\fP [ \fIZiel\fP ]
-Verwenden Sie diesen Unterbefehl um den angegebenen \fIAlias\fP zu löschen.
-.br
-Wurde die optionale Zieladresse \fIZiel\fP angegeben, so wird nur diese
-Zieladresse vom angegebenen \fIAlias\fP entfernt.
+.\" -----------------------------------------------------------------------
+.SH RELOCATED UNTERBEFEHLE
+.SS relocatedadd (ra)
+.BI "vmm relocatedadd" " address newaddress"
+.PP
+Um einen neuen Relocated User anzulegen kann dieser Unterbefehl verwendet
+werden.
+.PP
+Dabei ist
+.I address
+die ehemalige Adresse des Benutzers, zum Beispiel b.nutzer@example.com, und
+.I newaddress
+die neue Adresse, unter der die/der Benutzer/in erreichbar ist.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm relocatedadd b.nutzer@example.com b\-nutzer@firma.tld
+.fi
+.\" ------------------------------------
+.SS relocatedinfo (ri)
+.BI "vmm relocatedinfo " address
+.PP
+Dieser Unterbefehl zeigt die neue Adresse des Relocated Users mit mit der
+angegebenen Adresse.
+.PP
+Beispiel:
+.PP
+.nf
+.B vmm relocatedinfo b.nutzer@example.com
+Verschiebe\-Informationen
+------------------------
+        Der Benutzer »b.nutzer@example.com« wurde nach »b\-nutzer@firma.tld« verschoben
+.fi
+.\" ------------------------------------
+.SS relocateddelete (rd)
+.BI "vmm relocateddelete " address
+.PP
+Mit diesem Unterbefehl kann der Relocated User mit der angegebenen Adresse
+gelöscht werden.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-        \fBvmm ad support@example.com d.user@example.com\fP
+.B vmm relocateddelete b.nutzer@example.com
 .fi
-.\"
-.SS RELOCATED UNTERBEFEHLE
-.TP
-\fBrelocatedadd\fP (\fBra\fP) \fIalte_adresse\fP \fIneue_adresse\fP
-Um einen neuen relocated User anzulegen kann dieser Unterbefehl verwendet
-werden.
-.br
-Dabei ist \fIalte_adresse\fP die ehemalige Adresse des Benutzers, z. B.
-b.user@example.com, und \fIneue_adresse\fP die neue Adresse, unter der Benutzer
-erreichbar ist.
+.\" -----------------------------------------------------------------------
+.SH CATCH\-ALL UNTERBEFEHLE
+.SS catchalladd (caa)
+.BI "vmm catchalladd" " fqdn destination ..."
+.PP
+Mit diesem Unterbefehl können für eine Domain Adressen definiert werden, an
+die E\-Mails geleitet werden, die an nicht\-existente Adressen innerhalb
+dieser Domains adressiert sind.
+Diese Adressen \(dqfangen alle\(dq diese E\-Mails auf, es sei denn es
+bestehen spezifischere Aliase, Mailboxen oder Relocated\-Einträge.
+.PP
+WARNUNG: Catch\-all Adressen können dazu führen, daß ein Mailserver von Spam
+überflutet wird, da Spammer zuweilen gerne alle möglichen Emailadressen
+ausprobieren und man auf einmal zig tausend Nachrichten gerichtet an
+Adressen von abba@example.org bis zztop@example.org weitergeleitet bekommt.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm relocatedadd b.user@example.com b-user@company.tld\fP
+.B vmm catchalladd example.com b.nutzer@example.org
 .fi
-.TP
-\fBrelocatedinfo\fP (\fBri\fP) \fIalte_adresse\fP
-Dieser Unterbefehl zeigt die neue Adresse des relocated Users mit
-\fIalte_adresse\fP.
+.\" ------------------------------------
+.SS catchallinfo (cai)
+.BI "vmm catchallinfo " fqdn
+.PP
+Dieser Unterbefehl zeigt die für eine Domain definierten Catch\-all Aliase
+an.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm relocatedinfo b.user@example.com\fP
-        Relocated Informationen
-        -----------------------
-        Der Benutzer „b.user@example.com“ ist erreichbar unter „b-user@company.tld“
+.B vmm catchallinfo example.com
+Catch-all Informationen
+-----------------------
+  Nachrichten an unbekannte Adressen innerhalb der example.com Domäne werden
+  weitergeleitet an:
+         * b.nutzer@example.org
 .fi
-.TP
-\fBrelocateddelete\fP (\fBrd\fP) \fIalte_adresse\fP
-Mit diesem Unterbefehl kann der relocated User mit \fIalte_adresse\fP gelöscht
-werden.
+.\" ------------------------------------
+.SS catchalldelete (cad)
+.BI "vmm catchalldelete " fqdn
+.RI [ destination ]
+.PP
+Mit diesem Unterbefehl werden Catch\-all Aliase einer Domain wieder
+gelöscht, entweder nur das angegebene Alias, oder alle, wenn keines
+angegeben wurde.
+.PP
+Beispiel:
 .PP
 .nf
-        Beispiel:
-
-        \fBvmm relocateddelete b.user@example.com\fP
+.B vmm catchalldelete example.com b.nutzer@example.org
 .fi
-.\"
+.\" -----------------------------------------------------------------------
 .SH DATEIEN
-/usr/local/etc/vmm.cfg
+.TP
+.I /root/vmm.cfg
+Wird verwendet, falls vorhanden.
+.TP
+.I /usr/local/etc/vmm.cfg
+Wird verwendet, sollte obige Datei nicht gefunden werden.
+.TP
+.I /etc/vmm.cfg
+Wird verwendet, falls die oben genannten Dateien nicht existieren.
+.\" -----------------------------------------------------------------------
 .SH SIEHE AUCH
-vmm.cfg(5), Konfigurationsdatei für vmm
-.SH AUTOR
-\fBvmm\fP und die dazugehörigen Manualseiten wurden von Pascal Volk
-<\fIneverseen@users.sourceforge.net\fP> geschrieben und sind unter den
-Bedingungen der BSD Lizenz lizenziert.
+.BR dsync (1),
+.BR transport (5),
+.BR vmm.cfg (5)
+.\" -----------------------------------------------------------------------
+.SH INTERNET RESSOURCEN
+.TP
+Wiki
+http://de.vmm.localdomain.org/
+.TP
+Projekt\-Seite
+http://sf.net/projects/vmm/
+.TP
+Bugtracker
+https://bitbucket.org/pvo/vmm/issues
+.\" -----------------------------------------------------------------------
+.SH COPYING
+vmm und die dazugehörigen Manualseiten wurden von Pascal Volk <user+vmm AT
+localhost.localdomain.org> geschrieben und sind unter den Bedingungen der
+BSD Lizenz lizenziert.
--- a/man/de/man5/vmm.cfg.5	Mon Nov 07 03:22:15 2011 +0000
+++ b/man/de/man5/vmm.cfg.5	Thu Jun 28 19:26:50 2012 +0000
@@ -1,45 +1,86 @@
-.TH vmm.cfg 5 "17 Aug 2009" "Pascal Volk"
+.TH "VMM.CFG" "5" "2011-11-12" "vmm 0.6" "vmm"
 .SH NAME
 vmm.cfg \- Konfigurationsdatei für vmm
-.SH SYNOPSIS
+.\" -----------------------------------------------------------------------
+.SH ÜBERSICHT
 vmm.cfg
+.\" -----------------------------------------------------------------------
 .SH BESCHREIBUNG
-\fBvmm\fR(1) liest Konfigurationsparameter aus der Datei \fIvmm.cfg\fP.
-.br
-Die Konfigurationsdatei ist in mehrere Abschnitte unterteilt. Jeder Abschnitt
-wird mit dem, in eckigen Klammern '[' und ']' eingefassten, Namen des Abschnitts
-eingeleitet (z. B. \fB[database]\fP), gefolgt von \'Option = Wert\' Einträgen
-(Z. B. \fBhost = 127.0.0.1\fP).
-.br
-Leerräume um das Gleichheitszeichen '=' und am Ende eine Wertes werden
+.BR vmm (1)
+liest seine Konfigurationsparameter aus der Datei
+.IR vmm.cfg .
+.PP
+Die Konfigurationsdatei ist in mehrere Sektionen unterteilt.
+Jede Sektion wird mit dem in eckigen Klammern
+.RB ' [ "' und '" ] '
+eingefassten Namen der Sektion eingeleitet, gefolgt von
+.RI ' Option " = " Wert '
+Einträgen.
+.PP
+Leerräume um das Gleichheitszeichen '=' und am Ende eines Wertes werden
 ignoriert.
-.PP
 Leerzeilen und Zeilen, die mit einer '#' oder einem ';' anfangen, werden
 ignoriert.
 .PP
 Jeder Wert ist von einem der folgenden Datentypen:
-.IP \(bu
+.PP
+.TP 8
 .I Boolean
-um zu bestimmen, ob etwas eingeschaltet/aktiviert (true) oder
+um festzulegen, ob etwas eingeschaltet/aktiviert (true) oder
 ausgeschaltet/deaktiviert (false) ist.
 .br
-Mögliche Werte für \fBtrue\fP sind: \fB1\fP, \fByes\fP, \fBtrue\fP und \fBon\fP.
+Mögliche Werte für
+.I true
+sind:
+.BR 1 , " yes" , " true" " und " on .
 .br
-Mögliche Werte für \fBfalse\fP sind: \fB0\fP, \fBno\fP, \fBfalse\fP und
-\fBoff\fP.
-.IP \(bu
+Mögliche Werte für
+.I false
+sind:
+.BR 0 , " no" , " false" " und " off .
+.TP
 .I Int
-eine Integer-Zahl, geschrieben ohne eine gebrochene oder dezimale Komponente.
+eine Integer\-Zahl, geschrieben ohne eine gebrochene oder dezimale
+Komponente.
 .br
-Beispielsweise sind \fB1\fP, \fB50\fP oder \fB321\fP Integer-Zahlen.
-.IP \(bu
+Beispielsweise
+.BR 1 , " 50" " oder " 321
+sind Integer\-Zahlen.
+.TP
 .I String
 eine Folge von Buchstaben und Zahlen.
 .br
-Zum Beispiel: '\fBWort\fP', '\fBHallo Welt\fP', oder '\fB/usr/bin/strings\fP'
-.SS SUCHREIHENFOLGE
-Standardmäßig sucht vmm die \fIvmm.cfg\fP in folgenden Verzeichnissen, in dieser
-Reihenfolge:
+Zum Beispiel:
+.RB ' Wort "', '" "Hallo Welt" "' oder '" /usr/bin/strings '
+.PP
+Die meisten Optionen haben einen Vorgabewert.
+Dieser ist nach dem Namen der Option in Klammern angegebenen.
+Um den Vorgabewert einer Option zu verwenden, wird die entsprechende Zeile
+entweder mit
+.BR # " oder " ;
+auskommentiert oder die Zeile wird einfach aus der
+.I vmm.cfg
+entfernt.
+.PP
+Eine minimale
+.I vmm.cfg
+könnte so aussehen:
+.PP
+.nf
+[database]
+user = ich
+pass = xxxxxxxx
+
+[misc]
+dovecot_version = 1.2.16
+.fi
+.\" -----------------------------------------------------------------------
+.SH SUCHREIHENFOLGE
+Standardmäßig sucht
+.BR vmm (1)
+die
+.I vmm.cfg
+in folgenden Verzeichnissen, in der angegebenen Reihenfolge:
 .RS
 .PD 0
 .TP
@@ -55,219 +96,530 @@
 .RE
 .PP
 Die zuerst gefundene Datei wird verwendet.
-.\" -----
-.SH DATABASE ABSCHNITT
-Der \fBdatabase\fP-Abschnitt wird verwendet, um die für den Datenbankzugriff
-benötigten Optionen festzulegen.
-.TP
-\fBhost\fP (\fIString\fP)
-Der Hostname oder die IP-Adresse des Datenbank-Servers.
-.TP
-\fBuser\fP (\fIString\fP)
-Der Name des Datenbank-Benutzers.
-.TP
-\fBpass\fP (\fIString\fP)
-Das Passwort des Datenbank-Benutzers
-.TP
-\fBname\fP (\fIString\fP)
-Name der zu verwendenden Datenbank.
-.TP
-\fBBeispiel\fP:
-[database]
-.br
-host = localhost
-.br
-user = vmm
-.br
-pass = T~_:L4OYyl]TU?)
+.\" -----------------------------------------------------------------------
+.SH SEKTION ACCOUNT
+Die Optionen der Sektion
+.B account
+legen Konto\-spezifische Einstellungen fest.
+.SS account.delete_directory
+.BR delete_directory " (Vorgabe: false) :"
+.I Boolean
+.PP
+Bestimmt das Verhalten von
+.BR vmm (1)
+beim Löschen eines Kontos (userdelete).
+Wenn der Wert dieser Option
+.I true
+ist, wird das Home\-Verzeichnis des zu löschenden Anwenders rekursiv
+gelöscht.
+.\" ------------------------------------
+.SS account.directory_mode
+.BR directory_mode " (Vorgabe: 448) :"
+.I Int
+.PP
+Zugriffsbits des Home\-Verzeichnisses, sowie aller enthaltenen
+Verzeichnisse, in Dezimal\-Schreibweise (Basis 10).
 .br
-name = mailsys
-.\" -----
-.SH MAILDIR ABSCHNITT
-Im \fBmaildir\fP-Abschnitt werden die für die Maildirs erforderlichen Optionen
-festgelegt.
-.TP
-\fBname\fP (\fIString\fP)
-Standard-Name des Maildir-Verzeichnisses im Verzeichnis des jeweiligen
-Anwenders.
-.TP
-\fBfolders\fP (\fIString\fP)
-Eine durch Doppelpunkten getrennte Liste mit Verzeichnisnamen, die innerhalb des
-Maildirs erstellt werden sollen.
-.br
-Sollen innerhalb des Maildirs keine Verzeichnisse angelegt werden, ist dieser
-Optionen ein einzelner Doppelpunkt (':') als Wert zuzuweisen.
-.TP
-\fBmode\fP (\fIInt\fP)
-Zugriffsbits des Maildirs in Dezimal-Schreibweise (Basis 10).
-.br
-Beispiel: \'drwx------' -> oktal 0700 -> dezimal 448
-.TP
-\fBdiskusage\fP (\fIBoolean\fP)
-Legt fest, ob die Festplattenbelegung des Maildirs jedes Mal, wenn
-Konto-Informationen ausgegeben werden, ermittelt und mit ausgegeben werden
-sollen.
+Beispiel: 'drwx\-\-\-\-\-\-' \(-> oktal 0700 \(-> dezimal 448
+.\" ------------------------------------
+.SS account.disk_usage
+.BR disk_usage " (Vorgabe: false) :"
+.I Boolean
+.PP
+Legt fest, ob die Festplattenbelegung des Maildirs eines Benutzers jedes
+Mal mit
+.BR du (1)
+ermittelt und mit den Konto\-Informationen ausgegeben werden soll.
+.PP
+Bei umfangreichen Maildirs kann das langsam sein.
+Falls Sie Quotas aktiviert haben, wird der
+.BR vmm\-Unterbefehl
+userinfo ebenfalls die aktuelle Quota\-Nutzung des Kontos mit ausgegeben.
+Sie können auch eines der optionalen Argumente
+.BR du " oder " full
+an userinfo übergeben, um sich die aktuelle Festplattenbelegung anzeigen zu
+lassen.
+.\" ------------------------------------
+.SS account.password_length
+.BR password_length " (Vorgabe: 8) :"
+.I Int
+.PP
+Diese Option legt die Anzahl der Zeichen für automatisch erzeugte
+Passwörter fest.
+Alle Werte kleiner als 8 werden auf 8 erhöht.
+.\" ------------------------------------
+.SS account.random_password
+.BR random_password " (Vorgabe: false) :"
+.I Boolean
+.PP
+Mit dieser Option wird bestimmt, ob
+.BR vmm (1)
+ein zufälliges Passwort generieren soll, wenn kein Passwort an den
+Unterbefehl useradd übergeben wurde.
+Ist der Wert dieser Option
+.IR false ,
+wird
+.B vmm
+Sie auffordern, ein Passwort für den neuen Account einzugeben.
+.PP
+Sie können die Länge für automatisch generierte Passwörter mit der Option
+.I account.password_length
+konfigurieren.
+.\" -----------------------------------------------------------------------
+.SH SEKTION BIN
+In der
+.BR bin \-Sektion
+werden die Pfade zu den von
+.BR vmm (1)
+benötigten Binaries angegeben.
+.SS bin.dovecotpw
+.BR dovecotpw " (Vorgabe: /usr/sbin/dovecotpw) :"
+.I String
+.PP
+Der absolute Pfad zum dovecotpw Binary.
+Geben Sie den absoluten Pfad zum
+.BR doveadm (1)
+Binary an, falls Sie Dovecot v2.0 verwenden.
+.PP
+Dieses Binary wird zur Hash\-Erzeugung verwendet, wenn
+.I misc.password_scheme
+einen der nachfolgenden Werte hat: 'CRAM\-MD5', 'HMAC\-MD5', 'LANMAN',
+\(aqOTP', 'RPA' oder 'SKEY'.
+Dieses Binary wird auch benötigt, wenn Ihre Python\-Installation einen der
+folgenden Hash\-Algorithmen nicht unterstützt:
+.IP \(bu 4
+md4: (hashlib + OpenSSL oder PyCrypto) verwendet für die Passwort\-Schemen:
+\(aqPLAIN\-MD4' und 'NTLM'
+.IP \(bu
+sha256: (hashlib oder PyCrypto \(>= 2.1.0alpha1) verwendet für die
+Passwort\-Schemen: 'SHA256' und 'SSHA256'
+.IP \(bu
+sha512: (hashlib) verwendet für die Passwort\-Schemen: 'SHA512' und
+\(aqSSHA512'
+.PP
+Das
+.BR doveadm (1)
+Binary wird auch gebraucht, um die INBOX und zusätzliche Mailboxen
+.RI ( mailbox.folders )
+für einen neuen Account zu erstellen, wenn die Option
+.I mailbox.format
+den Wert
+.BR mdbox " oder " sdbox
+hat.
+.\" ------------------------------------
+.SS bin.du
+.BR du " (Vorgabe: /usr/bin/du) :"
+.I String
+.PP
+Der absolute Pfad zu
+.BR du (1).
+Dieses Binary wird verwendet, wenn die Festplattenbelegung eines Kontos
+ermittelt wird.
+.\" ------------------------------------
+.SS bin.postconf
+.BR postconf " (Vorgabe: /usr/sbin/postconf) :"
+.I String
+.PP
+Der absolute Pfad zu Postfix'
+.BR postconf (1).
+Dieses Binary wird verwendet, wenn
+.BR vmm (1)
+diverse Postfix\-Einstellungen prüft, zum Beispiel das
+.IR virtual_alias_expansion_limit .
+.\" -----------------------------------------------------------------------
+.SH SEKTION DATABASE
+Die
+.BR database \-Sektion
+wird verwendet, um die für den Datenbankzugriff erforderlichen Optionen
+festzulegen.
+.SS database.host
+.BR host " (Vorgabe: localhost) :"
+.I String
+.PP
+Der Hostname oder die IP\-Adresse des Datenbankservers.
+.\" ------------------------------------
+.SS database.module
+.BR module " (Vorgabe: psycopg2) :"
+.I String
+.PP
+Das für den Datenbankzugriff zu verwendende Python PostgreSQL Adapter
+Modul.
+Unterstützte Module sind
+.BR psycopg2 " und " pyPgSQL .
+.\" ------------------------------------
+.SS database.name
+.BR name " (Vorgabe: mailsys) :"
+.I String
+.PP
+Der Name der zu verwendenden Datenbank.
+.\" ------------------------------------
+.SS database.pass
+.BR pass " (Vorgabe: " None ") :"
+.I String
+.PP
+Das Passwort des Datenbank\-Benutzers.
+.\" ------------------------------------
+.SS database.port
+.BR port " (Vorgabe: 5432) :"
+.I Int
+.PP
+Der TCP\-Port, auf dem der Datenbankserver Verbindungen annimmt.
+.\" ------------------------------------
+.SS database.sslmode
+.BR sslmode " (Vorgabe: prefer) :"
+.I String
+.PP
+Bestimmt, ob und mit welcher Priorität eine SSL\-Verbindung mit dem
+Datenbankserver ausgehandelt wird.
+Mögliche Werte sind:
+.BR disabled ", " allow ", " prefer ", " require ", " verify\-ca " und "
+.BR verify\-full .
+Die Modi
+.BR verify\-ca " und " verify\-full
+stehen seit PostgreSQL 8.4 zur Verfügung.
+.PP
+Diese Option wird ignoriert, wenn das
+.I database.module
+.B pyPgSQL
+verwendet wird.
+.\" ------------------------------------
+.SS database.user
+.BR user " (Vorgabe: " None ") :"
+.I String
+.PP
+Der Name des Datenbank\-Benutzers.
+.\" -----------------------------------------------------------------------
+.SH SEKTION DOMAIN
+In der
+.BR domain \-Sektion
+werden Domain\-spezifische Einstellungen hinterlegt.
+.PP
+Das Quota\-Limit (quota_bytes und quota_messages), Service\-Einstellungen
+(imap,  pop3, sieve und smtp) und der Transport werden angewendet, wenn
+eine Domain angelegt wird.
+Um die Einstellungen einer vorhandenen Domain zu ändern, verwenden Sie
+einen der folgenden
+.BR vmm (1)
+Unterbefehle:
+.PP
 .TP
-\fBdelete\fP (\fIBoolean\fP)
-Bestimmt, ob das Maildir rekursiv gelöscht werden soll, wenn ein Konto gelöscht
-wird.
-.TP
-\fBBeispiel\fP:
-[maildir]
-.br
-name = Maildir
-.br
-folders = Drafts:Sent:Templates:Trash:INBOX.News
-.br
-mode = 448
-.br
-diskusage = false
-.br
-delete = false
-.\" -----
-.SH SERVICES ABSCHNITT
-Im \fBservices\fP-Abschnitt werden die Standard-Beschränkungen für alle Konten
-festgelegt.
-.TP
-\fBsmtp\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per SMTP einloggen kann.
-.TP
-\fBpop3\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per POP3 einloggen kann.
+.B domainquota
+um das Quota\-Limit einer Domain zu ändern
 .TP
-\fBimap\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per IMAP einloggen kann.
-.TP
-\fBsieve\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per MANAGESIEVE einloggen kann.
-.TP
-\fBBeispiel\fP:
-[services]
-.br
-smtp = true
-.br
-pop3 = true
-.br
-imap = false
-.br
-sieve = false
-.\" -----
-.SH DOMDIR ABSCHNITT
-Im \fBdomdir\fP-Abschnitt werden die Optionen der Domain-Verzeichnisse bestimmt.
-.TP
-\fBbase\fP (\fIString\fP)
-Alle Domain-Verzeichnisse werden unterhalb dieses Basis-Verzeichnisses angelegt.
-.TP
-\fBmode\fP (\fIInt\fP)
-Zugriffsbits des Domain-Verzeichnisses in Dezimal-Schreibweise (Basis 10).
-.br
-Beispiel: 'drwxrwx---' -> oktal 0770 -> dezimal 504
+.B domainservices
+um einer Domain einen abweichenden Satz von nutzbaren Services zuzuweisen
 .TP
-\fBdelete\fP (\fIBoolean\fP)
-Bestimmt, ob beim Löschen einer Domain das Verzeichnis einer Domain, inklusive
-aller Anwender-Verzeichnisse, rekursiv gelöscht werden soll.
-.TP
-\fBBeispiel\fP:
-[domdir]
+.B domaintransport
+um einen neuen Vorgabe\-Transport für eine Domain festzulegen
+.PP
+Wenn ein Account angelegt wird, erbt er alle Einstellungen von der Domain,
+zu der er hinzugefügt wird.
+Abweichende Einstellungen für einen vorhandenen Account nehmen Sie mit
+einem der Unterbefehle
+.BR userquota ", " userservices " und " usertransport
+vor.
+.\" ------------------------------------
+.SS domain.auto_postmaster
+.BR auto_postmaster " (Vorgabe: true) :"
+.I Boolean
+.PP
+Ist der Wert dieser Option
+.IR true ,
+wird
+.BR vmm (1)
+beim Anlegen einer Domain (domainadd) automatisch einen postmaster\-Account
+erstellen.
+.\" ------------------------------------
+.SS domain.delete_directory
+.BR delete_directory " (Vorgabe: false) :"
+.I Boolean
+.PP
+Legt fest, ob beim Löschen einer Domain (domaindelete) das Verzeichnis der
+zu löschenden Domain, inklusive aller Anwender\-Verzeichnisse, rekursiv
+gelöscht werden soll.
+.\" ------------------------------------
+.SS domain.directory_mode
+.BR directory_mode " (Vorgabe: 504) :"
+.I Int
+.PP
+Zugriffsbits des Domain\-Verzeichnisses in Dezimal\-Schreibweise
+(Basis 10).
 .br
-base = /srv/mail
-.br
-mode = 504
+Beispiel: 'drwxrwx\-\-\-' \(-> oktal 0770 \(-> dezimal 504
+.\" ------------------------------------
+.SS domain.force_deletion
+.BR force_deletion " (Vorgabe: false) :"
+.I Boolean
+.PP
+Erzwingt das Löschen aller zugeordneten Konten und Aliase beim Löschen
+einer Domain (domaindelete).
+.\" ------------------------------------
+.SS domain.imap
+.BR imap " (Vorgabe: true) :"
+.I Boolean
+.PP
+Legt fest, ob sich neu angelegte Benutzer per IMAP anmelden können sollen.
+.\" ------------------------------------
+.SS domain.pop3
+.BR pop3 " (Vorgabe: true) :"
+.I Boolean
+.PP
+Legt fest, ob sich neu angelegte Benutzer per POP3 anmelden können sollen.
+.\" ------------------------------------
+.SS domain.quota_bytes
+.BR quota_bytes " (Vorgabe: 0) :"
+.I String
+.PP
+Quota Limit in Bytes.
+0 bedeutet unbegrenzt.
+Dieses Limit wird beim Anlegen von Domains angewendet.
+.PP
+Der Wert dieser Option kann als Integer\-Wert, zum Beispiel
+.B 20480
+geschrieben werden.
+Es ist auch möglich dem Wert eines der folgenden
+Suffixe anzuhängen:
+.BR b " (Bytes), " k " (Kilobytes), " M " (Megabytes) oder " G
+(Gigabytes).
 .br
-delete = false
-.\" -----
-.SH BIN ABSCHNITT
-Der \fBbin\fP-Abschnitt wird verwendet, um Pfade zu Binaries, die von \fBvmm\fP
-benötigt werden, anzugeben.
-.TP
-\fBdovecotpw\fP (\fIString\fP)
-Der absolute Pfad zum dovecotpw-Binary. Dieses wird verwendet, wenn als
-Passwort-Schema eines der folgenden verwendet wird: 'SMD5', 'SSHA', 'CRAM-MD5',
-\'HMAC-MD5', 'LANMAN', 'NTLM' oder 'RPA'.
+1024 entspricht 1024b oder 1k.
+.\" ------------------------------------
+.SS domain.quota_messages
+.BR quota_messages " (Vorgabe: 0) :"
+.I Int
+.PP
+Quota Limit als Anzahl von Nachrichten.
+0 bedeutet unbegrenzt.
+Dieses Limit wird beim Anlegen neuer Domains angewendet.
+.\" ------------------------------------
+.SS domain.sieve
+.BR sieve " (Vorgabe: true) :"
+.I Boolean
+.PP
+Legt fest, ob sich neu angelegte Benutzer per SIEVE (ManageSieve) anmelden
+können sollen.
+.\" ------------------------------------
+.SS domain.smtp
+.BR smtp " (Vorgabe: true) :"
+.I Boolean
+.PP
+Legt fest, ob sich neu angelegte Benutzer per SMTP (SMTP AUTH) anmelden
+können sollen.
+.\" ------------------------------------
+.SS domain.transport
+.BR transport " (Vorgabe: dovecot:) :"
+.I String
+.PP
+Der Standard\-Transport aller neuen Domains.
+Siehe auch:
+.BR transport (5).
+.\" -----------------------------------------------------------------------
+.SH SEKTION MAILBOX
+In der
+.BR mailbox \-Sektion
+werden die für die Erstellung von Mailboxen erforderlichen Optionen
+festgelegt.
+Die INBOX wird in jedem Fall erstellt.
+.SS mailbox.folders
+.BR folders " (Vorgabe: Drafts:Sent:Templates:Trash) :"
+.I String
+.PP
+Eine durch Doppelpunkte getrennte Liste mit Namen der zu erstellenden
+Mailboxen.
+Sollen keine zusätzlichen Mailboxen angelegt werden, ist dieser Option ein
+einzelner Doppelpunkt
+.RB (' : ')
+als Wert zuzuweisen.
+.PP
+Sollen Verzeichnisse mit Unterverzeichnissen angelegt werden, ist ein
+einzelner Punkt
+.RB (' . ')
+als Separator zu verwenden.
+.PP
+Sollen Mailboxen mit internationalisierten Namen erstellt werden (zum
+Beispiel: 'Wysłane' oder 'Gelöschte Objekte'), ist der Name UTF\-8 kodiert
+anzugeben.
+.BR vmm (1)
+wird die internationalisierten Mailboxnamen in eine modifizierten Variante
+des UTF\-7\-Zeichensatzes (siehe auch: RFC 3501, Sektion 5.1.3)
+konvertieren.
+.\" ------------------------------------
+.SS mailbox.format
+.BR format " (Vorgabe: maildir) :"
+.I String
+.PP
+Das zu verwendende Mailbox\-Format für die Mailboxen der Benutzer.
+Abhängig von der verwendeten Dovecot\-Version
+.RI ( misc.dovecot_version ),
+unterstützt
+.BR vmm (1)
+bis zu drei Formate:
+.TP 8
+.B maildir
+Dovecot \(>= v1.0.0
 .TP
-\fBdu\fP (\fIString\fP)
-Der absolute Pfad zu \fBdu\fR(1). Dieses Binary wird verwendet, wenn die
-Festplattenbelegung eines Kontos ermittelt wird.
-.TP
-\fBpostconf\fP (\fIString\fP)
-Der absolute Pfad zu Postfix' \fBpostconf\fR(1).
-.br
-Dieses Binary wird verwendet, wenn \fBvmm\fR(1) diverse Postfix-Einstellungen
-prüft, zum Beispiel virtual_alias_expansion_limit.
-.TP
-\fBBeispiel\fP:
-[bin]
-.br
-dovecotpw = /usr/sbin/dovecotpw
-.br
-du = /usr/bin/du
-.br
-postconf = /usr/sbin/postconf
-.\" -----
-.SH MISC ABSCHNITT
-Im \fBmisc\fP-Abschnitt werden verschiedene Einstellungen festgelegt.
-.TP
-\fBpasswdscheme\fP (\fIString\fP)
-Das zu verwendende Passwort-Schema (siehe auch: dovecotpw -l)
-.TP
-\fBgid_mail\fP (\fIInt\fP)
-Die numerische Gruppen-ID der Gruppe mail, bzw. der Gruppe aus
-mail_privileged_group der Datei dovecot.conf.
+.B mdbox
+Dovecot \(>= v2.0.beta5
 .TP
-\fBforcedel\fP (\fIBoolean\fP)
-Legt fest, ob beim Löschen einer Domain alle vorhanden Konten und/oder Aliase,
-ohne Nachfrage, gelöscht werden sollen.
-.TP
-\fBtransport\fP (\fIString\fP)
-Der Standard-Transport aller Domains und Konten.
-.TP
-\fBdovecotvers\fP (\fIInt\fP)
-Die verketteten Major- und Minor-Teile der eingesetzten Dovecot-Version
-(siehe: dovecot --version).
-.br
-Diese Option beeinflusst diverse Datenbankzugriffe. Da es zwischen Dovecot
-v1.1.x und v1.2.x einige Änderungen gab. Zum Beispiel \fB11\fP, falls
-\fBdovecot --version\fP den Wert \fB1.1\fP.18 ausgibt.
-.TP
-\fBBeispiel\fP:
+.B sdbox
+Dovecot \(>= v2.0.rc3
+.\" ------------------------------------
+.SS mailbox.root
+.BR root " (Vorgabe: Maildir) :"
+.I String
+.PP
+Name des Mailbox\-Wurzelverzeichnisses im Home\-Verzeichnis des jeweiligen
+Benutzers.
+Übliche Namen, je nach verwendetem
+.IR mailbox.format ,
+sind
+.BR Maildir ", " mdbox " or " sdbox .
+.\" ------------------------------------
+.SS mailbox.subscribe
+.BR subscribe " (Vorgabe: true) :"
+.I Boolean
+.PP
+Wenn dieser Option der Wert
+.B true
+zugewiesen wurde, werden die, gemäß
+.IR mailbox.folders ,
+erstellen Mailboxen in der subscriptions\-Datei des Benutzers gelistet.
+Sollen die erstellen Mailboxen nicht nicht in der subscriptions\-Datei
+gelistet werden, weisen Sie dieser Option den Wert
+.B false
+zu.
+.\" -----------------------------------------------------------------------
+.SH SEKTION MISC
+In der
+.BR misc \-Sektion
+werden Einstellungen für verschiedene Bereiche festgelegt.
+.SS misc.base_directory
+.BR base_directory " (Vorgabe: /srv/mail) :"
+.I String
+.PP
+Alle Domain\-Verzeichnisse werden innerhalb dieses Basis\-Verzeichnisses
+angelegt.
+.\" ------------------------------------
+.SS misc.crypt_blowfish_rounds
+.BR crypt_blowfish_rounds " (Vorgabe: 5) :"
+.I Int
+.PP
+Anzahl der Verschlüsselungsdurchgänge für das
+.I password_scheme
+.BR BLF\-CRYPT .
+.PP
+Der Wert muss im Bereich von
+.BR 4 " \- " 31
+liegen.
+.\" ------------------------------------
+.SS misc.crypt_sha256_rounds
+.BR crypt_sha256_rounds " (Vorgabe: 5000) :"
+.I Int
+.PP
+Anzahl der Verschlüsselungdurchgänge für das
+.I password_scheme
+.BR SHA256\-CRYPT .
+.PP
+Der Wert muss im Bereich von
+.BR 1000 " \- " 999999999
+liegen.
+.\" ------------------------------------
+.SS misc.crypt_sha512_rounds
+.BR crypt_sha512_rounds " (Vorgabe: 5000) :"
+.I Int
+.PP
+Anzahl der Verschlüsselungdurchgänge für das
+.I password_scheme
+.BR SHA512\-CRYPT .
+.PP
+Der Wert muss im Bereich von
+.BR 1000 " \- " 999999999
+liegen.
+.\" ------------------------------------
+.SS misc.dovecot_version
+.BR dovecot_version " (Vorgabe: " None ") :"
+.I String
+.PP
+Die aktuell eingesetzte Dovecot\-Version.
+(siehe:
+.BR "dovecot \-\-version" ).
+
+Wenn das Kommando
+.B dovecot \-\-version
+zum Beispiel
+.I 2.0.beta4 (8818db00d347)
+ausgibt, ist dieser Option der Wert
+.B 2.0.beta4
+zuzuweisen.
+.\" ------------------------------------
+.SS misc.password_scheme
+.BR password_scheme " (Vorgabe: CRAM\-MD5) :"
+.I String
+.PP
+Das zu verwendende Passwort\-Schema.
+Um eine Liste aller verwendbaren Passwort\-Schemen zu erhalten, führen Sie
+das Kommando
+.B vmm lp
+aus.
+.PP
+Seit Dovecot \(>= v1.1.alpha1 ist es möglich, dem
+.I password_scheme
+ein Encoding\-Suffix anzufügen.
+Unterstützte Encoding\-Suffixe:
+.BR .b64 ", " .base64 " und " .hex .
+Beispiel: PLAIN.BASE64
+.\" -----------------------------------------------------------------------
+.SH BEISPIEL
+Eine Beispiel\-Konfiguration.
+Alle Optionen, die nicht in der Konfigurationsdatei gelistet sind, haben
+ihren Vorgabewert.
+.PP
+.nf
+[account]
+password_length = 10
+random_password = true
+
+[bin]
+dovecotpw = /usr/bin/doveadm
+
+[database]
+host = dbsrv8.example.net
+pass = PY_SRJ}L/0p\-oOk
+port = 5433
+sslmode = require
+user = vmm
+
+[domain]
+quota_bytes = 500M
+quota_messages = 10000
+transport = lmtp:unix:private/dovecot\-lmtp
+
+[mailbox]
+folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
+
 [misc]
-.br
-passwdscheme = CRAM-MD5
-.br
-gid_mail = 8
-.br
-forcedel = false
-.br
-transport = dovecot:
-.br
-dovecotvers = 11
-.\" -----
-.SH CONFIG ABSCHNITT
-Beim \fBconfig\fP-Abschnitt handelt es sich um einen internen
-Steuerungs-Abschnitt.
+crypt_sha512_rounds = 10000
+dovecot_version = 2.0.beta4
+password_scheme = SHA512\-CRYPT.hex
+.fi
+.\" -----------------------------------------------------------------------
+.SH SIEHE AUCH
+.BR postconf (1),
+.BR vmm (1),
+.BR transport (5)
+.\" -----------------------------------------------------------------------
+.SH INTERNET RESSOURCEN
 .TP
-\fBdone\fP (\fIBoolean\fP)
-Diese Option hat den den Wert \fIfalse\fP, wenn vmm zum ersten Mal installiert
-wurde. Wenn die Datei \fIvmm.cfg\fP von Hand editiert wird, weisen Sie dieser
-Option abschließend den Wert \fItrue\fP zu.
-.br
-Wird die Konfiguration über das Kommando \fBvmm configure\fP angepasst, wird der
-Wert dieser Option automatisch auf \fItrue\fP gesetzt.
-.br
-Sollte diese Option den Wert \fIfalse\fP zugewiesen haben, so startet \fBvmm\fP
-beim nächsten Aufruf im interaktiven Konfigurations-Modus.
+Wiki
+http://de.vmm.localdomain.org/
+.TP
+Projekt\-Seite
+http://sf.net/projects/vmm/
 .TP
-\fBBeispiel\fP:
-[config]
-.br
-done = true
-.\" -----
-.SH DATEIEN
-vmm.cfg
-.SH SIEHE AUCH
-vmm(1), Programm für die Kommandozeile, um E-Mail-Domains, -Konten und -Aliase
-zu verwalten.
-.SH AUTOR
-\fBvmm\fP und die dazugehörigen Manualseiten wurden von Pascal Volk
-<\fIneverseen@users.sourceforge.net\fP> geschrieben und sind unter den
-Bedingungen der BSD Lizenz lizenziert.
+Bugtracker
+https://bitbucket.org/pvo/vmm/issues
+.\" -----------------------------------------------------------------------
+.SH COPYING
+vmm und die dazugehörigen Manualseiten wurden von Pascal Volk <user+vmm AT
+localhost.localdomain.org> geschrieben und sind unter den Bedingungen der
+BSD Lizenz lizenziert.
--- a/man/man1/vmm.1	Mon Nov 07 03:22:15 2011 +0000
+++ b/man/man1/vmm.1	Thu Jun 28 19:26:50 2012 +0000
@@ -1,423 +1,977 @@
-.TH "VMM" "1" "17 Aug 2009" "Pascal Volk"
+.TH "VMM" "1" "2012-04-15" "vmm 0.6" "vmm"
 .SH NAME
 vmm \- command line tool to manage email domains/accounts/aliases
+.\" -----------------------------------------------------------------------
 .SH SYNOPSIS
 .B vmm
-\fIsubcommand\fP \fIobject\fP [ \fIargs\fP ]
+.IR subcommand " [" "argument ..." ]
+.\" -----------------------------------------------------------------------
 .SH DESCRIPTION
-\fBvmm\fP (Virtual Mail Manager) is a command line tool for
-administrators/postmasters to manage domains, accounts and aliases. It's
-designed for Dovecot and Postfix with a PostgreSQL backend.
-.SH SUBCOMMANDS
-Each subcommand has both a long and a short form. Both forms are case sensitive.
-.SS GENERAL SUBCOMMANDS
+.B vmm
+(a virtual mail manager) is the easy to use command line tool for
+administrators and postmasters to manage (alias) domains, accounts, aliases
+and relocated users.
+It allows you to simply and quickly administer your mail server.
+.br
+It's designed for Dovecot and Postfix with a PostgreSQL backend.
+.PP
+Each
+.I subcommand
+has both a long and a short form.
+The short form is shown enclosed in parentheses.
+Both forms are case sensitive.
+.PP
+Most of the
+.IR subcommand s
+take one or more
+.IR argument s.
+.\" -----------------------------------------------------------------------
+.SH ARGUMENTS
+.TP 12
+.I address
+The complete e\-mail address
+.RI ( local\-part @ fqdn )
+of an user account, alias address or relocated user.
+.\" --------------------------
 .TP
-\fBconfigure\fP (\fBcf\fP) [ \fIsection\fP ]
-Starts the interactive configuration for all configuration sections.
-.br
-If the optional argument \fIsection\fP is given, only the configuration options
-from the given section will be displayed and will be configurable. The following
-sections are available:
-.RS
-.PD 0
+.I destination
+Is either an e\-mail
+.I address
+when used with
+.IR "ALIAS SUBCOMMANDS" .
+Or a
+.I fqdn
+when used with
+.IR "ALIASDOMAIN SUBCOMMANDS" .
+.\" --------------------------
 .TP
--
-.B
-database
+.I fqdn
+The fully qualified domain name \- without the trailing dot \- of a domain
+or alias domain.
+.\" --------------------------
 .TP
--
-.B
-maildir
+.I messages
+An integer value which specifies a quota limit in number of messages.
+.B 0
+(zero) means unlimited \- no quota limit for the number of messages.
+.\" --------------------------
 .TP
--
-.B
-services
+.I option
+is the name of a configuration option, prefixed with the section name and a
+dot.
+For example:
+.IB misc . transport
+.br
+All configuration options are described in
+.BR vmm.cfg (5).
+.\" --------------------------
+.TP
+.I service
+The name of a service, commonly used with Dovecot.
+Supported services are:
+.BR imap ", " pop3 ", " sieve " and " smtp .
+.\" --------------------------
 .TP
--
-.B
-domdir
+.I storage
+Specifies a quota limit in bytes.
+One of the following prefixes can be appended to the integer value:
+.BR b " (bytes), " k " (kilobytes), " M " (megabytes) or " G
+(gigabytes).
+.B 0
+(zero) means unlimited \- no quota limit in bytes.
+.\" --------------------------
 .TP
--
-.B
-bin
-.TP
--
-.B
-misc
-.PD
-.RE
-.LP
+.I transport
+A transport for Postfix, written as:
+.IB transport :
+or
+.IB transport :\c
+.IR nexthop .
+See
+.BR transport (5)
+for more details.
+.\" -----------------------------------------------------------------------
+.SH GENERAL SUBCOMMANDS
+.SS configget (cg)
+.BI "vmm configget" " option"
+.PP
+This subcommand is used to display the actual value of the given
+configuration
+.IR option .
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm configure services\fP
-        Using configuration file: /usr/local/etc/vmm.cfg
-
-        * Config section: “services”
-        Enter new value for option pop3 [True]: 
-        Enter new value for option smtp [True]: 
-        Enter new value for option imap [True]: 
-        Enter new value for option sieve [True]: false
+.B vmm configget misc.crypt_sha512_rounds
+misc.crypt_sha512_rounds = 5000
+.fi
+.\" ------------------------------------
+.SS configset (cs)
+.B vmm configset
+.I option value
+.PP
+Use this subcommand to set or update a single configuration option's value.
+.I option
+is the configuration option,
+.I value
+is the
+.IR option 's
+new value.
+.IP Note:
+This subcommand will create a new
+.I vmm.cfg
+without any comments.
+Your current configuration file will be backed as
+.IR vmm.cfg.bak .
+.PP
+Example:
+.PP
+.nf
+.B vmm configget domain.transport
+domain.transport = dovecot:
+.B vmm configset domain.transport lmtp:unix:private/dovecot\-lmtp
+.B vmm cg domain.transport
+domain.transport = lmtp:unix:private/dovecot\-lmtp
 .fi
+.\" ------------------------------------
+.SS configure (cf)
+.B vmm configure
+.RI [ section ]
 .PP
+Starts the interactive configuration for all configuration sections.
+.PP
+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.
+.PP
+If the optional argument
+.I section
+is given, only the configuration options from the given section will be
+displayed and will be configurable.
+The following sections are available:
+.RS
+.TP 10
+.B account
+Account settings
 .TP
-\fBgetuser\fP (\fBgu\fP) \fIuserid\fP
-If only the userid is available, for example from process list, the subcommand
-\fBgetuser\fP will show the user's address.
+.B bin
+Paths to external binaries
+.TP
+.B database
+Database settings
+.TP
+.B domain
+Domain settings
+.TP
+.B mailbox
+Mailbox settings
+.TP
+.B misc
+Miscellaneous settings
+.RE
+.PP
+All configuration options are described in
+.BR vmm.cfg (5).
+.IP Note:
+This subcommand will create a new
+.I vmm.cfg
+without any comments.
+Your current configuration file will be backed as
+.IR vmm.cfg.bak .
+.PP
+Example:
 .PP
 .nf
-        Example:
+.B vmm configure mailbox
+Using configuration file: /usr/local/etc/vmm.cfg
 
-        \fBvmm getuser 70004\fP
-        Account information
-        -------------------
-                UID............: 70004
-                GID............: 70000
-                Address........: c.user@example.com
+* Configuration section: `mailbox'
+Enter new value for option folders [Drafts:Sent:Templates:Trash]:
+Enter new value for option format [maildir]: mdbox
+Enter new value for option subscribe [True]:
+Enter new value for option root [Maildir]: mdbox
+.fi
+.\" ------------------------------------
+.SS getuser (gu)
+.BI "vmm getuser" " uid"
+.PP
+If only the
+.I uid
+is available, for example from process list, the subcommand
+.B getuser
+will show the user's address.
+.PP
+Example:
+.PP
+.nf
+.B vmm getuser 79876
+Account information
+-------------------
+        UID............: 79876
+        GID............: 70704
+        Address........: a.user@example.com
 .fi
-.\"
-.TP
-\fBlistdomains\fP (\fBld\fP) [ \fIpattern\fP ]
-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 \fIpattern\fP.
+.\" ------------------------------------
+.SS help (h)
+.B vmm help
+.RI [ subcommand ]
+.PP
+Prints a list of available subcommands with a short description to stdout.
+When a
+.I subcommand
+was given, help for that
+.I subcommand
+will be displayed.
+After this
+.B vmm
+exits.
+.\" ------------------------------------
+.SS listdomains (ld)
+.B vmm listdomains
+.RI [ pattern ]
+.PP
+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
+.IR pattern .
+.PP
+To perform a wild card search, the % character can be used at the start
+and/or the end of the
+.IR pattern .
+.PP
+Example:
+.PP
+.nf
+.B vmm listdomains %example%
+Matching domains
+----------------
+        [+] example.com
+        [\-]     e.g.example.com
+        [\-]     example.name
+        [+] example.net
+        [+] example.org
+.fi
+.\" ------------------------------------
+.SS listpwschemes (lp)
+.B vmm listpwschemes
+.PP
+This subcommand lists all password schemes which could be used in the
+.I vmm.cfg
+as value of the
+.I misc.password_scheme
+option.
+The output varies, depending on the used Dovecot version and the system's
+libc.
 .br
-To perform a wild card search, the % character can be used at the start and/or
-the end of the \fIpattern\fP.
+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.
+.PP
+Example:
 .PP
 .nf
-        Example:
+.B vmm listpwschemes
+Usable password schemes
+-----------------------
+        CRYPT SHA512-CRYPT LDAP-MD5 DIGEST-MD5 SHA256 SHA512 SSHA512
+        SKEY SSHA NTLM RPA MD5-CRYPT HMAC-MD5 SHA1 PLAIN SHA CRAM-MD5
+        SSHA256 MD5 LANMAN CLEARTEXT PLAIN-MD5 PLAIN-MD4 OTP SMD5
+        SHA256-CRYPT
 
-        \fBvmm listdomains %example%\fP
-        Matching domains
-        ----------------
-                [+] example.com
-                [-]     e.g.example.com
-                [-]     example.name
-                [+] example.net
-                [+] example.org
+Usable encoding suffixes
+------------------------
+        .B64 .BASE64 .HEX
 .fi
-.\"
-.TP
-\fBhelp\fP (\fBh\fP)
-Prints all available commands to stdout. After this \fBvmm\fP exits.
-.TP
-\fBversion\fP (\fBv\fP)
-Prints the version information from \fBvmm\fP.
-.\"
-.SS DOMAIN SUBCOMMANDS
-.TP
-\fBdomainadd\fP (\fBda\fP) \fIdomain\fP [ \fItransport\fP ]
-Adds the new \fIdomain\fP into the database.
-.br
-If the optional argument \fItransport\fP is given, it will overwrite the
-default transport from \fBvmm.cfg\fP (misc/transport). The specified transport
+.\" ------------------------------------
+.SS version (v)
+.B vmm version
+.PP
+Prints
+.BR vmm 's
+version and copyright information to stdout.
+After this
+.B vmm
+exits.
+.\" -----------------------------------------------------------------------
+.SH DOMAIN SUBCOMMANDS
+.SS domainadd (da)
+.B vmm domainadd
+.IR fqdn " [" transport ]
+.PP
+Adds the new domain into the database and creates the domain directory.
+.PP
+If the optional argument
+.I transport
+is given, it will override the default transport
+.RI ( domain.transport ") from " vmm.cfg .
+The specified
+.I transport
 will be the default transport for all new accounts in this domain.
 .PP
-.nf
-        Examples:
-
-        \fBvmm domainadd support.example.com smtp:mx1.example.com
-        vmm domainadd sales.example.com\fP
-.fi
+Configuration\-related behavior:
+.RS
+.TP
+.I domain.auto_postmaster
+When that option is set to
+.BR true " (default) " vmm
+will automatically create the postmaster account for the new domain and
+prompt for
+.BI postmaster@ fqdn\c
+\(aqs password.
 .TP
-\fBdomaininfo\fP (\fBdi\fP) \fIdomain\fP [ \fIdetails\fP ]
-This subcommand shows some information about the given \fIdomain\fP.
-.br
-For a more detailed information about the \fIdomain\fP the optional argument
-\fIdetails\fP can be specified. A possible \fIdetails\fP value may be one of
-the following five keywords:
+.I account.random_password
+When the value of that option is also set to
+.BR true ", " vmm
+will automatically create the postmaster account for the new domain and
+print the generated postmaster password to stdout.
+.RE
+.PP
+Examples:
+.PP
+.nf
+.B vmm domainadd support.example.com smtp:[mx1.example.com]:2025
+Creating account for postmaster@support.example.com
+Enter new password:
+Retype new password:
+.B vmm cs account.random_password true
+.B vmm domainadd sales.example.com
+Creating account for postmaster@sales.example.com
+Generated password: pLJUQ6Xg_z
+.fi
+.\" ------------------------------------
+.SS domaindelete (dd)
+.BI "vmm domaindelete " fqdn
+.RB [ force ]
+.PP
+This subcommand deletes the domain specified by
+.IR fqdn .
+.PP
+If there are accounts, aliases and/or relocated users assigned to the given
+domain,
+.B vmm
+will abort the requested operation and show an error message.
+If you know, what you are doing, you can specify the optional keyword
+.BR force .
+.PP
+If you really always know what you are doing, edit your
+.I vmm.cfg
+and set the option
+.I domain.force_deletion
+to
+.BR true .
+.\" ------------------------------------
+.SS domaininfo (di)
+.B vmm domaininfo
+.IR fqdn \ [ details ]
+.PP
+This subcommand shows some information about the given domain.
+.PP
+For a more detailed information about the domain the optional argument
+.I details
+can be specified.
+A possible
+.I details
+value can be one of the following five keywords:
 .RS
-.PD 0
-.TP
+.TP 14
 .B accounts
-to list all existing accounts
+to list the e\-mail addresses of all existing user accounts
 .TP
 .B aliasdomains
-to list all assigned alias domains
+to list all assigned alias domain names
 .TP
 .B aliases
-to list all available aliases addresses
+to list all available alias e\-mail addresses
 .TP
 .B relocated
-to list all relocated users
+to list the e\-mail addresses of all relocated users
+.TP
+.B catchall
+to list all catch\-all destinations
 .TP
 .B full
 to list all information mentioned above
-.PD
 .RE
-.LP
+.PP
+Example:
+.PP
 .nf
-        Example:
-
-        \fBvmm domaininfo sales.example.com\fP
-        Domain information
-        ------------------
-                Domainname.....: sales.example.com
-                GID............: 70002
-                Transport......: dovecot:
-                Domaindir......: /home/mail/5/70002
-                Aliasdomains...: 0
-                Accounts.......: 0
-                Aliases........: 0
-                Relocated......: 0
-
+.B vmm domaininfo sales.example.com
+Domain information
+------------------
+        Domain Name......: sales.example.com
+        GID..............: 70708
+        Domain Directory.: /srv/mail/c/70708
+        Quota Limit/User.: Storage: 500.00 MiB; Messages: 10,000
+        Active Services..: IMAP SIEVE
+        Transport........: lmtp:unix:private/dovecot-lmtp
+        Alias Domains....: 0
+        Accounts.........: 1
+        Aliases..........: 0
+        Relocated........: 0
+        Catch-All Dests..: 1
 .fi
-.TP
-\fBdomaintransport\fP (\fBdt\fP) \fIdomain\fP \fItransport\fP [ \fIforce\fP ]
-A new transport for the indicated domain can be set with this subcommand.
+.\" ------------------------------------
+.SS domainquota (dq)
+.B vmm domainquota
+.IR "fqdn storage" " [" messages ]
+.RB [ force ]
+.PP
+This subcommand is used to configure a new quota limit for the accounts of
+the domain - not for the domain itself.
+.PP
+The default quota limit for accounts is defined in the
+.IR vmm.cfg " (" domain.quota_bytes " and " domain.quota_messages ).
+.PP
+The new quota limit will affect only those accounts for which the limit
+has not been overridden. If you want to restore the default to all accounts,
+you may pass the keyword
+.BR force .
 .br
-If the additional keyword '\fBforce\fP' is given all account specific transport
-settings will be overwritten.
-.br
-Otherwise this setting will affect only new created accounts.
+When the argument
+.I messages
+was omitted the default number of messages
+.B 0
+(zero) will be applied.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm domaintransport support.example.com dovecot:\fP
+.B vmm domainquota example.com 1g force
 .fi
-.TP
-\fBdomaindelete\fP (\fBdd\fP) \fIdomain\fP [ \fIdelalias\fP | \fIdeluser\fP | \fIdelall\fP ]
-This subcommand deletes the specified \fIdomain\fP.
+.\" ------------------------------------
+.SS domainservices (ds)
+.B vmm domainservices
+.IR fqdn " [" "service ..." ]
+.RB [ force ]
+.PP
+To define which services could be used by the users of the domain \(em with
+the given
+.I fqdn
+\(em use this subcommand.
+.PP
+Each specified
+.I service
+will be enabled/usable.
+All other services will be deactivated/unusable.
+Possible service names are: 
+.BR  imap ", " pop3 ", " sieve " and " smtp .
 .br
-If there are accounts and/or aliases assigned to the given domain, \fBvmm\fP
-will abort the requested operation and show an error message. If you know, what
-you are doing, you can specify one of the following keywords: '\fBdelalias\fP', '\fBdeluser\fP' or '\fBdelall\fP'.
-.br
-
-If you really always know what you are doing, edit your \fBvmm.cfg\fP and set
-the option \fIforcedel\fP, in section \fImisc\fP, to true.
-.\"
-.SS ALIAS DOMAIN SUBCOMMANDS
-.TP
-\fBaliasdomainaddd\fP (\fBada\fP) \fIaliasdomain\fP \fItargetdomain\fP
-This subcommand adds the new \fIaliasdomain\fP to the \fItargetdomain\fP that
-should be aliased.
+The new service set will affect only those accounts for which the set has not
+been overridden. If you want to restore the default to all accounts, you may
+pass the keyword
+.BR force .
+.\" ------------------------------------
+.SS domaintransport (dt)
+.BI "vmm domaintransport" " fqdn transport"
+.RB [ force ]
+.PP
+A new transport for the indicated domain can be set with this subcommand.
+.PP
+The new transport will affect only those accounts for which the transport has
+not been overridden. If you want to restore the default to all accounts, you
+may pass the keyword
+.BR force .
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm aliasdomainadd example.name example.com\fP
+.B vmm domaintransport support.example.com dovecot:
 .fi
-.TP
-\fBaliasdomaininfo (\fBadi\fP) \fIaliasdomain\fP
-This subcommand shows to which domain the \fIaliasdomain\fP is assigned to.
+.\" ------------------------------------
+.SS domainnote (do)
+.BI "vmm domainnote" " fqdn"
+.RI [ note ]
+.PP
+With this subcommand, it is possible to attach a note to the specified
+domain. Without an argument, an existing note is removed.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm aliasdomaininfo example.name\fP
-        Alias domain information
-        ------------------------
-                The alias domain example.name belongs to:
-                    * example.com
+.B vmm do example.com Belongs to Robert
 .fi
-.TP
-\fBaliasdomainswitch\fP (\fBads\fP) \fIaliasdomain\fP \fItargetdomain\fP
-If the target of the existing \fIaliasdomain\fP should be switched to another
-\fItargetdomain\fP use this subcommand.
+.\" -----------------------------------------------------------------------
+.SH ALIAS DOMAIN SUBCOMMANDS
+An alias domain is an alias for a domain that was previously added with the
+subcommand
+.BR domainadd .
+All accounts, aliases and relocated users from the domain will be also
+available in the alias domain.
+.br
+In the following is to be assumed that example.net is an alias for
+example.com.
+.PP
+Postfix will not accept erroneously e\-mails for unknown.user@example.net
+and bounce them back later to the mostly faked sender.
+Postfix will immediately reject all e\-mails addressed to nonexistent
+users.
+.br
+This behavior is ensured as long as you use the recommended database
+queries in your
+.I $config_directory/pgsql\-*.cf
+configuration files.
+.\" ------------------------------------
+.SS aliasdomainadd (ada)
+.BI "vmm aliasdomainadd" " fqdn destination"
+.PP
+This subcommand adds the new alias domain
+.RI ( fqdn )
+to the
+.I destination
+domain that should be aliased.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm aliasdomainswitch example.name example.org\fP
+.B vmm aliasdomainadd example.net example.com
 .fi
-.TP
-\fBaliasdomaindelete\fP (\fBadd\fP) \fIaliasdomain\fP
-Use this subcommand if the alias domain \fIaliasdomain\fP should be removed.
+.\" ------------------------------------
+.SS aliasdomaindelete (add)
+.BI "vmm aliasdomaindelete" " fqdn"
+.PP
+Use this subcommand if the alias domain
+.I fqdn
+should be removed.
+.PP
+Example:
+.PP
+.nf
+.B vmm aliasdomaindelete e.g.example.com
+.fi
+.\" ------------------------------------
+.SS aliasdomaininfo (adi)
+.BI "vmm aliasdomaininfo" " fqdn"
+.PP
+This subcommand shows to which domain the alias domain
+.I fqdn
+is assigned to.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm aliasdomaindelete e.g.example.com\fP
+.B vmm adi example.net
+Alias domain information
+------------------------
+        The alias domain example.net belongs to:
+            * example.com
+.fi
+.\" ------------------------------------
+.SS aliasdomainswitch (ads)
+.BI "vmm aliasdomainswitch" " fqdn destination"
+.PP
+If the destination of the existing alias domain
+.I fqdn
+should be switched to another
+.I destination
+use this subcommand.
+.nf
+.PP
+Example:
+.PP
+.B vmm aliasdomainswitch example.name example.org
 .fi
-.\"
-.SS ACCOUNT SUBCOMMANDS
-.TP
-\fBuseradd\fP (\fBua\fP) \fIaddress\fP [ \fIpassword\fP ]
-Use this subcommand to create a new email account for the given \fIaddress\fP.
-.br
-If the \fIpassword\fP is not provided, \fBvmm\fP will prompt for it
-interactively.
+.\" -----------------------------------------------------------------------
+.SH ACCOUNT SUBCOMMANDS
+.SS useradd (ua)
+.B vmm useradd
+.IR address " [" password ]
+.PP
+Use this subcommand to create a new e\-mail account for the given
+.IR address .
+.PP
+If the
+.I password
+is not provided,
+.B vmm
+will prompt for it interactively.
+When no
+.I password
+is provided and
+.I account.random_password
+is set to
+.BR true ", " vmm
+will generate a random password and print it to stdout after the account
+has been created.
+.PP
+Examples:
 .PP
 .nf
-        Examples:
-
-        \fBvmm ua d.user@example.com 'A 5ecR3t P4s5\\/\\/0rd'\fP
-        \fBvmm ua e.user@example.com\fP
-        Enter new password:
-        Retype new password:
+.B vmm ua d.user@example.com \(dqA 5ecR3t P4s5\(rs/\(rs/0rd\(dq
+.B vmm useradd e.user@example.com
+Enter new password:
+Retype new password:
 .fi
-.TP
-\fBuserinfo\fP (\fBui\fP) \fIaddress\fP [ \fIdetails\fP ]
+.\" ------------------------------------
+.SS userdelete (ud)
+.BI "vmm userdelete" " address"
+.RB [ force ]
+.PP
+Use this subcommand to delete the account with the given
+.IR address .
+.PP
+If there are one or more aliases with an identical destination address,
+.B vmm
+will abort the requested operation and show an error message.
+To prevent this, specify the optional keyword
+.BR force .
+.\" ------------------------------------
+.SS userinfo (ui)
+.B "vmm userinfo"
+.IR address " [" details ]
+.PP
 This subcommand displays some information about the account specified by
-\fIaddress\fP.
-.br
-If the optional argument \fIdetails\fP is given some more information will be
-displayed.
-.br
-Possible values for \fIdetails\fP are:
+.IR address .
+.PP
+If the optional argument
+.I details
+is given some more information will be displayed.
+Possible values for
+.I details
+are:
 .RS
-.PD 0
-.TP 
+.TP 8
 .B aliases
-to list all alias addresses with the destination \fIaddress\fP
+to list all alias addresses with the destination
+.I address
 .TP
 .B du
-to display the disk usage of users maildir
+to display the disk usage of the user's mail directory.
+In order to summarize the disk usage each time the this subcommand is
+executed automatically, set
+.I account.disk_usage
+in your
+.I vmm.cfg
+to
+.BR true .
 .TP
 .B full
 to list all information mentioned above
-.PD
 .RE
-.LP
-.TP
-\fBusername\fP (\fBun\fP) \fIaddress\fP \fI'Users Name'\fP
-The user's real name can be set/updated with this subcommand.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm un d.user@example.com 'John Doe'\fP
+.B vmm ui d.user@example.com
+Account information
+-------------------
+        Address..........: d.user@example.com
+        Name.............: None
+        UID..............: 79881
+        GID..............: 70704
+        Home.............: /srv/mail/2/70704/79881
+        Mail_Location....: mdbox:~/mdbox
+        Quota Storage....: [  0.00%] 0/500.00 MiB
+        Quota Messages...: [  0.00%] 0/10,000
+        Transport........: lmtp:unix:private/dovecot-lmtp
+        SMTP.............: disabled
+        POP3.............: disabled
+        IMAP.............: enabled
+        SIEVE............: enabled
 .fi
-.TP
-\fBuserpassword\fP (\fBup\fP) \fIaddress\fP [ \fIpassword\fP ]
-The \fIpassword\fP from an account can be updated with this subcommand.
-.br
-If the \fIpassword\fP is not provided, \fBvmm\fP will prompt for it
-interactively.
+.\" ------------------------------------
+.SS username (un)
+.BI "vmm username" " address"
+.RI [ name ]
+.PP
+The user's real
+.I name
+can be set/updated with this subcommand.
+.PP
+If no
+.I name
+is given, the value stored for the account is erased.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm up d.user@example.com 'A |\\/|0r3 5ecur3 P4s5\\/\\/0rd?'\fP
+.B vmm un d.user@example.com \(dqJohn Doe\(dq
 .fi
-.TP
-\fBusertransport\fP (\fBut\fP) \fIaddress\fP \fItransport\fP
-A different transport for an account can be specified with this subcommand.
+.\" ------------------------------------
+.SS userpassword (up)
+.BI "vmm userpassword" " address"
+.RI [ password ]
+.PP
+The password of an account can be updated with this subcommand.
+.PP
+If no
+.I password
+was provided,
+.B vmm
+will prompt for it interactively.
+.PP
+Example:
+.PP
+.nf
+.B vmm up d.user@example.com \(dqA |\(rs/|0r3 5ecur3 P4s5\(rs/\(rs/0rd?\(dq
+.fi
+.\" ------------------------------------
+.SS usernote (uo)
+.BI "vmm usernote" " address"
+.RI [ note ]
+.PP
+With this subcommand, it is possible to attach a note to the specified
+account. Without an argument, an existing note is removed.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm ut d.user@example.com smtp:pc105.it.example.com\fP
+.B vmm uo d.user@example.com Only needed until end of May 2012
 .fi
-.TP
-\fBuserdisable\fP (\fBu0\fP) \fIaddress\fP [ \fIservice\fP ]
-If a user shouldn't have access to one or all services you can restrict the 
-access with this subcommand.
-.br
-If neither a \fIservice\fP nor the keyword '\fIall\fP' is given all services
-(\fIsmtp\fP, \fIpop3\fP, \fIimap\fP, and \fIsieve\fP) will be disabled for the
-account with the specified \fIaddress\fP. Otherwise only the specified 
-\fIservice\fP will be restricted.
+.\" ------------------------------------
+.SS userquota (uq)
+.BI "vmm userquota" " address storage"
+.RI [ messages ]
+.PP
+This subcommand is used to set a new quota limit for the given account.
+.PP
+When the argument
+.I messages
+was omitted the default number of messages
+.B 0
+(zero) will be applied.
+.PP
+Instead of
+.I transport
+pass 'domain' to remove the account\-specific override, causing the
+domain's value to be in effect.
+.PP
+Example:
 .PP
 .nf
-        Examples:
-
-        \fBvmm u0 b.user@example.com imap\fP
-        \fBvmm userdisable c.user@example.com\fP
+.B vmm userquota d.user@example.com 750m
 .fi
-.TP
-\fBuserenable\fP (\fBu1\fP) \fIaddress\fP [ \fIservice\fP ]
-To allow access to one or all restricted services use this subcommand.
+.\" ------------------------------------
+.SS userservices (us)
+.B vmm userservices
+.IR address " [" "service ..." ]
+.PP
+To grant a user access to the specified services, use this command.
+.PP
+All omitted services will be deactivated/unusable for the user with the
+given
+.IR address .
+.PP
+Instead of
+.I transport
+pass 'domain' to remove the account\-specific override, causing the
+domain's value to be in effect.
+.PP
+Example:
+.PP\
+.nf
+.B vmm userservices d.user@example.com SMTP IMAP
+.\" ------------------------------------
+.SS usertransport (ut)
+.BI "vmm usertransport" " address transport"
+.PP
+A different
+.I transport
+for an account can be specified with this subcommand.
+.PP
+Instead of
+.I transport
+pass 'domain' to remove the account\-specific override, causing the
+domain's value to be in effect.
+.PP
+Example:
 .br
-If neither a \fIservice\fP nor the keyword '\fIall\fP' is given all services
-(\fIsmtp\fP, \fIpop3\fP, \fIimap\fP, and \fIsieve\fP) will be enabled for the
-account with the specified \fIaddress\fP. Otherwise only the specified 
-\fIservice\fP will be enabled.
+Assumed you want to use Dovecot's
+.BR dsync (1)
+to convert a user's mailbox from Maildir format to mdbox format, you
+can tell Postfix to retry later.
+.PP
+.nf
+.B vmm ut d.user@example.com \(dqretry:4.0.0 Mailbox being migrated\(dq
+# convert the mailbox ... then set the transport to Dovecot's lmtp
+.B vmm ut d.user@example.com lmtp:unix:private/dovecot\-lmtp
+.fi
+.\" -----------------------------------------------------------------------
+.SH ALIAS SUBCOMMANDS
+.SS aliasadd (aa)
+.BI "vmm aliasadd" " address destination ..."
 .PP
-.TP
-\fBuserdelete\fP (\fBud\fP) \fIaddress\fP [ \fIdelalias\fP ]
-Use this subcommand to delete the account with the given \fIaddress\fP.
-.br
-If there are one or more aliases with an identical destination address,
-\fBvmm\fP will abort the requested operation and show an error message. To
-prevent this, specify the optional keyword '\fIdelalias\fP'.
-.\"
-.SS ALIAS SUBCOMMANDS
-.TP
-\fBaliasadd\fP (\fBaa\fP) \fIalias\fP \fItarget\fP
-This subcommand is used to create a new alias.
+This subcommand is used to create a new alias
+.I address
+with one or more
+.I destination
+addresses.
+.PP
+Within the destination address, the placeholders
+.IR %n ,
+.IR %d ,
+and
+.IR %=
+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.
+.PP
+Examples:
 .PP
 .nf
-        Examples:
-
-        \fBvmm aliasadd john.doe@example.com d.user@example.com\fP
-        \fBvmm aa support@example.com d.user@example.com\fP
-        \fBvmm aa support@example.com e.user@example.com\fP
+.B vmm aliasadd john.doe@example.com d.user@example.com
+.B vmm aa support@example.com d.user@example.com e.user@example.com
+.B vmm aa postmaster@example.com postmaster+%d@example.org
 .fi
-.TP
-\fBaliasinfo\fP (\fBai\fP) \fIalias\fP
-Information about an alias can be displayed with this subcommand.
+.\" ------------------------------------
+.SS aliasdelete (ad)
+.BI "vmm aliasdelete" " address"
+.RI [ destination ]
+.PP
+Use this subcommand to delete the alias with the given
+.IR address .
+.PP
+If the optional
+.I destination
+address is given, only this
+destination will be removed from the alias.
+.PP
+Example:
+.PP
+.nf
+.B vmm ad support@example.com d.user@example.com
+.fi
+.\" ------------------------------------
+.SS aliasinfo (ai)
+.BI "vmm aliasinfo" " address"
+.PP
+Information about the alias with the given
+.I address
+can be displayed with this subcommand.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm aliasinfo support@example.com\fP
-        Alias information
-        -----------------
-                Mail for support@example.com will be redirected to:
-                     * d.user@example.com
-                     * e.user@example.com
+.B vmm aliasinfo support@example.com
+Alias information
+-----------------
+        Mail for support@example.com will be redirected to:
+             * e.user@example.com
+.fi
+.\" -----------------------------------------------------------------------
+.SH RELOCATED SUBCOMMANDS
+.SS relocatedadd (ra)
+.BI "vmm relocatedadd" " address newaddress"
+.PP
+A new relocated user can be created with this subcommand.
+.PP
+.I address
+is the user's ex\-email address, for example b.user@example.com, and
+.I newaddress
+points to the new email address where the user can be reached.
+.PP
+Example:
+.PP
+.nf
+.B vmm relocatedadd b.user@example.com b\-user@company.tld
 .fi
-.TP
-\fBaliasdelete\fP (\fBad\fP) \fIalias\fP [ \fItarget\fP ]
-Use this subcommand to delete the \fIalias\fP.
-.br
-If the optional destination address \fItarget\fP is given, only this
-destination will be removed from the \fIalias\fP.
+.\" ------------------------------------
+.SS relocatedinfo (ri)
+.BI "vmm relocatedinfo " address
+.PP
+This subcommand shows the new address of the relocated user with the given
+.IR address .
+.PP
+Example:
+.PP
+.nf
+.B vmm relocatedinfo b.user@example.com
+Relocated information
+---------------------
+        User `b.user@example.com' has moved to `b\-user@company.tld'
+.fi
+.\" ------------------------------------
+.SS relocateddelete (rd)
+.BI "vmm relocateddelete " address
+.PP
+Use this subcommand in order to delete the relocated user with the given
+.IR address .
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm ad support@example.com d.user@example.com\fP
+.B vmm relocateddelete b.user@example.com
 .fi
-.\"
-.SS RELOCATED SUBCOMMANDS
-.TP
-\fBrelocatedadd\fP (\fBra\fP) \fIold_address\fP \fInew_address\fP
-A new relocated user can be created with this subcommand.
-.br
-\fIold_address\fP is the users ex-email address, for example b.user@example.com,
-and \fInew_address\fP points to the new email address where the user can be
-reached.
+.\" -----------------------------------------------------------------------
+.SH CATCH\-ALL SUBCOMMANDS
+.SS catchalladd (caa)
+.BI "vmm catchalladd" " fqdn destination ..."
+.PP
+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 \(dqcatch all\(dq mail to any address in the
+domain (unless a more specific alias, mailbox or relocated entry exists).
+.PP
+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.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm relocatedadd b.user@example.com b-user@company.tld\fP
+.B vmm catchalladd example.com user@example.org
 .fi
-.TP
-\fBrelocatedinfo\fP (\fBri\fP) \fIold_address\fP
-This subcommand shows the new address of the relocated user with the 
-\fIold_address\fP.
+.\" ------------------------------------
+.SS catchallinfo (cai)
+.BI "vmm catchallinfo " fqdn
+.PP
+This subcommand displays information about catch\-all aliases defined for
+a domain.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm relocatedinfo b.user@example.com\fP
-        Relocated information
-        ---------------------
-                User “b.user@example.com” has moved to “b-user@company.tld”
+.B vmm catchallinfo example.com
+Catch-all information
+---------------------
+  Mail to unknown localparts in domain example.com will be sent to:
+         * user@example.org
 .fi
-.TP
-\fBrelocateddelete\fP (\fBrd\fP) \fIold_address\fP
-Use this subcommand in order to delete the relocated user with the
-\fIold_address\fP.
+.\" ------------------------------------
+.SS catchalldelete (cad)
+.BI "vmm catchalldelete " fqdn
+.RI [ destination ]
+.PP
+With this subcommand, catch\-all aliases defined for a domain can be
+removed, either all of them, or a single one if specified explicitly.
+.PP
+Example:
 .PP
 .nf
-        Example:
-
-        \fBvmm relocateddelete b.user@example.com\fP
+.B vmm catchalldelete example.com user@example.com
 .fi
-.\"
+.\" -----------------------------------------------------------------------
 .SH FILES
-/usr/local/etc/vmm.cfg
+.TP
+.I /root/vmm.cfg
+will be used when found.
+.TP
+.I /usr/local/etc/vmm.cfg
+will be used when the above file doesn't exist.
+.TP
+.I /etc/vmm.cfg
+will be used when none of the both above mentioned files exists.
+.\" -----------------------------------------------------------------------
 .SH SEE ALSO
-vmm.cfg(5), configuration file for vmm
-.SH AUTHOR
-\fBvmm\fP and its man pages were written by Pascal Volk
-<\fIneverseen@users.sourceforge.net\fP> and are licensed under the terms of the
-BSD License.
+.BR dsync (1),
+.BR transport (5),
+.BR vmm.cfg (5)
+.\" -----------------------------------------------------------------------
+.SH INTERNET RESOURCES
+.TP
+Wiki
+http://vmm.localdomain.org/
+.TP
+Project site
+http://sf.net/projects/vmm/
+.TP
+Bug tracker
+https://bitbucket.org/pvo/vmm/issues
+.\" -----------------------------------------------------------------------
+.SH COPYING
+vmm and its manual pages were written by Pascal Volk <user+vmm AT
+localhost.localdomain.org> and are licensed under the terms of the BSD
+License.
--- a/man/man5/vmm.cfg.5	Mon Nov 07 03:22:15 2011 +0000
+++ b/man/man5/vmm.cfg.5	Thu Jun 28 19:26:50 2012 +0000
@@ -1,40 +1,82 @@
-.TH vmm.cfg 5 "17 Aug 2009" "Pascal Volk"
+.TH "VMM.CFG" "5" "2011-11-12" "vmm 0.6" "vmm"
 .SH NAME
 vmm.cfg \- configuration file for vmm
+.\" -----------------------------------------------------------------------
 .SH SYNOPSIS
 vmm.cfg
+.\" -----------------------------------------------------------------------
 .SH DESCRIPTION
-\fBvmm\fR(1) reads configuration data from \fIvmm.cfg\fP.
+.BR vmm (1)
+reads its configuration data from
+.IR vmm.cfg .
+.PP
+The configuration file is split into multiple sections.
+A section starts with the section name, enclosed in square brackets
+.RB ` [ "' and `" ] ',
+followed by
+.RI ` option " = " value '
+pairs.
 .br
-The configuration file is split in multiple sections. A section starts with the
-section  name, enclosed in square brackets '[' and ']' (e.g. \fB[database]\fP),
-followed by \'option=value' pairs (e.g. \fBhost = 127.0.0.1\fP).
-.br
-Whitespace around the '=' and at the end of a value is ignored.
-.PP
-Empty lines and lines starting with '#' or ';' will be ignored.
+Whitespace around the `=' and at the end of a value is ignored.
+Empty lines and lines starting with `#' or `;' will be ignored.
 .PP
 Each value uses one of the following data types:
-.IP \(bu
+.TP 8
 .I Boolean
-to indicate if something is enabled/activated (true) or disabled/deactivated
-(false).
+to indicate if something is enabled/activated (true) or
+disabled/deactivated (false).
+.br
+Accepted values for
+.I true
+are:
+.BR 1 , " yes" , " true" " and " on .
 .br
-Accepted values for \fBtrue\fP are: \fB1\fP, \fByes\fP, \fBtrue\fP and \fBon\fP.
+Accepted values for
+.I false
+are:
+.BR 0 , " no" , " false" " and " off .
+.TP
+.I Int
+an integer number, written without a fractional or decimal component.
+.br
+For example
+.BR 1 , " 50" " or " 321
+are integers.
+.TP
+.I String
+a sequence of characters and/or numbers.
 .br
-Accepted values for \fBfalse\fP are: \fB0\fP, \fBno\fP, \fBfalse\fP and
-\fBoff\fP.
-.IP \(bu
-.I Int
-an integer number, written without a fractional or decimal component. For
-example \fB1\fP, \fB50\fP or \fB321\fP are integers.
-.IP \(bu
-.I String
-a sequence of characters and numbers. For example '\fBword\fP', '\fBhello
-world\fP' or '\fB/usr/bin/strings\fP'
-.SS SEARCH ORDER
-By default vmm looks for \fIvmm.cfg\fP in the following directories in the
-order listed:
+For example
+.RB ` word "', `" "hello world" "' or `" /usr/bin/strings '
+are strings.
+.PP
+Most options have a default value, shown in parentheses after the option's
+name.
+In order to use a option's default setting, comment out the line, either
+with a
+.BR # " or " ;
+or simply remove the setting from
+.IR vmm.cfg .
+.PP
+A minimal
+.I vmm.cfg
+would be:
+.PP
+.nf
+[database]
+user = me
+pass = xxxxxxxx
+
+[misc]
+dovecot_version = 1.2.16
+.fi
+.\" -----------------------------------------------------------------------
+.SH SEARCH ORDER
+By default
+.BR vmm (1)
+looks for the
+.I vmm.cfg
+file in the following directories in the order listed:
 .RS
 .PD 0
 .TP
@@ -49,208 +91,500 @@
 .PD
 .RE
 .PP
-The first match it finds will be used.
-.\" -----
-.SH DATABASE SECTION
-The \fBdatabase\fP section is used to specify some options required to
-connect to the database.
-.TP
-\fBhost\fP (\fIString\fP)
-Hostname or IP address of the database server.
-.TP
-\fBuser\fP (\fIString\fP)
-Name of the database user.
-.TP
-\fBpass\fP (\fIString\fP)
-Database password
-.TP
-\fBname\fP (\fIString\fP)
-Name of the database.
-.TP
-\fBExample\fP:
-[database]
-.br
-host = localhost
+The first configuration file found will be used.
+.\" -----------------------------------------------------------------------
+.SH SECTION ACCOUNT
+The options in the section
+.B account
+are used to specify user account related settings.
+.SS account.delete_directory
+.BR delete_directory " (default: false) :"
+.I Boolean
+.PP
+Determines the behavior of
+.BR vmm (1)
+when an account is deleted (userdelete).
+If this option is set to
+.I true
+the user's home directory will be deleted recursively.
+.\" ------------------------------------
+.SS account.directory_mode
+.BR directory_mode " (default: 448) :"
+.I Int
+.PP
+Access mode for a user's home directory and all directories inside.
+The value has to be specified in decimal (base 10) notation.
 .br
-user = vmm
-.br
-pass = T~_:L4OYyl]TU?)
-.br
-name = mailsys
-.\" -----
-.SH MAILDIR SECTION
-The \fBmaildir\fP section is used to specify some options for the Maildirs.
-.TP
-\fBname\fP (\fIString\fP)
-Default name of the maildir folder in users home directory.
-.TP
-\fBfolders\fP (\fIString\fP)
-A colon separated list of folder names, that should be created.
-.br
-If no folders should be created inside the Maildir, set the value of this option
-to a single colon (':').
-.TP
-\fBmode\fP (\fIInt\fP)
-Access mode for the maildir in decimal (base 10) notation. For example:
-\'drwx------' -> octal 0700 -> decimal 448
-.TP
-\fBdiskusage\fP (\fIBoolean\fP)
-Decides if the disk usage of users maildir always should be summarized and
-displayed with account information.
+For example: `drwx\-\-\-\-\-\-' \(-> octal 0700 \(-> decimal 448
+.\" ------------------------------------
+.SS account.disk_usage
+.BR disk_usage " (default: false) :"
+.I Boolean
+.PP
+Determines whether the disk usage of a user's mail directory always should
+be summarized, using
+.BR du (1),
+and displayed with the account information (userinfo).
+.PP
+This could be slow on large Maildirs.
+When you have enabled quotas,
+.BR vmm 's
+userinfo subcommand will also display the current quota usage of the
+account.
+You may also use userinfo's optional details\-argument
+.BR du " or " full ,
+in order to display the current disk usage of an account's mail directory.
+.\" ------------------------------------
+.SS account.password_length
+.BR password_length " (default: 8) :"
+.I Int
+.PP
+Determines how many characters and/or numbers should be used for randomly
+generated passwords.
+Any value less than 8 will be increased to 8.
+.\" ------------------------------------
+.SS account.random_password
+.BR random_password " (default: false) :"
+.I Boolean
+.PP
+Determines whether
+.BR vmm (1)
+should generate a random password when no password was given for the
+useradd subcommand.
+If this option is set to
+.I false
+.B vmm
+will prompt you to enter a password for the new account.
+.PP
+You can specify the password length of generated passwords with the
+.I account.password_length
+option.
+.\" -----------------------------------------------------------------------
+.SH SECTION BIN
+The
+.B bin
+section is used to specify some paths to some binaries required by
+.BR vmm (1).
+.SS bin.dovecotpw
+.BR dovecotpw " (default: /usr/sbin/dovecotpw) :"
+.I String
+.PP
+The absolute path to the
+.BR dovecotpw (1)
+binary.
+Use the absolute path to the
+.BR doveadm (1)
+binary, if you are using Dovecot v2.0.
+.PP
+This binary is used to generate a password hash, if
+.I misc.password_scheme
+is set to one of `CRAM\-MD5', `HMAC\-MD5', `LANMAN', `OTP', `RPA' or
+`SKEY'.
+This binary will be also required if your Python installation doesn't
+support the:
+.IP \(bu 4
+md4 hash algorithm (hashlib + OpenSSL or PyCrypto) used for the password
+schemes: `PLAIN\-MD4' and `NTLM'
+.IP \(bu
+sha256 hash algorithm (hashlib or PyCrypto \(>= 2.1.0alpha1) used for the
+password schemes: `SHA256' and `SSHA256'
+.IP \(bu
+sha512 hash algorithm (hashlib) used for the password schemes: `SHA512' and
+`SSHA512'
+.PP
+The
+.BR doveadm (1)
+binary is also used to create a user's INBOX and additional mailboxes
+.RI ( mailbox.folders ),
+when the
+.I mailbox.format
+is set to
+.BR mdbox " or " sdbox .
+.\" ------------------------------------
+.SS bin.du
+.BR du " (default: /usr/bin/du) :"
+.I String
+.PP
+The absolute path to
+.BR du (1).
+This binary is used to summarize the disk usage of a user's mail directory.
+.\" ------------------------------------
+.SS bin.postconf
+.BR postconf " (default: /usr/sbin/postconf) :"
+.I String
+.PP
+The absolute path to Postfix'
+.BR postconf (1).
+This binary is required when
+.BR vmm (1)
+has to check for some Postfix settings, e.g. the
+.IR virtual_alias_expansion_limit .
+.\" -----------------------------------------------------------------------
+.SH SECTION DATABASE
+The
+.B database
+section is used to specify some options required to connect to the
+database.
+.SS database.host
+.BR host " (default: localhost) :"
+.I String
+.PP
+Hostname or IP address of the database server.
+.\" ------------------------------------
+.SS database.module
+.BR module " (default: psycopg2) :"
+.I String
+.PP
+The Python PostgreSQL database adapter module to be used.
+Supported modules are
+.BR psycopg2 " and " pyPgSQL .
+.\" ------------------------------------
+.SS database.name
+.BR name " (default: mailsys) :"
+.I String
+.PP
+Name of the database.
+.\" ------------------------------------
+.SS database.pass
+.BR pass " (default: " None ") :"
+.I String
+.PP
+Database password.
+.\" ------------------------------------
+.SS database.port
+.BR port " (default: 5432) :"
+.I Int
+.PP
+The TCP port, on which the database server is listening for connections.
+.\" ------------------------------------
+.SS database.sslmode
+.BR sslmode " (default: prefer) :"
+.I String
+.PP
+Determines whether and with what priority an SSL connection will be
+negotiated with the database server.
+Possible values are:
+.BR disabled ", " allow ", " prefer ", " require ", " verify\-ca " and "
+.BR verify\-full .
+The modes
+.BR verify\-ca " and " verify\-full
+are available since PostgreSQL 8.4
+.PP
+This setting will be ignored when the
+.I database.module
+is set to
+.BR pyPgSQL .
+.\" ------------------------------------
+.SS database.user
+.BR user " (default: " None ") :"
+.I String
+.PP
+Name of the database user.
+.\" -----------------------------------------------------------------------
+.SH SECTION DOMAIN
+The
+.B domain
+section specifies some domain related settings.
+.PP
+The quota limit (quota_bytes and quota_messages), service settings (imap,
+pop3, sieve and smtp) and the transport setting will be applied when a
+domain is created.
+In order to modify those settings for an existing domain, use one of
+the following
+.BR vmm (1)
+subcommands:
+.PP
 .TP
-\fBdelete\fP (\fIBoolean\fP)
-Decides if the maildir should be deleted recursive when the account is deleted.
-.TP
-\fBExample\fP:
-[maildir]
-.br
-name = Maildir
-.br
-folders = Drafts:Sent:Templates:Trash:INBOX.News
-.br
-mode = 448
-.br
-diskusage = false
-.br
-delete = false
-.\" -----
-.SH SERVICES SECTION
-The \fBservices\fP section is used to specify the default restrictions for
-all accounts.
-.TP
-\fBsmtp\fP (\fIBoolean\fP)
-Decides if users can login via smtp by default. 
-.TP
-\fBpop3\fP (\fIBoolean\fP)
-Decides if users can login via pop3 by default. 
+.B domainquota
+in order to update a domain's quota limit
 .TP
-\fBimap\fP (\fIBoolean\fP)
-Decides if users can login via imap by default. 
-.TP
-\fBsieve\fP (\fIBoolean\fP)
-Decides if users can login via managesieve by default. 
-.TP
-\fBExample\fP:
-[services]
-.br
-smtp = true
-.br
-pop3 = true
-.br
-imap = false
-.br
-sieve = false
-.\" -----
-.SH DOMDIR SECTION
-The \fBdomdir\fP section is used to specify options for the directories of the
-domains.
-.TP
-\fBbase\fP (\fIString\fP)
-All domain directories will be created inside this directory.
-.TP
-\fBmode\fP (\fIInt\fP)
-Access mode for the domain directory in decimal (base 10) notation. For
-example: 'drwxrwx---' -> octal 0770 -> decimal 504
+.B domainservices
+in order to assign a different service set to a domain
 .TP
-\fBdelete\fP (\fIBoolean\fP)
-Decides if the domain directory and all user directories inside should be
-deleted when a domain is deleted.
-.TP
-\fBExample\fP:
-[domdir]
+.B domaintransport
+in order to set a new default domain transport
+.PP
+When an account is created, it inherits all the settings of the domain
+to which it is added.
+Different settings for an existing account can be set using the subcommands
+.BR userquota ", " userservices " and " usertransport .
+.\" ------------------------------------
+.SS domain.auto_postmaster
+.BR auto_postmaster " (default: true) :"
+.I Boolean
+.PP
+Determines if
+.BR vmm (1)
+should create also a postmaster account when a new domain is created
+(domainadd).
+.\" ------------------------------------
+.SS domain.delete_directory
+.BR delete_directory " (default: false) :"
+.I Boolean
+.PP
+Specifies whether the domain directory and all user directories inside
+should be deleted when a domain is deleted (domaindelete).
+.\" ------------------------------------
+.SS domain.directory_mode
+.BR directory_mode " (default: 504) :"
+.I Int
+.PP
+Access mode for the domain directory in decimal (base 10) notation.
 .br
-base = /srv/mail
-.br
-mode = 504
+For example: `drwxrwx\-\-\-' \(-> octal 0770 \(-> decimal 504
+.\" ------------------------------------
+.SS domain.force_deletion
+.BR force_deletion " (default: false) :"
+.I Boolean
+.PP
+Force the deletion of accounts and aliases when a domain is deleted
+(domaindelete).
+.\" ------------------------------------
+.SS domain.imap
+.BR imap " (default: true) :"
+.I Boolean
+.PP
+Determines whether newly created users can log in via IMAP.
+.\" ------------------------------------
+.SS domain.pop3
+.BR pop3 " (default: true) :"
+.I Boolean
+.PP
+Determines whether newly created users can log in via POP3.
+.\" ------------------------------------
+.SS domain.quota_bytes
+.BR quota_bytes " (default: 0) :"
+.I String
+.PP
+Quota limit in bytes.
+0 means unlimited.
+This limit will be applied to all newly created domains.
+.PP
+The option's value can be written as an integer value, e.g.:
+.BR 20480 .
+It's also possible to append one of the following prefixes to the limit:
+.BR b " (bytes), " k " (kilobytes), " M " (megabytes) or " G
+(gigabytes).
 .br
-delete = false
-.\" -----
-.SH BIN SECTION
-The \fBbin\fP section is used to specify some paths to some binaries required
-by \fBvmm\fP.
-.TP
-\fBdovecotpw\fP (\fIString\fP)
-The absolute path to the dovecotpw binary. This binary is used to generate a
-password hash, if the \fIpasswdscheme\fP is one of 'SMD5', 'SSHA', 'CRAM-MD5',
-\'HMAC-MD5', 'LANMAN', 'NTLM' or 'RPA'.
+1024 is the same as 1024b or 1k.
+.\" ------------------------------------
+.SS domain.quota_messages
+.BR quota_messages " (default: 0) :"
+.I Int
+.PP
+Quota limit in number of messages.
+0 means unlimited.
+This limit will be applied to all newly created domains.
+.\" ------------------------------------
+.SS domain.sieve
+.BR sieve " (default: true) :"
+.I Boolean
+.PP
+Determines whether newly created users can log in via SIEVE (ManageSieve).
+.\" ------------------------------------
+.SS domain.smtp
+.BR smtp " (default: true) :"
+.I Boolean
+.PP
+Determines whether newly created users can log in via SMTP (SMTP AUTH).
+.\" ------------------------------------
+.SS domain.transport
+.BR transport " (default: dovecot:) :"
+.I String
+.PP
+Default transport for domains and accounts.
+For details see
+.BR transport (5).
+.\" -----------------------------------------------------------------------
+.SH SECTION MAILBOX
+The
+.B mailbox
+section is used to specify some options for new created mailboxes in the
+users home directories.
+The INBOX will be created always.
+.SS mailbox.folders
+.BR folders " (default: Drafts:Sent:Templates:Trash) :"
+.I String
+.PP
+A colon separated list of mailboxes that should be created.
+If no additionally mailboxes should be created, set the value of this
+option to a single colon
+.RB (` : ').
+.PP
+If you want to create folders containing one or more subfolders, separate
+them with a single dot
+.RB (` . ').
+.PP
+If you want to use internationalized mailbox names (e.g. `Wysłane' or
+`Gelöschte Objekte'), write their names UTF\-8 encoded.
+.BR vmm (1)
+will convert internationalized mailbox names to a modified version of the
+UTF\-7 encoding (see also: RFC 3501, section 5.1.3).
+.\" ------------------------------------
+.SS mailbox.format
+.BR format " (default: maildir) :"
+.I String
+.PP
+The mailbox format to be used for a user's mailbox.
+Depending on the used Dovecot version
+.RI ( misc.dovecot_version )
+.BR vmm (1)
+supports up to three formats:
+.TP 8
+.B maildir
+Dovecot \(>= v1.0.0
 .TP
-\fBdu\fP (\fIString\fP)
-The absolute path to \fBdu\fR(1). This binary is used to summarize the disk
-usage of a maildir.
-.TP
-\fBpostconf\fP (\fIString\fP)
-The absolute path to Postfix' \fBpostconf\fR(1).
-.br
-This binary is required if \fBvmm\fR(1) has to check for some Postfix settings,
-e.g. virtual_alias_expansion_limit.
-.TP
-\fBExample\fP:
-[bin]
-.br
-dovecotpw = /usr/sbin/dovecotpw
-.br
-du = /usr/bin/du
-.br
-postconf = /usr/sbin/postconf
-.\" -----
-.SH MISC SECTION
-The \fBmisc\fP section is used to define miscellaneous settings.
-.TP
-\fBpasswdscheme\fP (\fIString\fP)
-Password scheme to use (see also: dovecotpw -l)
-.TP
-\fBgid_mail\fP (\fIInt\fP)
-Numeric group ID of group mail (mail_privileged_group from dovecot.conf)
+.B mdbox
+Dovecot \(>= v2.0.beta5
 .TP
-\fBforcedel\fP (\fIBoolean\fP)
-Force deletion of accounts and aliases when a domain is deleted.
-.TP
-\fBtransport\fP (\fIString\fP)
-Default transport for domains and accounts.
-.TP
-\fBdovecotvers\fP (\fIInt\fP)
-The concatenated major and minor version number of the currently used Dovecot
-version. (see: dovecot --version).
-.br
-This option affects various database operations. There are some differences
-between Dovecot v1.1.x and v1.2.x. For example, when the command \fBdovecot
---version\fP shows \fB1.1\fP.18, set the value of this option to \fB11\fP.
-.TP
-\fBExample\fP:
-[misc]
-.br
-passwdscheme = CRAM-MD5
-.br
-gid_mail = 8
-.br
-forcedel = false
-.br
-transport = dovecot:
+.B sdbox
+Dovecot \(>= v2.0.rc3
+.\" ------------------------------------
+.SS mailbox.root
+.BR root " (default: Maildir) :"
+.I String
+.PP
+Name of the mailbox root directory in a user's home directory.
+Commonly used names, depending on the used
+.IR mailbox.format ,
+are
+.BR Maildir ", " mdbox " or " sdbox .
+.\" ------------------------------------
+.SS mailbox.subscribe
+.BR subscribe " (default: true) :"
+.I Boolean
+.PP
+When this option is set to
+.BR true ,
+the mailboxes from the
+.I mailbox.folders
+option will be listed in the user's subscriptions file.
+If you don't want to subscribe the created mailboxes, set this option to
+.BR false .
+.\" -----------------------------------------------------------------------
+.SH SECTION MISC
+The
+.I misc
+section is used to define miscellaneous settings.
+.SS misc.base_directory
+.BR base_directory " (default: /srv/mail) :"
+.I String
+.PP
+All domain directories will be created inside this directory.
+.\" ------------------------------------
+.SS misc.crypt_blowfish_rounds
+.BR crypt_blowfish_rounds " (default: 5) :"
+.I Int
+.PP
+Number of encryption rounds for the
+.I password_scheme
+.BR BLF\-CRYPT .
+.PP
+The value must be in range
+.BR 4 " \- " 31 .
+.\" ------------------------------------
+.SS misc.crypt_sha256_rounds
+.BR crypt_sha256_rounds " (default: 5000) :"
+.I Int
+.PP
+Number of encryption rounds for the
+.I password_scheme
+.BR SHA256\-CRYPT .
+.PP
+The value must be in range
+.BR 1000 " \- " 999999999 .
+.\" ------------------------------------
+.SS misc.crypt_sha512_rounds
+.BR crypt_sha512_rounds " (default: 5000) :"
+.I Int
+.PP
+Number of encryption rounds for the
+.I password_scheme
+.BR SHA512\-CRYPT .
+.PP
+The value must be in range
+.BR 1000 " \- " 999999999 .
+.\" ------------------------------------
+.SS misc.dovecot_version
+.BR dovecot_version " (default: " None ") :"
+.I String
+.PP
+The version number of the currently used Dovecot version.
+(see:
+.BR "dovecot \-\-version" )
 .br
-dovecotvers = 11
-.\" -----
-.SH CONFIG SECTION
-The \fBconfig\fP section is a internal used control section.
-.TP
-\fBdone\fP (\fIBoolean\fP)
-This option is set to \fIfalse\fP when \fBvmm\fP is installed for the first
-time. When you edit \fIvmm.cfg\fP, set this option to \fItrue\fP. This option is
-also set to \fItrue\fP when you configure vmm with the command \fBvmm
-configure\fP.
-.br
-If this option is set to \fIfalse\fP, \fBvmm\fP will start in the interactive
-configurations mode.
+When, for example, the command
+.B dovecot \-\-version
+prints
+.IR "2.0.beta4 (8818db00d347)" ,
+set the value of this option to
+.BR 2.0.beta4 .
+.\" ------------------------------------
+.SS misc.password_scheme
+.BR password_scheme " (default: CRAM\-MD5) :"
+.I String
+.PP
+Password scheme to use.
+To get a list of all usable password schemes execute the command
+.BR "vmm lp" .
+.PP
+With Dovecot \(>= v1.1.alpha1 it is also possible to append an encoding
+suffix to the password_scheme.
+Supported encoding suffixes are:
+.BR .b64 ", " .base64 " and " .hex .
+For example: PLAIN.BASE64
+.\" -----------------------------------------------------------------------
+.SH EXAMPLE
+An example configuration.
+All options that are not listed in the configuration file will have their
+default values.
+.PP
+.nf
+[account]
+password_length = 10
+random_password = true
+
+[bin]
+dovecotpw = /usr/bin/doveadm
+
+[database]
+host = dbsrv8.example.net
+pass = PY_SRJ}L/0p\-oOk
+port = 5433
+sslmode = require
+user = vmm
+
+[domain]
+quota_bytes = 500M
+quota_messages = 10000
+transport = lmtp:unix:private/dovecot\-lmtp
+
+[mailbox]
+folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
+
+[misc]
+crypt_sha512_rounds = 10000
+dovecot_version = 2.0.beta4
+password_scheme = SHA512\-CRYPT.hex
+.fi
+.\" -----------------------------------------------------------------------
+.SH SEE ALSO
+.BR postconf (1),
+.BR vmm (1),
+.BR transport (5)
+.\" -----------------------------------------------------------------------
+.SH INTERNET RESOURCES
 .TP
-\fBExample\fP:
-[config]
-.br
-done = true
-.\" -----
-.SH FILES
-vmm.cfg
-.SH SEE ALSO
-vmm(1), command line tool to manage email domains/accounts/aliases
-.SH AUTHOR
-\fBvmm\fP and its man pages were written by Pascal Volk
-<\fIneverseen@users.sourceforge.net\fP> and are licensed under the terms of the
-BSD License.
+Wiki
+http://vmm.localdomain.org/
+.TP
+Project site
+http://sf.net/projects/vmm/
+.TP
+Bug tracker
+https://bitbucket.org/pvo/vmm/issues
+.\" -----------------------------------------------------------------------
+.SH COPYING
+vmm and its manual pages were written by Pascal Volk <user+vmm AT
+localhost.localdomain.org> and are licensed under the terms of the BSD
+License.
--- a/pgsql/create_optional_types_and_functions-dovecot-1.2.x.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,374 +0,0 @@
--- --- Information:
--- This file contains some data types and functions these should speed up some
--- operations. Read the comment on each data type/functions for more details.
--- ---
-
--- ---
--- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
--- ---
-CREATE TYPE sender_login AS (
-    sender  varchar(320),
-    login   text
-);
-
--- ---
--- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
---      set of sender_login records.
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, users, alias TO postfix;
---
--- For more details see postconf(5) section smtpd_sender_login_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
-AS $$
-    DECLARE
-        rec sender_login;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        sender varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        -- Get all addresses for 'localpart' in the primary and aliased domains
-        FOR rec IN
-            SELECT sender, local_part || '@' || domainname
-              FROM domain_name, users
-             WHERE domain_name.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        IF NOT FOUND THEN
-            -- Loop over the alias addresses for localpart@the_domain
-            FOR rec IN
-                SELECT DISTINCT sender, destination
-                  FROM alias
-                 WHERE alias.gid = did
-                   AND alias.address = localpart
-                LOOP
-                    RETURN NEXT rec;
-                END LOOP;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_virtual_mailbox(varchar, varchar)
--- ---
-CREATE TYPE address_maildir AS (
-    address varchar(320),
-    maildir text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: address_maildir records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_data,domain_name,maillocation,users TO postfix;
---
--- For more details see postconf(5) section virtual_mailbox_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
-   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
-AS $$
-    DECLARE
-        rec address_maildir;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        address varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR rec IN
-            SELECT address, domaindir||'/'||users.uid||'/'||maillocation||'/'
-              FROM domain_data, users, maillocation
-             WHERE domain_data.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for functions: postfix_relocated_map(varchar, varchar)
---                          postfix_virtual_alias_map(varchar, varchar)
---                          
--- ---
-CREATE TYPE recipient_destination AS (
-    recipient   varchar(320),
-    destination text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON alias, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_alias_maps and virtual(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM alias
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, relocated TO postfix;
---
--- For more details see postconf(5) section relocated_maps and relocated(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_relocated_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM relocated
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_transport_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_transport AS (
-    recipient   varchar(320),
-    transport   text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_transport records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, transport, domain_name TO postfix;
---
--- For more details see postconf(5) section transport_maps and transport(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_transport_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_transport
-AS $$
-    DECLARE
-        record recipient_transport;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, transport
-              FROM transport
-             WHERE tid = (SELECT tid
-                            FROM users
-                           WHERE gid = (SELECT gid
-                                          FROM domain_name
-                                         WHERE domainname = the_domain)
-                             AND local_part = localpart)
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_virtual_uid_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_uid AS (
-    recipient   varchar(320),
-    uid         bigint
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_uid records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_uid_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
-AS $$
-    DECLARE
-        record recipient_uid;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, uid
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function dovecotuser(varchar, varchar)
--- ---
-CREATE TYPE dovecotuser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text
-);
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotuser records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users,domain_data,domain_name,maillocation TO dovecot;
---
--- For more details see http://wiki.dovecot.org/UserDatabase
--- ---
-CREATE OR REPLACE FUNCTION dovecotuser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
-AS $$
-    DECLARE
-        record dovecotuser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir ||'/'|| uid AS home,
-                   '~/'|| maillocation AS mail
-              FROM users, domain_data, maillocation
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND domain_data.gid = did
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function dovecotpassword(varchar, varchar)
--- ---
-CREATE TYPE dovecotpassword AS (
-    userid    varchar(320),
-    password  varchar(74),
-    smtp      boolean,
-    pop3      boolean,
-    imap      boolean,
-    sieve     boolean
-);
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotpassword records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users, domain_name TO dovecot;
---
--- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
--- ---
-CREATE OR REPLACE FUNCTION dovecotpassword(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
-AS $$
-    DECLARE
-        record dovecotpassword;
-        userid varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, sieve
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- a/pgsql/create_optional_types_and_functions.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,374 +0,0 @@
--- --- Information:
--- This file contains some data types and functions these should speed up some
--- operations. Read the comment on each data type/functions for more details.
--- ---
-
--- ---
--- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
--- ---
-CREATE TYPE sender_login AS (
-    sender  varchar(320),
-    login   text
-);
-
--- ---
--- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
---      set of sender_login records.
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, users, alias TO postfix;
---
--- For more details see postconf(5) section smtpd_sender_login_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
-AS $$
-    DECLARE
-        rec sender_login;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        sender varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        -- Get all addresses for 'localpart' in the primary and aliased domains
-        FOR rec IN
-            SELECT sender, local_part || '@' || domainname
-              FROM domain_name, users
-             WHERE domain_name.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        IF NOT FOUND THEN
-            -- Loop over the alias addresses for localpart@the_domain
-            FOR rec IN
-                SELECT DISTINCT sender, destination
-                  FROM alias
-                 WHERE alias.gid = did
-                   AND alias.address = localpart
-                LOOP
-                    RETURN NEXT rec;
-                END LOOP;
-        END IF;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_virtual_mailbox(varchar, varchar)
--- ---
-CREATE TYPE address_maildir AS (
-    address varchar(320),
-    maildir text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: address_maildir records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_data,domain_name,maillocation,users TO postfix;
---
--- For more details see postconf(5) section virtual_mailbox_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
-   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
-AS $$
-    DECLARE
-        rec address_maildir;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-        address varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR rec IN
-            SELECT address, domaindir||'/'||users.uid||'/'||maillocation||'/'
-              FROM domain_data, users, maillocation
-             WHERE domain_data.gid = did
-               AND users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-            LOOP
-                RETURN NEXT rec;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for functions: postfix_relocated_map(varchar, varchar)
---                          postfix_virtual_alias_map(varchar, varchar)
---                          
--- ---
-CREATE TYPE recipient_destination AS (
-    recipient   varchar(320),
-    destination text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON alias, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_alias_maps and virtual(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM alias
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_destination records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON domain_name, relocated TO postfix;
---
--- For more details see postconf(5) section relocated_maps and relocated(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_relocated_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_destination
-AS $$
-    DECLARE
-        record recipient_destination;
-        recipient varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT recipient, destination
-              FROM relocated
-             WHERE gid = did
-               AND address = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_transport_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_transport AS (
-    recipient   varchar(320),
-    transport   text
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_transport records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, transport, domain_name TO postfix;
---
--- For more details see postconf(5) section transport_maps and transport(5)
--- ---
-CREATE OR REPLACE FUNCTION postfix_transport_map(
-    IN localpart varchar, IN the_domain varchar)
-    RETURNS SETOF recipient_transport
-AS $$
-    DECLARE
-        record recipient_transport;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, transport
-              FROM transport
-             WHERE tid = (SELECT tid
-                            FROM users
-                           WHERE gid = (SELECT gid
-                                          FROM domain_name
-                                         WHERE domainname = the_domain)
-                             AND local_part = localpart)
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function postfix_virtual_uid_map(varchar, varchar)
--- ---
-CREATE TYPE recipient_uid AS (
-    recipient   varchar(320),
-    uid         bigint
-);
-
--- ---
--- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: recipient_uid records
---
--- Required access privileges for your postfix database user:
---      GRANT SELECT ON users, domain_name TO postfix;
---
--- For more details see postconf(5) section virtual_uid_maps
--- ---
-CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
-AS $$
-    DECLARE
-        record recipient_uid;
-        recipient varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT recipient, uid
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function dovecotuser(varchar, varchar)
--- ---
-CREATE TYPE dovecotuser AS (
-    userid      varchar(320),
-    uid         bigint,
-    gid         bigint,
-    home        text,
-    mail        text
-);
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotuser records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users,domain_data,domain_name,maillocation TO dovecot;
---
--- For more details see http://wiki.dovecot.org/UserDatabase
--- ---
-CREATE OR REPLACE FUNCTION dovecotuser(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
-AS $$
-    DECLARE
-        record dovecotuser;
-        userid varchar(320) := localpart || '@' || the_domain;
-        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
-    BEGIN
-        FOR record IN
-            SELECT userid, uid, did, domaindir ||'/'|| uid AS home,
-                   '~/'|| maillocation AS mail
-              FROM users, domain_data, maillocation
-             WHERE users.gid = did
-               AND users.local_part = localpart
-               AND maillocation.mid = users.mid
-               AND domain_data.gid = did
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- ########################################################################## --
-
--- ---
--- Data type for function dovecotpassword(varchar, varchar)
--- ---
-CREATE TYPE dovecotpassword AS (
-    userid      varchar(320),
-    password    varchar(74),
-    smtp        boolean,
-    pop3        boolean,
-    imap        boolean,
-    managesieve boolean
-);
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotpassword records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users, domain_name TO dovecot;
---
--- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
--- ---
-CREATE OR REPLACE FUNCTION dovecotpassword(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
-AS $$
-    DECLARE
-        record dovecotpassword;
-        userid varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, managesieve
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
--- a/pgsql/create_tables-dovecot-1.2.x.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ b/pgsql/create_tables-dovecot-1.2.x.pgsql	Thu Jun 28 19:26:50 2012 +0000
@@ -4,8 +4,14 @@
 
 CREATE SEQUENCE transport_id;
 
+CREATE SEQUENCE mailboxformat_id;
+
 CREATE SEQUENCE maillocation_id;
 
+CREATE SEQUENCE quotalimit_id;
+
+CREATE SEQUENCE service_set_id;
+
 CREATE SEQUENCE domain_gid
     START WITH 70000
     INCREMENT BY 1
@@ -30,20 +36,80 @@
 -- Insert default transport
 INSERT INTO transport(transport) VALUES ('dovecot:');
 
-CREATE TABLE maillocation(
-    mid     bigint NOT NULL DEFAULT nextval('maillocation_id'),
-    maillocation varchar(20) NOT NULL,
+CREATE TABLE mailboxformat (
+    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
+    format      varchar(20) NOT NULL,
+    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
+    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
+);
+-- Insert supported mailbox formats
+INSERT INTO mailboxformat(format) VALUES ('maildir');
+INSERT INTO mailboxformat(format) VALUES ('mdbox');
+INSERT INTO mailboxformat(format) VALUES ('sdbox');
+
+CREATE TABLE maillocation (
+    mid         bigint NOT NULL DEFAULT nextval('maillocation_id'),
+    fid         bigint NOT NULL DEFAULT 1,
+    directory   varchar(20) NOT NULL,
+    extra       varchar(1024),
     CONSTRAINT  pkey_maillocation PRIMARY KEY (mid),
-    CONSTRAINT  ukey_maillocation UNIQUE (maillocation)
+    CONSTRAINT  fkey_maillocation_fid_mailboxformat FOREIGN KEY (fid)
+        REFERENCES mailboxformat (fid)
 );
 -- Insert default Maildir-folder name
-INSERT INTO maillocation(maillocation) VALUES ('Maildir');
+INSERT INTO maillocation(directory) VALUES ('Maildir');
+
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+CREATE TABLE service_set (
+    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
+    smtp        boolean NOT NULL DEFAULT TRUE,
+    pop3        boolean NOT NULL DEFAULT TRUE,
+    imap        boolean NOT NULL DEFAULT TRUE,
+    sieve       boolean NOT NULL DEFAULT TRUE,
+    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
+    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, sieve)
+);
+-- Insert all possible service combinations
+COPY service_set (smtp, pop3, imap, sieve) FROM stdin;
+TRUE	TRUE	TRUE	TRUE
+FALSE	TRUE	TRUE	TRUE
+TRUE	FALSE	TRUE	TRUE
+FALSE	FALSE	TRUE	TRUE
+TRUE	TRUE	FALSE	TRUE
+FALSE	TRUE	FALSE	TRUE
+TRUE	FALSE	FALSE	TRUE
+FALSE	FALSE	FALSE	TRUE
+TRUE	TRUE	TRUE	FALSE
+FALSE	TRUE	TRUE	FALSE
+TRUE	FALSE	TRUE	FALSE
+FALSE	FALSE	TRUE	FALSE
+TRUE	TRUE	FALSE	FALSE
+FALSE	TRUE	FALSE	FALSE
+TRUE	FALSE	FALSE	FALSE
+FALSE	FALSE	FALSE	FALSE
+\.
 
 CREATE TABLE domain_data (
     gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
-    tid         bigint NOT NULL DEFAULT 1, -- defualt transport
+    qid         bigint NOT NULL DEFAULT 1, -- default quota limit
+    ssid        bigint NOT NULL DEFAULT 1, -- default service set
+    tid         bigint NOT NULL DEFAULT 1, -- default transport
     domaindir   varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
+    note        text NULL DEFAULT NULL,
     CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
+    CONSTRAINT  fkey_domain_data_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid),
+    CONSTRAINT  fkey_domain_data_ssid_service_set FOREIGN KEY (ssid)
+        REFERENCES service_set (ssid),
     CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
         REFERENCES transport (tid)
 );
@@ -59,26 +125,38 @@
 
 CREATE TABLE users (
     local_part  varchar(64) NOT NULL,-- only localpart w/o '@'
-    passwd      varchar(74) NOT NULL,-- {CRAM-MD5}+64hex numbers
+    passwd      varchar(270) NOT NULL,
     name        varchar(128) NULL,
     uid         bigint NOT NULL DEFAULT nextval('users_uid'),
     gid         bigint NOT NULL,
     mid         bigint NOT NULL DEFAULT 1,
-    tid         bigint NOT NULL DEFAULT 1,
-    smtp        boolean NOT NULL DEFAULT TRUE,
-    pop3        boolean NOT NULL DEFAULT TRUE,
-    imap        boolean NOT NULL DEFAULT TRUE,
-    sieve       boolean NOT NULL DEFAULT TRUE,
+    qid         bigint NULL DEFAULT NULL,
+    ssid        bigint NULL DEFAULT NULL,
+    tid         bigint NULL DEFAULT NULL,
+    note        text NULL DEFAULT NULL,
     CONSTRAINT  pkey_users PRIMARY KEY (local_part, gid),
     CONSTRAINT  ukey_users_uid UNIQUE (uid),
     CONSTRAINT  fkey_users_gid_domain_data FOREIGN KEY (gid)
         REFERENCES domain_data (gid),
     CONSTRAINT  fkey_users_mid_maillocation FOREIGN KEY (mid)
         REFERENCES maillocation (mid),
+    CONSTRAINT  fkey_users_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid),
+    CONSTRAINT fkey_users_ssid_service_set FOREIGN KEY (ssid)
+        REFERENCES service_set (ssid),
     CONSTRAINT  fkey_users_tid_transport FOREIGN KEY (tid)
         REFERENCES transport (tid)
 );
 
+CREATE TABLE userquota (
+    uid         bigint NOT NULL,
+    bytes       bigint NOT NULL DEFAULT 0,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
+    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid) ON DELETE CASCADE
+);
+
 CREATE TABLE alias (
     gid         bigint NOT NULL,
     address     varchar(64) NOT NULL,-- only localpart w/o '@'
@@ -97,59 +175,20 @@
         REFERENCES domain_data (gid)
 );
 
-CREATE OR REPLACE VIEW dovecot_password AS
-    SELECT local_part || '@' || domain_name.domainname AS "user",
-           passwd AS "password", smtp, pop3, imap, sieve
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW dovecot_user AS
-    SELECT local_part || '@' || domain_name.domainname AS userid,
-           uid, gid, domain_data.domaindir || '/' || uid AS home,
-           '~/' || maillocation.maillocation AS mail
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
+CREATE TABLE catchall (
+    gid         bigint NOT NULL,
+    destination varchar(320) NOT NULL,
+    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
+    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
+        REFERENCES domain_data (gid)
+);
 
 CREATE OR REPLACE VIEW postfix_gid AS
     SELECT gid, domainname
       FROM domain_name;
 
-CREATE OR REPLACE VIEW postfix_uid AS
-    SELECT local_part || '@' || domain_name.domainname AS address, uid
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_maildir AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           domain_data.domaindir||'/'||uid||'/'||maillocation.maillocation||'/'
-           AS maildir
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
-
-CREATE OR REPLACE VIEW postfix_relocated AS
-    SELECT address || '@' || domain_name.domainname AS address, destination
-      FROM relocated
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_alias AS
-    SELECT address || '@' || domain_name.domainname AS address, destination, gid
-      FROM alias
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_transport AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           transport.transport
-      FROM users
-           LEFT JOIN transport USING (tid)
-           LEFT JOIN domain_name USING (gid);
-
 CREATE OR REPLACE VIEW vmm_domain_info AS
-    SELECT gid, domainname, transport, domaindir,
-           count(uid) AS accounts,
+    SELECT gid, count(uid) AS accounts,
            (SELECT count(DISTINCT address)
               FROM alias
              WHERE alias.gid = domain_data.gid) AS aliases,
@@ -159,17 +198,92 @@
            (SELECT count(gid)
               FROM domain_name
              WHERE domain_name.gid = domain_data.gid
-               AND NOT domain_name.is_primary) AS aliasdomains
+               AND NOT domain_name.is_primary) AS aliasdomains,
+           (SELECT count(gid)
+              FROM catchall
+             WHERE catchall.gid = domain_data.gid) AS catchall
       FROM domain_data
            LEFT JOIN domain_name USING (gid)
-           LEFT JOIN transport USING (tid)
            LEFT JOIN users USING (gid)
      WHERE domain_name.is_primary
-  GROUP BY gid, domainname, transport, domaindir;
+  GROUP BY gid;
 
+-- ########################################################################## --
 
 CREATE LANGUAGE plpgsql;
 
+-- ######################## TYPEs ########################################### --
+
+-- ---
+-- Data type for function postfix_virtual_mailbox(varchar, varchar)
+-- ---
+CREATE TYPE address_maildir AS (
+    address varchar(320),
+    maildir text
+);
+-- ---
+-- Data type for function dovecotpassword(varchar, varchar)
+-- ---
+CREATE TYPE dovecotpassword AS (
+    userid    varchar(320),
+    password  varchar(270),
+    smtp      boolean,
+    pop3      boolean,
+    imap      boolean,
+    sieve     boolean
+);
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
+-- Data type for function dovecotuser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotuser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text
+);
+-- ---
+-- Data type for functions: postfix_relocated_map(varchar, varchar)
+--                          postfix_virtual_alias_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_destination AS (
+    recipient   varchar(320),
+    destination text
+);
+-- ---
+-- Data type for function postfix_transport_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_transport AS (
+    recipient   varchar(320),
+    transport   text
+);
+-- ---
+-- Data type for function postfix_virtual_uid_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_uid AS (
+    recipient   varchar(320),
+    uid         bigint
+);
+-- ---
+-- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
+-- ---
+CREATE TYPE sender_login AS (
+    sender  varchar(320),
+    login   text
+);
+
+-- ######################## TRIGGERs ######################################## --
 
 CREATE OR REPLACE FUNCTION domain_primary_trigger() RETURNS TRIGGER AS $$
 DECLARE
@@ -194,3 +308,463 @@
 
 CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
     FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
+
+
+CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+        IF NEW.messages IS NULL THEN
+            NEW.messages = 0;
+        ELSE
+            NEW.messages = -NEW.messages;
+        END IF;
+        RETURN NEW;
+    END IF;
+    LOOP
+        UPDATE userquota
+           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
+         WHERE uid = NEW.uid;
+        IF found THEN
+            RETURN NULL;
+        END IF;
+        BEGIN
+            IF NEW.messages = 0 THEN
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
+            ELSE
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
+            END IF;
+            RETURN NULL;
+        EXCEPTION
+            WHEN unique_violation THEN
+                -- do nothing, and loop to try the UPDATE again
+            WHEN foreign_key_violation THEN
+                -- break the loop: a non matching uid means no such user
+                RETURN NULL;
+        END;
+    END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
+
+-- ######################## FUNCTIONs ####################################### --
+
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotpassword records
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT ON users, domain_name, service_set TO dovecot;
+--
+-- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
+-- ---
+CREATE OR REPLACE FUNCTION dovecotpassword(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
+AS $$
+    DECLARE
+        record dovecotpassword;
+        userid varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT userid, passwd, smtp, pop3, imap, sieve
+              FROM users, service_set, domain_data
+             WHERE users.gid = (SELECT gid
+                                  FROM domain_name
+                                 WHERE domainname = the_domain)
+               AND local_part = localpart
+               AND users.gid = domain_data.gid
+               AND CASE WHEN
+                     users.ssid IS NOT NULL
+                     THEN
+                       service_set.ssid = users.ssid
+                     ELSE
+                       service_set.ssid = domain_data.ssid
+                     END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Nearly the same as function dovecotuser below. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND CASE WHEN
+                     users.qid IS NOT NULL
+                   THEN
+                     quotalimit.qid = users.qid
+                   ELSE
+                     quotalimit.qid = domain_data.qid
+                   END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotuser records
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat
+--          TO dovecot;
+--
+-- For more details see http://wiki.dovecot.org/UserDatabase
+-- ---
+CREATE OR REPLACE FUNCTION dovecotuser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
+AS $$
+    DECLARE
+        record dovecotuser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail
+              FROM users, domain_data, mailboxformat, maillocation
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_name, relocated TO postfix;
+--
+-- For more details see postconf(5) section relocated_maps and relocated(5)
+-- ---
+CREATE OR REPLACE FUNCTION postfix_relocated_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        record recipient_destination;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient, destination
+              FROM relocated
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
+--      set of sender_login records.
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_name, users, alias TO postfix;
+--
+-- For more details see postconf(5) section smtpd_sender_login_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
+AS $$
+    DECLARE
+        rec sender_login;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        sender varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        -- Get all addresses for 'localpart' in the primary and aliased domains
+        FOR rec IN
+            SELECT sender, local_part || '@' || domainname
+              FROM domain_name, users
+             WHERE domain_name.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        IF NOT FOUND THEN
+            -- Loop over the alias addresses for localpart@the_domain
+            FOR rec IN
+                SELECT DISTINCT sender, destination
+                  FROM alias
+                 WHERE alias.gid = did
+                   AND alias.address = localpart
+                LOOP
+                    RETURN NEXT rec;
+                END LOOP;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_transport records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON users, transport, domain_name TO postfix;
+--
+-- For more details see postconf(5) section transport_maps and transport(5)
+-- ---
+CREATE OR REPLACE FUNCTION postfix_transport_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_transport
+AS $$
+    DECLARE
+        record recipient_transport;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
+        transport_id bigint;
+    BEGIN
+        IF did IS NULL THEN
+            RETURN;
+        END IF;
+
+        SELECT tid INTO transport_id
+          FROM users
+         WHERE gid = did AND local_part = localpart;
+
+        IF transport_id IS NULL THEN
+            SELECT tid INTO STRICT transport_id
+              FROM domain_data
+             WHERE gid = did;
+        END IF;
+
+        FOR record IN
+            SELECT recipient, transport
+              FROM transport
+             WHERE tid = transport_id
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON alias, domain_name TO postfix;
+--
+-- For more details see postconf(5) section virtual_alias_maps and virtual(5)
+-- ---
+CREATE OR REPLACE FUNCTION _interpolate_destination(
+    IN destination varchar, localpart varchar, IN the_domain varchar)
+    RETURNS varchar
+AS $$
+    DECLARE
+        result varchar(320);
+    BEGIN
+        IF position('%' in destination) = 0 THEN
+            RETURN destination;
+        END IF;
+        result := replace(destination, '%n', localpart);
+        result := replace(result, '%d', the_domain);
+        result := replace(result, '%=', localpart || '=' || the_domain);
+        RETURN result;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
+CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        recordc recipient_destination;
+        record recipient_destination;
+        catchall_cursor refcursor;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient,
+                _interpolate_destination(destination, localpart, the_domain)
+              FROM alias
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+
+        IF NOT FOUND THEN
+            -- There is no matching virtual_alias. If there are no catchall
+            -- records for this domain, we can just return NULL since Postfix
+            -- will then later consult mailboxes/relocated itself. But if
+            -- there is a catchall destination, then it would take precedence
+            -- over mailboxes/relocated, which is not what we want. Therefore,
+            -- we must first find out if the query is for an existing mailbox
+            -- or relocated entry and return the identity mapping if that is
+            -- the case
+            OPEN catchall_cursor FOR
+                SELECT recipient,
+                    _interpolate_destination(destination, localpart, the_domain)
+                  FROM catchall
+                 WHERE gid = did;
+            FETCH NEXT FROM catchall_cursor INTO recordc;
+
+            IF recordc IS NOT NULL THEN
+                -- Since there are catchall records for this domain
+                -- check the mailbox and relocated records and return identity
+                -- if a matching record exists.
+                FOR record IN
+                    SELECT recipient, recipient as destination
+                      FROM users
+                    WHERE gid = did
+                      AND local_part = localpart
+                    UNION SELECT recipient, recipient as destination
+                      FROM relocated
+                    WHERE gid = did
+                      AND address = localpart
+                    LOOP
+                        RETURN NEXT record;
+                    END LOOP;
+
+                IF NOT FOUND THEN
+                    -- There were no records found for mailboxes/relocated,
+                    -- so now we can actually iterate the cursor and populate
+                    -- the return set
+                    LOOP
+                        RETURN NEXT recordc;
+                        FETCH NEXT FROM catchall_cursor INTO recordc;
+                        EXIT WHEN recordc IS NULL;
+                    END LOOP;
+                END IF;
+            END IF;
+            CLOSE catchall_cursor;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: address_maildir records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_data,domain_name,maillocation,users TO postfix;
+--
+-- For more details see postconf(5) section virtual_mailbox_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
+   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
+AS $$
+    DECLARE
+        rec address_maildir;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        address varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR rec IN
+            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
+              FROM domain_data, users, maillocation
+             WHERE domain_data.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_uid records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON users, domain_name TO postfix;
+--
+-- For more details see postconf(5) section virtual_uid_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
+AS $$
+    DECLARE
+        record recipient_uid;
+        recipient varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT recipient, uid
+              FROM users
+             WHERE gid = (SELECT gid
+                            FROM domain_name
+                           WHERE domainname = the_domain)
+               AND local_part = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
--- a/pgsql/create_tables.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ b/pgsql/create_tables.pgsql	Thu Jun 28 19:26:50 2012 +0000
@@ -4,8 +4,14 @@
 
 CREATE SEQUENCE transport_id;
 
+CREATE SEQUENCE mailboxformat_id;
+
+CREATE SEQUENCE quotalimit_id;
+
 CREATE SEQUENCE maillocation_id;
 
+CREATE SEQUENCE service_set_id;
+
 CREATE SEQUENCE domain_gid
     START WITH 70000
     INCREMENT BY 1
@@ -30,20 +36,80 @@
 -- Insert default transport
 INSERT INTO transport(transport) VALUES ('dovecot:');
 
-CREATE TABLE maillocation(
-    mid     bigint NOT NULL DEFAULT nextval('maillocation_id'),
-    maillocation varchar(20) NOT NULL,
+CREATE TABLE mailboxformat (
+    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
+    format      varchar(20) NOT NULL,
+    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
+    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
+);
+-- Insert supported mailbox formats
+INSERT INTO mailboxformat(format) VALUES ('maildir');
+INSERT INTO mailboxformat(format) VALUES ('mdbox');
+INSERT INTO mailboxformat(format) VALUES ('sdbox');
+
+CREATE TABLE maillocation (
+    mid         bigint NOT NULL DEFAULT nextval('maillocation_id'),
+    fid         bigint NOT NULL DEFAULT 1,
+    directory   varchar(20) NOT NULL,
+    extra       varchar(1024),
     CONSTRAINT  pkey_maillocation PRIMARY KEY (mid),
-    CONSTRAINT  ukey_maillocation UNIQUE (maillocation)
+    CONSTRAINT  fkey_maillocation_fid_mailboxformat FOREIGN KEY (fid)
+        REFERENCES mailboxformat (fid)
 );
 -- Insert default Maildir-folder name
-INSERT INTO maillocation(maillocation) VALUES ('Maildir');
+INSERT INTO maillocation(directory) VALUES ('Maildir');
+
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+CREATE TABLE service_set (
+    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
+    smtp        boolean NOT NULL DEFAULT TRUE,
+    pop3        boolean NOT NULL DEFAULT TRUE,
+    imap        boolean NOT NULL DEFAULT TRUE,
+    managesieve boolean NOT NULL DEFAULT TRUE,
+    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
+    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, managesieve)
+);
+-- Insert all possible service combinations
+COPY service_set (smtp, pop3, imap, managesieve) FROM stdin;
+TRUE	TRUE	TRUE	TRUE
+FALSE	TRUE	TRUE	TRUE
+TRUE	FALSE	TRUE	TRUE
+FALSE	FALSE	TRUE	TRUE
+TRUE	TRUE	FALSE	TRUE
+FALSE	TRUE	FALSE	TRUE
+TRUE	FALSE	FALSE	TRUE
+FALSE	FALSE	FALSE	TRUE
+TRUE	TRUE	TRUE	FALSE
+FALSE	TRUE	TRUE	FALSE
+TRUE	FALSE	TRUE	FALSE
+FALSE	FALSE	TRUE	FALSE
+TRUE	TRUE	FALSE	FALSE
+FALSE	TRUE	FALSE	FALSE
+TRUE	FALSE	FALSE	FALSE
+FALSE	FALSE	FALSE	FALSE
+\.
 
 CREATE TABLE domain_data (
     gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
-    tid         bigint NOT NULL DEFAULT 1, -- defualt transport
+    qid         bigint NOT NULL DEFAULT 1, -- default quota limit
+    ssid        bigint NOT NULL DEFAULT 1, -- default service_set
+    tid         bigint NOT NULL DEFAULT 1, -- default transport
     domaindir   varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
+    note        text NULL DEFAULT NULL,
     CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
+    CONSTRAINT  fkey_domain_data_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid),
+    CONSTRAINT  fkey_domain_data_ssid_service_set FOREIGN KEY (ssid)
+        REFERENCES service_set (ssid),
     CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
         REFERENCES transport (tid)
 );
@@ -59,26 +125,38 @@
 
 CREATE TABLE users (
     local_part  varchar(64) NOT NULL,-- only localpart w/o '@'
-    passwd      varchar(74) NOT NULL,-- {CRAM-MD5}+64hex numbers
+    passwd      varchar(270) NOT NULL,
     name        varchar(128) NULL,
     uid         bigint NOT NULL DEFAULT nextval('users_uid'),
     gid         bigint NOT NULL,
     mid         bigint NOT NULL DEFAULT 1,
-    tid         bigint NOT NULL DEFAULT 1,
-    smtp        boolean NOT NULL DEFAULT TRUE,
-    pop3        boolean NOT NULL DEFAULT TRUE,
-    imap        boolean NOT NULL DEFAULT TRUE,
-    managesieve boolean NOT NULL DEFAULT TRUE,
+    qid         bigint NULL DEFAULT NULL,
+    ssid        bigint NULL DEFAULT NULL,
+    tid         bigint NULL DEFAULT NULL,
+    note        text NULL DEFAULT NULL,
     CONSTRAINT  pkey_users PRIMARY KEY (local_part, gid),
     CONSTRAINT  ukey_users_uid UNIQUE (uid),
     CONSTRAINT  fkey_users_gid_domain_data FOREIGN KEY (gid)
         REFERENCES domain_data (gid),
     CONSTRAINT  fkey_users_mid_maillocation FOREIGN KEY (mid)
         REFERENCES maillocation (mid),
+    CONSTRAINT  fkey_users_qid_quotalimit FOREIGN KEY (qid)
+        REFERENCES quotalimit (qid),
+    CONSTRAINT fkey_users_ssid_service_set FOREIGN KEY (ssid)
+        REFERENCES service_set (ssid),
     CONSTRAINT  fkey_users_tid_transport FOREIGN KEY (tid)
         REFERENCES transport (tid)
 );
 
+CREATE TABLE userquota_11 (
+    uid         bigint NOT NULL,
+    path        varchar(16) NOT NULL,
+    current     bigint NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
+    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid) ON DELETE CASCADE
+);
+
 CREATE TABLE alias (
     gid         bigint NOT NULL,
     address     varchar(64) NOT NULL,-- only localpart w/o '@'
@@ -97,59 +175,20 @@
         REFERENCES domain_data (gid)
 );
 
-CREATE OR REPLACE VIEW dovecot_password AS
-    SELECT local_part || '@' || domain_name.domainname AS "user",
-           passwd AS "password", smtp, pop3, imap, managesieve
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW dovecot_user AS
-    SELECT local_part || '@' || domain_name.domainname AS userid,
-           uid, gid, domain_data.domaindir || '/' || uid AS home,
-           '~/' || maillocation.maillocation AS mail
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
+CREATE TABLE catchall (
+    gid         bigint NOT NULL,
+    destination varchar(320) NOT NULL,
+    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
+    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
+        REFERENCES domain_data (gid)
+);
 
 CREATE OR REPLACE VIEW postfix_gid AS
     SELECT gid, domainname
       FROM domain_name;
 
-CREATE OR REPLACE VIEW postfix_uid AS
-    SELECT local_part || '@' || domain_name.domainname AS address, uid
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_maildir AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           domain_data.domaindir||'/'||uid||'/'||maillocation.maillocation||'/'
-           AS maildir
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
-
-CREATE OR REPLACE VIEW postfix_relocated AS
-    SELECT address || '@' || domain_name.domainname AS address, destination
-      FROM relocated
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_alias AS
-    SELECT address || '@' || domain_name.domainname AS address, destination, gid
-      FROM alias
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_transport AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           transport.transport
-      FROM users
-           LEFT JOIN transport USING (tid)
-           LEFT JOIN domain_name USING (gid);
-
 CREATE OR REPLACE VIEW vmm_domain_info AS
-    SELECT gid, domainname, transport, domaindir,
-           count(uid) AS accounts,
+    SELECT gid, count(uid) AS accounts,
            (SELECT count(DISTINCT address)
               FROM alias
              WHERE alias.gid = domain_data.gid) AS aliases,
@@ -159,17 +198,92 @@
            (SELECT count(gid)
               FROM domain_name
              WHERE domain_name.gid = domain_data.gid
-               AND NOT domain_name.is_primary) AS aliasdomains
+               AND NOT domain_name.is_primary) AS aliasdomains,
+           (SELECT count(gid)
+              FROM catchall
+             WHERE catchall.gid = domain_data.gid) AS catchall
       FROM domain_data
            LEFT JOIN domain_name USING (gid)
-           LEFT JOIN transport USING (tid)
            LEFT JOIN users USING (gid)
      WHERE domain_name.is_primary
-  GROUP BY gid, domainname, transport, domaindir;
+  GROUP BY gid;
 
+-- ########################################################################## --
 
 CREATE LANGUAGE plpgsql;
 
+-- ######################## TYPEs ########################################### --
+
+-- ---
+-- Data type for function postfix_virtual_mailbox(varchar, varchar)
+-- ---
+CREATE TYPE address_maildir AS (
+    address varchar(320),
+    maildir text
+);
+-- ---
+-- Data type for function dovecotpassword(varchar, varchar)
+-- ---
+CREATE TYPE dovecotpassword AS (
+    userid      varchar(320),
+    password    varchar(270),
+    smtp        boolean,
+    pop3        boolean,
+    imap        boolean,
+    managesieve boolean
+);
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
+-- Data type for function dovecotuser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotuser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text
+);
+-- ---
+-- Data type for functions: postfix_relocated_map(varchar, varchar)
+--                          postfix_virtual_alias_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_destination AS (
+    recipient   varchar(320),
+    destination text
+);
+-- ---
+-- Data type for function postfix_transport_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_transport AS (
+    recipient   varchar(320),
+    transport   text
+);
+-- ---
+-- Data type for function postfix_virtual_uid_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_uid AS (
+    recipient   varchar(320),
+    uid         bigint
+);
+-- ---
+-- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
+-- ---
+CREATE TYPE sender_login AS (
+    sender  varchar(320),
+    login   text
+);
+
+-- ######################## TRIGGERs ######################################## --
 
 CREATE OR REPLACE FUNCTION domain_primary_trigger() RETURNS TRIGGER AS $$
 DECLARE
@@ -194,3 +308,441 @@
 
 CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
     FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
+
+
+CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE userquota_11
+       SET current = current + NEW.current
+     WHERE uid = NEW.uid AND path = NEW.path;
+    IF found THEN
+        RETURN NULL;
+    ELSE
+        RETURN NEW;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
+
+-- ######################## FUNCTIONs ####################################### --
+
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotpassword records
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT ON users, domain_name, service_set TO dovecot;
+--
+-- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
+-- ---
+CREATE OR REPLACE FUNCTION dovecotpassword(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
+AS $$
+    DECLARE
+        record dovecotpassword;
+        userid varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT userid, passwd, smtp, pop3, imap, managesieve
+              FROM users, service_set, domain_data
+             WHERE users.gid = (SELECT gid
+                                  FROM domain_name
+                                 WHERE domainname = the_domain)
+               AND local_part = localpart
+               AND service_set.ssid = users.ssid
+               AND users.gid = domain_data.gid
+               AND CASE WHEN
+                  users.ssid IS NOT NULL
+                  THEN
+                    service_set.ssid = users.ssid
+                  ELSE
+                    service_set.ssid = domain_data.ssid
+                  END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Nearly the same as function dovecotuser below. It returns additionally the
+-- field quota_rule.
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat,
+--             quotalimit
+--          TO dovecot;
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND CASE WHEN
+                     users.qid IS NOT NULL
+                   THEN
+                     quotalimit.qid = users.qid
+                   ELSE
+                     quotalimit.qid = domain_data.qid
+                   END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotuser records
+--
+-- Required access privileges for your dovecot database user:
+--      GRANT SELECT
+--          ON users, domain_data, domain_name, maillocation, mailboxformat
+--          TO dovecot;
+--
+-- For more details see http://wiki.dovecot.org/UserDatabase
+-- ---
+CREATE OR REPLACE FUNCTION dovecotuser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
+AS $$
+    DECLARE
+        record dovecotuser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail
+              FROM users, domain_data, mailboxformat, maillocation
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_name, relocated TO postfix;
+--
+-- For more details see postconf(5) section relocated_maps and relocated(5)
+-- ---
+CREATE OR REPLACE FUNCTION postfix_relocated_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        record recipient_destination;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient, destination
+              FROM relocated
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
+--      set of sender_login records.
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_name, users, alias TO postfix;
+--
+-- For more details see postconf(5) section smtpd_sender_login_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
+AS $$
+    DECLARE
+        rec sender_login;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        sender varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        -- Get all addresses for 'localpart' in the primary and aliased domains
+        FOR rec IN
+            SELECT sender, local_part || '@' || domainname
+              FROM domain_name, users
+             WHERE domain_name.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        IF NOT FOUND THEN
+            -- Loop over the alias addresses for localpart@the_domain
+            FOR rec IN
+                SELECT DISTINCT sender, destination
+                  FROM alias
+                 WHERE alias.gid = did
+                   AND alias.address = localpart
+                LOOP
+                    RETURN NEXT rec;
+                END LOOP;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_transport records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON users, transport, domain_name TO postfix;
+--
+-- For more details see postconf(5) section transport_maps and transport(5)
+-- ---
+CREATE OR REPLACE FUNCTION postfix_transport_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_transport
+AS $$
+    DECLARE
+        record recipient_transport;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
+        transport_id bigint;
+    BEGIN
+        IF did IS NULL THEN
+            RETURN;
+        END IF;
+
+        SELECT tid INTO transport_id
+          FROM users
+         WHERE gid = did AND local_part = localpart;
+
+        IF transport_id IS NULL THEN
+            SELECT tid INTO STRICT transport_id
+              FROM domain_data
+             WHERE gid = did;
+        END IF;
+
+        FOR record IN
+            SELECT recipient, transport
+              FROM transport
+             WHERE tid = transport_id
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON alias, domain_name TO postfix;
+--
+-- For more details see postconf(5) section virtual_alias_maps and virtual(5)
+-- ---
+CREATE OR REPLACE FUNCTION _interpolate_destination(
+    IN destination varchar, localpart varchar, IN the_domain varchar)
+    RETURNS varchar
+AS $$
+    DECLARE
+        result varchar(320);
+    BEGIN
+        IF position('%' in destination) = 0 THEN
+            RETURN destination;
+        END IF;
+        result := replace(destination, '%n', localpart);
+        result := replace(result, '%d', the_domain);
+        result := replace(result, '%=', localpart || '=' || the_domain);
+        RETURN result;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
+CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        recordc recipient_destination;
+        record recipient_destination;
+        catchall_cursor refcursor;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient,
+                _interpolate_destination(destination, localpart, the_domain)
+              FROM alias
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+
+        IF NOT FOUND THEN
+            -- There is no matching virtual_alias. If there are no catchall
+            -- records for this domain, we can just return NULL since Postfix
+            -- will then later consult mailboxes/relocated itself. But if
+            -- there is a catchall destination, then it would take precedence
+            -- over mailboxes/relocated, which is not what we want. Therefore,
+            -- we must first find out if the query is for an existing mailbox
+            -- or relocated entry and return the identity mapping if that is
+            -- the case
+            OPEN catchall_cursor FOR
+                SELECT recipient,
+                    _interpolate_destination(destination, localpart, the_domain)
+                  FROM catchall
+                 WHERE gid = did;
+            FETCH NEXT FROM catchall_cursor INTO recordc;
+
+            IF recordc IS NOT NULL THEN
+                -- Since there are catchall records for this domain
+                -- check the mailbox and relocated records and return identity
+                -- if a matching record exists.
+                FOR record IN
+                    SELECT recipient, recipient as destination
+                      FROM users
+                    WHERE gid = did
+                      AND local_part = localpart
+                    UNION SELECT recipient, recipient as destination
+                      FROM relocated
+                    WHERE gid = did
+                      AND address = localpart
+                    LOOP
+                        RETURN NEXT record;
+                    END LOOP;
+
+                IF NOT FOUND THEN
+                    -- There were no records found for mailboxes/relocated,
+                    -- so now we can actually iterate the cursor and populate
+                    -- the return set
+                    LOOP
+                        RETURN NEXT recordc;
+                        FETCH NEXT FROM catchall_cursor INTO recordc;
+                        EXIT WHEN recordc IS NULL;
+                    END LOOP;
+                END IF;
+            END IF;
+            CLOSE catchall_cursor;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: address_maildir records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON domain_data,domain_name,maillocation,users TO postfix;
+--
+-- For more details see postconf(5) section virtual_mailbox_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
+   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
+AS $$
+    DECLARE
+        rec address_maildir;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        address varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR rec IN
+            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
+              FROM domain_data, users, maillocation
+             WHERE domain_data.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_uid records
+--
+-- Required access privileges for your postfix database user:
+--      GRANT SELECT ON users, domain_name TO postfix;
+--
+-- For more details see postconf(5) section virtual_uid_maps
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
+AS $$
+    DECLARE
+        record recipient_uid;
+        recipient varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT recipient, uid
+              FROM users
+             WHERE gid = (SELECT gid
+                            FROM domain_name
+                           WHERE domainname = the_domain)
+               AND local_part = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pgsql/set-permissions.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+# coding: utf-8
+# Copyright 2012, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+    Use this script in order to set database permissions for your Dovecot
+    and Postfix database users.
+
+    Run `python set-permissions.py -h` for details.
+"""
+
+import getpass
+import sys
+
+from optparse import OptionParser
+
+has_psycopg2 = False
+try:
+    import psycopg2
+    has_psycopg2 = True
+except ImportError:
+    try:
+        from pyPgSQL import PgSQL
+    except ImportError:
+        sys.stderr.write('error: no suitable database module found\n')
+        raise SystemExit(1)
+
+if has_psycopg2:
+    DBErr = psycopg2.DatabaseError
+else:
+    DBErr = PgSQL.libpq.DatabaseError
+
+
+def check_opts(opts, err_hdlr):
+    if not opts.postfix:
+        err_hdlr('missing Postfix database user name')
+    if not opts.dovecot:
+        err_hdlr('missing Dovecot database user name')
+    if opts.askp:
+        opts.dbpass = getpass.getpass()
+
+
+def get_dbh(database, user, password, host, port):
+    if has_psycopg2:
+        return psycopg2.connect(database=database, user=user,
+                                password=password, host=host, port=port)
+    return PgSQL.connect(user=user, password=password, host=host,
+                         database=database, port=port)
+
+
+def get_optparser():
+    descr = 'Set permissions for Dovecot and Postfix in the vmm database.'
+    usage = 'usage: %prog OPTIONS'
+    parser = OptionParser(description=descr, usage=usage)
+    parser.add_option('-a', '--askpass', dest='askp', default=False,
+            action='store_true', help='Prompt for the database password.')
+    parser.add_option('-H', '--host', dest='host', metavar='HOST',
+            default=None,
+            help='Hostname or IP address of the database server. Leave ' +
+                 'blank in order to use the default Unix-domain socket.')
+    parser.add_option('-n', '--name', dest='name', metavar='NAME',
+            default='mailsys',
+            help='Specifies the name of the database to connect to. ' +
+                 'Default: %default')
+    parser.add_option('-p', '--pass', dest="dbpass", metavar='PASS',
+            default=None, help='Password for the database connection.')
+    parser.add_option('-P', '--port', dest='port', metavar='PORT', type='int',
+            default=5432,
+            help='Specifies the TCP port or the local Unix-domain socket ' +
+                 'file extension on which the server is listening for ' +
+                 'connections. Default: %default')
+    parser.add_option('-U', '--user', dest='user', metavar='USER',
+            default=getpass.getuser(),
+            help='Connect to the database as the user USER instead of the ' +
+                 'default: %default')
+    parser.add_option('-D', '--dovecot', dest='dovecot', metavar='USER',
+            default='dovecot',
+            help='Database user name of the Dovecot database user. Default: ' +
+                 '%default')
+    parser.add_option('-M', '--postfix', dest='postfix', metavar='USER',
+            default='postfix',
+            help='Database user name of the Postfix (MTA)  database user. ' +
+                 'Default: %default')
+    return parser
+
+
+def set_permissions(dbh, dc_vers, dovecot, postfix):
+    dc_rw = ('userquota_11', 'userquota')[dc_vers == 12]
+    dbc = dbh.cursor()
+    dbc.execute('GRANT SELECT ON domain_data, domain_name, mailboxformat, '
+                'maillocation, quotalimit, service_set, users TO %s' % dovecot)
+    dbc.execute('GRANT SELECT, INSERT, UPDATE, DELETE ON %s TO %s' %
+                (dc_rw, dovecot))
+    dbc.execute('GRANT SELECT ON alias, catchall, domain_data, domain_name, '
+                'maillocation, postfix_gid, relocated, transport, users TO %s'
+                % postfix)
+    dbc.close()
+
+
+def set_permissions84(dbh, dc_vers, dovecot, postfix):
+    dc_rw_tbls = ('userquota_11', 'userquota')[dc_vers == 12]
+    dc_ro_tbls = 'mailboxformat, maillocation, service_set, quotalimit'
+    pf_ro_tbls = 'alias, catchall, postfix_gid, relocated, transport'
+    db = dict(dovecot=dovecot, postfix=postfix)
+    db['dovecot_tbls'] = {
+        'domain_data': 'domaindir, gid, qid, ssid',
+        'domain_name': 'domainname, gid',
+        'users': 'gid, local_part, mid, passwd, qid, ssid, uid',
+    }
+    db['postfix_tbls'] = {
+        'domain_data': 'domaindir, gid, tid',
+        'domain_name': 'domainname, gid',
+        'maillocation': 'directory, mid',
+        'users': 'gid, local_part, mid, tid, uid',
+    }
+    dbc = dbh.cursor()
+    dbc.execute('GRANT SELECT, INSERT, UPDATE, DELETE ON %s TO %s' %
+                (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():
+        dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
+                                                       db['dovecot']))
+    for table, columns in db['postfix_tbls'].iteritems():
+        dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
+                                                       db['postfix']))
+    dbc.close()
+
+
+def set_versions(dbh, versions):
+    dbc = dbh.cursor()
+    if hasattr(dbh, 'server_version'):
+        versions['pgsql'] = dbh.server_version
+    else:
+        try:
+            dbc.execute("SELECT current_setting('server_version_num')")
+            versions['pgsql'] = int(dbc.fetchone()[0])
+        except DBErr:
+            versions['pgsql'] = 80199
+    dbc.execute("SELECT relname FROM pg_stat_user_tables WHERE relname LIKE "
+                "'userquota%'")
+    res = dbc.fetchall()
+    dbc.close()
+    tbls = [tbl[0] for tbl in res]
+    if 'userquota' in tbls:
+        versions['dovecot'] = 12
+    elif 'userquota_11' in tbls:
+        versions['dovecot'] = 11
+    else:
+        sys.stderr.write('error: no userquota table found\nis "' + dbh.dsn +
+                         '" correct? is the database up to date?\n')
+        dbh.close()
+        raise SystemExit(1)
+
+
+if __name__ == '__main__':
+    optparser = get_optparser()
+    opts, args = optparser.parse_args()
+    check_opts(opts, optparser.error)
+    dbh = get_dbh(opts.name, opts.user, opts.dbpass, opts.host, opts.port)
+    versions = {}
+    set_versions(dbh, versions)
+    if versions['pgsql'] < 80400:
+        set_permissions(dbh, versions['dovecot'], opts.dovecot, opts.postfix)
+    else:
+        set_permissions84(dbh, versions['dovecot'], opts.dovecot, opts.postfix)
+    dbh.commit()
+    dbh.close()
--- a/pgsql/update_tables_0.4.x-0.5.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-SET client_encoding = 'UTF8';
-SET client_min_messages = warning;
-
-ALTER SEQUENCE domains_gid RENAME TO domain_gid;
-
-
-CREATE TABLE domain_data (
-    gid         bigint NOT NULL DEFAULT nextval('domain_gid'),
-    tid         bigint NOT NULL DEFAULT 1,
-    domaindir   varchar(40) NOT NULL,
-    CONSTRAINT  pkey_domain_data PRIMARY KEY (gid),
-    CONSTRAINT  fkey_domain_data_tid_transport FOREIGN KEY (tid)
-        REFERENCES transport (tid)
-);
-
-CREATE TABLE domain_name (
-    domainname  varchar(255) NOT NULL,
-    gid         bigint NOT NULL,
-    is_primary  boolean NOT NULL,
-    CONSTRAINT  pkey_domain_name PRIMARY KEY (domainname),
-    CONSTRAINT  fkey_domain_name_gid_domain_data FOREIGN KEY (gid)
-        REFERENCES domain_data (gid)
-);
-
-INSERT INTO domain_data (gid, tid, domaindir) 
-    SELECT gid, tid, domaindir
-      FROM domains;
-
-INSERT INTO domain_name (domainname, gid, is_primary) 
-    SELECT domainname, gid, TRUE
-      FROM domains;
-
-
-ALTER TABLE users DROP CONSTRAINT pkye_users;
-ALTER TABLE users ADD CONSTRAINT  pkey_users PRIMARY KEY (local_part, gid);
-ALTER TABLE users DROP CONSTRAINT fkey_users_gid_domains;
-ALTER TABLE users ADD CONSTRAINT fkey_users_gid_domain_data FOREIGN KEY (gid)
-    REFERENCES domain_data (gid);
-
-ALTER TABLE alias DROP CONSTRAINT fkey_alias_gid_domains;
-ALTER TABLE alias DROP CONSTRAINT pkey_alias;
-ALTER TABLE alias ADD CONSTRAINT fkey_alias_gid_domain_data FOREIGN KEY (gid)
-    REFERENCES domain_data (gid);
-
-ALTER TABLE relocated DROP CONSTRAINT fkey_relocated_gid_domains;
-ALTER TABLE relocated ADD CONSTRAINT fkey_relocated_gid_domain_data
-    FOREIGN KEY (gid) REFERENCES domain_data (gid);
-
-
-CREATE OR REPLACE VIEW dovecot_password AS
-    SELECT local_part || '@' || domain_name.domainname AS "user",
-           passwd AS "password", smtp, pop3, imap, managesieve
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW dovecot_user AS
-    SELECT local_part || '@' || domain_name.domainname AS userid,
-           uid, gid, domain_data.domaindir || '/' || uid AS home,
-           '~/' || maillocation.maillocation AS mail
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
-
-CREATE OR REPLACE VIEW postfix_gid AS
-    SELECT gid, domainname
-      FROM domain_name;
-
-CREATE OR REPLACE VIEW postfix_uid AS
-    SELECT local_part || '@' || domain_name.domainname AS address, uid
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_maildir AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           domain_data.domaindir||'/'||uid||'/'||maillocation.maillocation||'/'
-           AS maildir
-      FROM users
-           LEFT JOIN domain_data USING (gid)
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN maillocation USING (mid);
-
-CREATE OR REPLACE VIEW postfix_relocated AS
-    SELECT address || '@' || domain_name.domainname AS address, destination
-      FROM relocated
-           LEFT JOIN domain_name USING (gid);
-
-DROP VIEW postfix_alias;
-DROP VIEW vmm_domain_info;
-DROP VIEW vmm_alias_count;
-
-ALTER TABLE alias ALTER address TYPE varchar(64);
-ALTER TABLE alias ADD CONSTRAINT pkey_alias 
-    PRIMARY KEY (gid, address, destination);
-
-CREATE OR REPLACE VIEW postfix_alias AS
-    SELECT address || '@' || domain_name.domainname AS address, destination, gid
-      FROM alias
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW postfix_transport AS
-    SELECT local_part || '@' || domain_name.domainname AS address,
-           transport.transport
-      FROM users
-           LEFT JOIN transport USING (tid)
-           LEFT JOIN domain_name USING (gid);
-
-CREATE OR REPLACE VIEW vmm_domain_info AS
-    SELECT gid, domainname, transport, domaindir,
-           count(uid) AS accounts,
-           (SELECT count(DISTINCT address)
-              FROM alias
-             WHERE alias.gid = domain_data.gid) AS aliases,
-           (SELECT count(gid)
-              FROM relocated
-             WHERE relocated.gid = domain_data.gid) AS relocated,
-           (SELECT count(gid)
-              FROM domain_name
-             WHERE domain_name.gid = domain_data.gid
-               AND NOT domain_name.is_primary) AS aliasdomains
-      FROM domain_data
-           LEFT JOIN domain_name USING (gid)
-           LEFT JOIN transport USING (tid)
-           LEFT JOIN users USING (gid)
-     WHERE domain_name.is_primary
-  GROUP BY gid, domainname, transport, domaindir;
-
-
-DROP TABLE domains;
-
-
-CREATE LANGUAGE plpgsql;
-
-CREATE OR REPLACE FUNCTION domain_primary_trigger() RETURNS TRIGGER AS $$
-DECLARE
-    primary_count bigint;
-BEGIN
-    SELECT INTO primary_count count(gid) + NEW.is_primary::integer
-      FROM domain_name
-     WHERE domain_name.gid = NEW.gid
-       AND is_primary;
-
-    IF (primary_count > 1) THEN
-        RAISE EXCEPTION 'There can only be one domain marked as primary.';
-    END IF;
-
-    RETURN NEW;
-END;
-$$ LANGUAGE plpgsql STABLE;
-
-DROP TRIGGER IF EXISTS primary_count ON domain_name;
-CREATE TRIGGER primary_count_ins BEFORE INSERT ON domain_name
-    FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
-
-CREATE TRIGGER primary_count_upd AFTER UPDATE ON domain_name
-    FOR EACH ROW EXECUTE PROCEDURE domain_primary_trigger();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pgsql/update_tables_0.5.x-0.6-dovecot-1.2.x.pgsql	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,680 @@
+SET client_encoding = 'UTF8';
+SET client_min_messages = warning;
+
+-- ---
+-- Create the new service_set table and insert all possible combinations
+-- --
+CREATE SEQUENCE service_set_id;
+
+CREATE TABLE service_set (
+    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
+    smtp        boolean NOT NULL DEFAULT TRUE,
+    pop3        boolean NOT NULL DEFAULT TRUE,
+    imap        boolean NOT NULL DEFAULT TRUE,
+    sieve       boolean NOT NULL DEFAULT TRUE,
+    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
+    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, sieve)
+);
+
+COPY service_set (smtp, pop3, imap, sieve) FROM stdin;
+TRUE	TRUE	TRUE	TRUE
+FALSE	TRUE	TRUE	TRUE
+TRUE	FALSE	TRUE	TRUE
+FALSE	FALSE	TRUE	TRUE
+TRUE	TRUE	FALSE	TRUE
+FALSE	TRUE	FALSE	TRUE
+TRUE	FALSE	FALSE	TRUE
+FALSE	FALSE	FALSE	TRUE
+TRUE	TRUE	TRUE	FALSE
+FALSE	TRUE	TRUE	FALSE
+TRUE	FALSE	TRUE	FALSE
+FALSE	FALSE	TRUE	FALSE
+TRUE	TRUE	FALSE	FALSE
+FALSE	TRUE	FALSE	FALSE
+TRUE	FALSE	FALSE	FALSE
+FALSE	FALSE	FALSE	FALSE
+\.
+
+-- ---
+-- Drop the obsolete VIEWs, we've functions now.
+-- ---
+DROP VIEW dovecot_user;
+DROP VIEW dovecot_password;
+DROP VIEW postfix_alias;
+DROP VIEW postfix_maildir;
+DROP VIEW postfix_relocated;
+DROP VIEW postfix_transport;
+DROP VIEW postfix_uid;
+-- the vmm_domain_info view will be restored later
+DROP VIEW vmm_domain_info;
+
+CREATE SEQUENCE mailboxformat_id;
+CREATE SEQUENCE quotalimit_id;
+
+CREATE TABLE mailboxformat (
+    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
+    format      varchar(20) NOT NULL,
+    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
+    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
+);
+-- Insert supported mailbox formats
+INSERT INTO mailboxformat(format) VALUES ('maildir');
+INSERT INTO mailboxformat(format) VALUES ('mdbox');
+INSERT INTO mailboxformat(format) VALUES ('sdbox');
+
+-- Adjust maillocation table
+ALTER TABLE maillocation DROP CONSTRAINT ukey_maillocation;
+ALTER TABLE maillocation RENAME COLUMN maillocation TO directory;
+ALTER TABLE maillocation
+    ADD COLUMN fid bigint NOT NULL DEFAULT 1,
+    ADD COLUMN extra varchar(1024);
+ALTER TABLE maillocation ADD CONSTRAINT fkey_maillocation_fid_mailboxformat
+    FOREIGN KEY (fid) REFERENCES mailboxformat (fid);
+
+ALTER TABLE users ALTER COLUMN passwd TYPE varchar(270);
+
+-- ---
+-- Add quota stuff
+-- ---
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+-- Adjust tables (quota)
+ALTER TABLE domain_data ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+ALTER TABLE users ADD COLUMN qid bigint NULL DEFAULT NULL;
+ALTER TABLE users ADD CONSTRAINT fkey_users_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+CREATE TABLE userquota (
+    uid         bigint NOT NULL,
+    bytes       bigint NOT NULL DEFAULT 0,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota PRIMARY KEY (uid),
+    CONSTRAINT  fkey_userquota_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid) ON DELETE CASCADE
+);
+
+CREATE OR REPLACE FUNCTION merge_userquota() RETURNS TRIGGER AS $$
+BEGIN
+    IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+        IF NEW.messages IS NULL THEN
+            NEW.messages = 0;
+        ELSE
+            NEW.messages = -NEW.messages;
+        END IF;
+        RETURN NEW;
+    END IF;
+    LOOP
+        UPDATE userquota
+           SET bytes = bytes + NEW.bytes, messages = messages + NEW.messages
+         WHERE uid = NEW.uid;
+        IF found THEN
+            RETURN NULL;
+        END IF;
+        BEGIN
+            IF NEW.messages = 0 THEN
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, NULL);
+            ELSE
+              INSERT INTO userquota VALUES (NEW.uid, NEW.bytes, -NEW.messages);
+            END IF;
+            RETURN NULL;
+        EXCEPTION
+            WHEN unique_violation THEN
+                -- do nothing, and loop to try the UPDATE again
+            WHEN foreign_key_violation THEN
+                -- break the loop: a non matching uid means no such user
+                RETURN NULL;
+        END;
+    END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota BEFORE INSERT ON userquota
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota();
+
+-- Adjust tables (services)
+ALTER TABLE domain_data ADD COLUMN ssid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_ssid_service_set
+    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
+
+ALTER TABLE users ADD COLUMN ssid bigint NULL DEFAULT NULL;
+-- save current service sets
+UPDATE users u
+   SET ssid = ss.ssid
+  FROM service_set ss
+ WHERE ss.smtp = u.smtp
+   AND ss.pop3 = u.pop3
+   AND ss.imap = u.imap
+   AND ss.sieve = u.sieve;
+
+ALTER TABLE users DROP COLUMN smtp;
+ALTER TABLE users DROP COLUMN pop3;
+ALTER TABLE users DROP COLUMN imap;
+ALTER TABLE users DROP COLUMN sieve;
+ALTER TABLE users ADD CONSTRAINT fkey_users_ssid_service_set
+    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
+
+-- ---
+-- Catchall
+-- ---
+
+CREATE TABLE catchall (
+    gid         bigint NOT NULL,
+    destination varchar(320) NOT NULL,
+    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
+    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
+        REFERENCES domain_data (gid)
+);
+
+-- ---
+-- Quota/Service/Transport inheritance
+-- ---
+ALTER TABLE users ALTER COLUMN tid DROP NOT NULL;
+ALTER TABLE users ALTER COLUMN tid SET DEFAULT NULL;
+-- The qid and ssid columns have already been defined accordingly above.
+-- The rest of the logic will take place in the functions.
+
+-- While qid and ssid are new and it's perfectly okay for existing users to
+-- get NULL values (i.e. inherit from the domain's default), tid existed in
+-- vmm 0.5.x. A sensible way forward seems thus to NULL all user records' tid
+-- fields where the tid duplicates the value stored in the domain's record.
+UPDATE users
+   SET tid = NULL
+ WHERE tid = (SELECT tid
+                FROM domain_data
+               WHERE domain_data.gid = users.gid);
+
+-- ---
+-- Account/domain notes
+-- ---
+
+ALTER TABLE users ADD COLUMN note text NULL DEFAULT NULL;
+ALTER TABLE domain_data ADD COLUMN note text NULL DEFAULT NULL;
+
+-- ---
+-- Restore view
+-- ---
+CREATE VIEW vmm_domain_info AS
+    SELECT gid, count(uid) AS accounts,
+           (SELECT count(DISTINCT address)
+              FROM alias
+             WHERE alias.gid = domain_data.gid) AS aliases,
+           (SELECT count(gid)
+              FROM relocated
+             WHERE relocated.gid = domain_data.gid) AS relocated,
+           (SELECT count(gid)
+              FROM domain_name
+             WHERE domain_name.gid = domain_data.gid
+               AND NOT domain_name.is_primary) AS aliasdomains,
+           (SELECT count(gid)
+              FROM catchall
+             WHERE catchall.gid = domain_data.gid) AS catchall
+      FROM domain_data
+           LEFT JOIN domain_name USING (gid)
+           LEFT JOIN users USING (gid)
+     WHERE domain_name.is_primary
+  GROUP BY gid;
+
+-- ---
+-- Drop all known v0.5 types (the dirty way)
+-- ---
+DROP TYPE address_maildir CASCADE;
+DROP TYPE dovecotpassword CASCADE;
+DROP TYPE dovecotuser CASCADE;
+DROP TYPE recipient_destination CASCADE;
+DROP TYPE recipient_transport CASCADE;
+DROP TYPE recipient_uid CASCADE;
+DROP TYPE sender_login CASCADE;
+
+-- ######################## TYPEs ########################################### --
+
+-- ---
+-- Data type for function postfix_virtual_mailbox(varchar, varchar)
+-- ---
+CREATE TYPE address_maildir AS (
+    address varchar(320),
+    maildir text
+);
+-- ---
+-- Data type for function dovecotpassword(varchar, varchar)
+-- ---
+CREATE TYPE dovecotpassword AS (
+    userid    varchar(320),
+    password  varchar(270),
+    smtp      boolean,
+    pop3      boolean,
+    imap      boolean,
+    sieve     boolean
+);
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
+-- Data type for function dovecotuser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotuser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text
+);
+-- ---
+-- Data type for functions: postfix_relocated_map(varchar, varchar)
+--                          postfix_virtual_alias_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_destination AS (
+    recipient   varchar(320),
+    destination text
+);
+-- ---
+-- Data type for function postfix_transport_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_transport AS (
+    recipient   varchar(320),
+    transport   text
+);
+-- ---
+-- Data type for function postfix_virtual_uid_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_uid AS (
+    recipient   varchar(320),
+    uid         bigint
+);
+-- ---
+-- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
+-- ---
+CREATE TYPE sender_login AS (
+    sender  varchar(320),
+    login   text
+);
+
+-- ######################## FUNCTIONs ####################################### --
+
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotpassword records
+-- ---
+CREATE OR REPLACE FUNCTION dovecotpassword(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
+AS $$
+    DECLARE
+        record dovecotpassword;
+        userid varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT userid, passwd, smtp, pop3, imap, sieve
+              FROM users, service_set, domain_data
+             WHERE users.gid = (SELECT gid
+                                  FROM domain_name
+                                 WHERE domainname = the_domain)
+               AND local_part = localpart
+               AND users.gid = domain_data.gid
+               AND CASE WHEN
+                     users.ssid IS NOT NULL
+                     THEN
+                       service_set.ssid = users.ssid
+                     ELSE
+                       service_set.ssid = domain_data.ssid
+                     END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Nearly the same as function dovecotuser below. It returns additionally the
+-- field quota_rule.
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND CASE WHEN
+                     users.qid IS NOT NULL
+                   THEN
+                     quotalimit.qid = users.qid
+                   ELSE
+                     quotalimit.qid = domain_data.qid
+                   END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotuser records
+-- ---
+CREATE OR REPLACE FUNCTION dovecotuser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
+AS $$
+    DECLARE
+        record dovecotuser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail
+              FROM users, domain_data, mailboxformat, maillocation
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_relocated_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        record recipient_destination;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient, destination
+              FROM relocated
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
+--      set of sender_login records.
+-- ---
+CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
+AS $$
+    DECLARE
+        rec sender_login;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        sender varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        -- Get all addresses for 'localpart' in the primary and aliased domains
+        FOR rec IN
+            SELECT sender, local_part || '@' || domainname
+              FROM domain_name, users
+             WHERE domain_name.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        IF NOT FOUND THEN
+            -- Loop over the alias addresses for localpart@the_domain
+            FOR rec IN
+                SELECT DISTINCT sender, destination
+                  FROM alias
+                 WHERE alias.gid = did
+                   AND alias.address = localpart
+                LOOP
+                    RETURN NEXT rec;
+                END LOOP;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_transport records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_transport_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_transport
+AS $$
+    DECLARE
+        record recipient_transport;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
+        transport_id bigint;
+    BEGIN
+        IF did IS NULL THEN
+            RETURN;
+        END IF;
+
+        SELECT tid INTO transport_id
+          FROM users
+         WHERE gid = did AND local_part = localpart;
+
+        IF transport_id IS NULL THEN
+            SELECT tid INTO STRICT transport_id
+              FROM domain_data
+             WHERE gid = did;
+        END IF;
+
+        FOR record IN
+            SELECT recipient, transport
+              FROM transport
+             WHERE tid = transport_id
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+-- ---
+CREATE OR REPLACE FUNCTION _interpolate_destination(
+    IN destination varchar, localpart varchar, IN the_domain varchar)
+    RETURNS varchar
+AS $$
+    DECLARE
+        result varchar(320);
+    BEGIN
+        IF position('%' in destination) = 0 THEN
+            RETURN destination;
+        END IF;
+        result := replace(destination, '%n', localpart);
+        result := replace(result, '%d', the_domain);
+        result := replace(result, '%=', localpart || '=' || the_domain);
+        RETURN result;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
+CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        recordc recipient_destination;
+        record recipient_destination;
+        catchall_cursor refcursor;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient,
+                _interpolate_destination(destination, localpart, the_domain)
+              FROM alias
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+
+        IF NOT FOUND THEN
+            -- There is no matching virtual_alias. If there are no catchall
+            -- records for this domain, we can just return NULL since Postfix
+            -- will then later consult mailboxes/relocated itself. But if
+            -- there is a catchall destination, then it would take precedence
+            -- over mailboxes/relocated, which is not what we want. Therefore,
+            -- we must first find out if the query is for an existing mailbox
+            -- or relocated entry and return the identity mapping if that is
+            -- the case
+            OPEN catchall_cursor FOR
+                SELECT recipient,
+                    _interpolate_destination(destination, localpart, the_domain)
+                  FROM catchall
+                 WHERE gid = did;
+            FETCH NEXT FROM catchall_cursor INTO recordc;
+
+            IF recordc IS NOT NULL THEN
+                -- Since there are catchall records for this domain
+                -- check the mailbox and relocated records and return identity
+                -- if a matching record exists.
+                FOR record IN
+                    SELECT recipient, recipient as destination
+                      FROM users
+                    WHERE gid = did
+                      AND local_part = localpart
+                    UNION SELECT recipient, recipient as destination
+                      FROM relocated
+                    WHERE gid = did
+                      AND address = localpart
+                    LOOP
+                        RETURN NEXT record;
+                    END LOOP;
+
+                IF NOT FOUND THEN
+                    -- There were no records found for mailboxes/relocated,
+                    -- so now we can actually iterate the cursor and populate
+                    -- the return set
+                    LOOP
+                        RETURN NEXT recordc;
+                        FETCH NEXT FROM catchall_cursor INTO recordc;
+                        EXIT WHEN recordc IS NULL;
+                    END LOOP;
+                END IF;
+            END IF;
+            CLOSE catchall_cursor;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: address_maildir records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
+   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
+AS $$
+    DECLARE
+        rec address_maildir;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        address varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR rec IN
+            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
+              FROM domain_data, users, maillocation
+             WHERE domain_data.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_uid records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
+AS $$
+    DECLARE
+        record recipient_uid;
+        recipient varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT recipient, uid
+              FROM users
+             WHERE gid = (SELECT gid
+                            FROM domain_name
+                           WHERE domainname = the_domain)
+               AND local_part = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pgsql/update_tables_0.5.x-0.6.pgsql	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,659 @@
+SET client_encoding = 'UTF8';
+SET client_min_messages = warning;
+
+-- ---
+-- Create the new service_set table and insert all possible combinations
+-- --
+CREATE SEQUENCE service_set_id;
+
+CREATE TABLE service_set (
+    ssid        bigint NOT NULL DEFAULT nextval('service_set_id'),
+    smtp        boolean NOT NULL DEFAULT TRUE,
+    pop3        boolean NOT NULL DEFAULT TRUE,
+    imap        boolean NOT NULL DEFAULT TRUE,
+    managesieve boolean NOT NULL DEFAULT TRUE,
+    CONSTRAINT  pkey_service_set PRIMARY KEY (ssid),
+    CONSTRAINT  ukey_service_set UNIQUE (smtp, pop3, imap, managesieve)
+);
+
+COPY service_set (smtp, pop3, imap, managesieve) FROM stdin;
+TRUE	TRUE	TRUE	TRUE
+FALSE	TRUE	TRUE	TRUE
+TRUE	FALSE	TRUE	TRUE
+FALSE	FALSE	TRUE	TRUE
+TRUE	TRUE	FALSE	TRUE
+FALSE	TRUE	FALSE	TRUE
+TRUE	FALSE	FALSE	TRUE
+FALSE	FALSE	FALSE	TRUE
+TRUE	TRUE	TRUE	FALSE
+FALSE	TRUE	TRUE	FALSE
+TRUE	FALSE	TRUE	FALSE
+FALSE	FALSE	TRUE	FALSE
+TRUE	TRUE	FALSE	FALSE
+FALSE	TRUE	FALSE	FALSE
+TRUE	FALSE	FALSE	FALSE
+FALSE	FALSE	FALSE	FALSE
+\.
+
+-- ---
+-- Drop the obsolete VIEWs, we've functions now.
+-- ---
+DROP VIEW dovecot_user;
+DROP VIEW dovecot_password;
+DROP VIEW postfix_alias;
+DROP VIEW postfix_maildir;
+DROP VIEW postfix_relocated;
+DROP VIEW postfix_transport;
+DROP VIEW postfix_uid;
+-- the vmm_domain_info view will be restored later
+DROP VIEW vmm_domain_info;
+
+CREATE SEQUENCE mailboxformat_id;
+CREATE SEQUENCE quotalimit_id;
+
+CREATE TABLE mailboxformat (
+    fid         bigint NOT NULL DEFAULT nextval('mailboxformat_id'),
+    format      varchar(20) NOT NULL,
+    CONSTRAINT  pkey_mailboxformat PRIMARY KEY (fid),
+    CONSTRAINT  ukey_mailboxformat UNIQUE (format)
+);
+-- Insert supported mailbox formats
+INSERT INTO mailboxformat(format) VALUES ('maildir');
+INSERT INTO mailboxformat(format) VALUES ('mdbox');
+INSERT INTO mailboxformat(format) VALUES ('sdbox');
+
+-- Adjust maillocation table
+ALTER TABLE maillocation DROP CONSTRAINT ukey_maillocation;
+ALTER TABLE maillocation RENAME COLUMN maillocation TO directory;
+ALTER TABLE maillocation
+    ADD COLUMN fid bigint NOT NULL DEFAULT 1,
+    ADD COLUMN extra varchar(1024);
+ALTER TABLE maillocation ADD CONSTRAINT fkey_maillocation_fid_mailboxformat
+    FOREIGN KEY (fid) REFERENCES mailboxformat (fid);
+
+ALTER TABLE users ALTER COLUMN passwd TYPE varchar(270);
+
+-- ---
+-- Add quota stuff
+-- ---
+CREATE TABLE quotalimit (
+    qid         bigint NOT NULL DEFAULT nextval('quotalimit_id'),
+    bytes       bigint NOT NULL,
+    messages    integer NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_quotalimit PRIMARY KEY (qid),
+    CONSTRAINT  ukey_quotalimit UNIQUE (bytes, messages)
+);
+-- Insert default (non) quota limit
+INSERT INTO quotalimit(bytes, messages) VALUES (0, 0);
+
+-- Adjust tables (quota)
+ALTER TABLE domain_data ADD COLUMN qid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+ALTER TABLE users ADD COLUMN qid bigint NULL DEFAULT NULL;
+ALTER TABLE users ADD CONSTRAINT fkey_users_qid_quotalimit
+    FOREIGN KEY (qid) REFERENCES quotalimit (qid);
+
+CREATE TABLE userquota_11 (
+    uid         bigint NOT NULL,
+    path        varchar(16) NOT NULL,
+    current     bigint NOT NULL DEFAULT 0,
+    CONSTRAINT  pkey_userquota_11 PRIMARY KEY (uid, path),
+    CONSTRAINT  fkey_userquota_11_uid_users FOREIGN KEY (uid)
+        REFERENCES users (uid) ON DELETE CASCADE
+);
+
+CREATE OR REPLACE FUNCTION merge_userquota_11() RETURNS TRIGGER AS $$
+BEGIN
+    UPDATE userquota_11
+       SET current = current + NEW.current
+     WHERE uid = NEW.uid AND path = NEW.path;
+    IF found THEN
+        RETURN NULL;
+    ELSE
+        RETURN NEW;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergeuserquota_11 BEFORE INSERT ON userquota_11
+    FOR EACH ROW EXECUTE PROCEDURE merge_userquota_11();
+
+-- Adjust tables (services)
+ALTER TABLE domain_data ADD COLUMN ssid bigint NOT NULL DEFAULT 1;
+ALTER TABLE domain_data ADD CONSTRAINT fkey_domain_data_ssid_service_set
+    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
+
+ALTER TABLE users ADD COLUMN ssid bigint NULL DEFAULT NULL;
+-- save current service sets
+UPDATE users u
+   SET ssid = ss.ssid
+  FROM service_set ss
+ WHERE ss.smtp = u.smtp
+   AND ss.pop3 = u.pop3
+   AND ss.imap = u.imap
+   AND ss.managesieve = u.managesieve;
+
+ALTER TABLE users DROP COLUMN smtp;
+ALTER TABLE users DROP COLUMN pop3;
+ALTER TABLE users DROP COLUMN imap;
+ALTER TABLE users DROP COLUMN managesieve;
+ALTER TABLE users ADD CONSTRAINT fkey_users_ssid_service_set
+    FOREIGN KEY (ssid) REFERENCES service_set (ssid);
+
+-- ---
+-- Catchall
+-- ---
+-- Quota/Service/Transport inheritance
+-- ---
+ALTER TABLE users ALTER COLUMN tid DROP NOT NULL;
+ALTER TABLE users ALTER COLUMN tid SET DEFAULT NULL;
+-- The qid and ssid columns have already been defined accordingly above.
+-- The rest of the logic will take place in the functions.
+
+-- While qid and ssid are new and it's perfectly okay for existing users to
+-- get NULL values (i.e. inherit from the domain's default), tid existed in
+-- vmm 0.5.x. A sensible way forward seems thus to NULL all user records' tid
+-- fields where the tid duplicates the value stored in the domain's record.
+UPDATE users
+   SET tid = NULL
+ WHERE tid = (SELECT tid
+                FROM domain_data
+               WHERE domain_data.gid = users.gid);
+
+-- ---
+
+CREATE TABLE catchall (
+    gid         bigint NOT NULL,
+    destination varchar(320) NOT NULL,
+    CONSTRAINT  pkey_catchall PRIMARY KEY (gid, destination),
+    CONSTRAINT  fkey_catchall_gid_domain_data FOREIGN KEY (gid)
+        REFERENCES domain_data (gid)
+);
+
+-- ---
+-- Account/domain notes
+-- ---
+
+ALTER TABLE users ADD COLUMN note text NULL DEFAULT NULL;
+ALTER TABLE domain_data ADD COLUMN note text NULL DEFAULT NULL;
+
+-- ---
+-- Restore view
+-- ---
+CREATE VIEW vmm_domain_info AS
+    SELECT gid, count(uid) AS accounts,
+           (SELECT count(DISTINCT address)
+              FROM alias
+             WHERE alias.gid = domain_data.gid) AS aliases,
+           (SELECT count(gid)
+              FROM relocated
+             WHERE relocated.gid = domain_data.gid) AS relocated,
+           (SELECT count(gid)
+              FROM domain_name
+             WHERE domain_name.gid = domain_data.gid
+               AND NOT domain_name.is_primary) AS aliasdomains,
+           (SELECT count(gid)
+              FROM catchall
+             WHERE catchall.gid = domain_data.gid) AS catchall
+      FROM domain_data
+           LEFT JOIN domain_name USING (gid)
+           LEFT JOIN users USING (gid)
+     WHERE domain_name.is_primary
+  GROUP BY gid;
+
+-- ---
+-- Drop all known v0.5 types (the dirty way)
+-- ---
+DROP TYPE address_maildir CASCADE;
+DROP TYPE dovecotpassword CASCADE;
+DROP TYPE dovecotuser CASCADE;
+DROP TYPE recipient_destination CASCADE;
+DROP TYPE recipient_transport CASCADE;
+DROP TYPE recipient_uid CASCADE;
+DROP TYPE sender_login CASCADE;
+
+-- ######################## TYPEs ########################################### --
+
+-- ---
+-- Data type for function postfix_virtual_mailbox(varchar, varchar)
+-- ---
+CREATE TYPE address_maildir AS (
+    address varchar(320),
+    maildir text
+);
+-- ---
+-- Data type for function dovecotpassword(varchar, varchar)
+-- ---
+CREATE TYPE dovecotpassword AS (
+    userid      varchar(320),
+    password    varchar(270),
+    smtp        boolean,
+    pop3        boolean,
+    imap        boolean,
+    managesieve boolean
+);
+-- ---
+-- Data type for function dovecotquotauser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotquotauser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text,
+    quota_rule  text
+);
+-- ---
+-- Data type for function dovecotuser(varchar, varchar)
+-- ---
+CREATE TYPE dovecotuser AS (
+    userid      varchar(320),
+    uid         bigint,
+    gid         bigint,
+    home        text,
+    mail        text
+);
+-- ---
+-- Data type for functions: postfix_relocated_map(varchar, varchar)
+--                          postfix_virtual_alias_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_destination AS (
+    recipient   varchar(320),
+    destination text
+);
+-- ---
+-- Data type for function postfix_transport_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_transport AS (
+    recipient   varchar(320),
+    transport   text
+);
+-- ---
+-- Data type for function postfix_virtual_uid_map(varchar, varchar)
+-- ---
+CREATE TYPE recipient_uid AS (
+    recipient   varchar(320),
+    uid         bigint
+);
+-- ---
+-- Data type for function postfix_smtpd_sender_login_map(varchar, varchar)
+-- ---
+CREATE TYPE sender_login AS (
+    sender  varchar(320),
+    login   text
+);
+
+-- ######################## FUNCTIONs ####################################### --
+
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotpassword records
+-- ---
+CREATE OR REPLACE FUNCTION dovecotpassword(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
+AS $$
+    DECLARE
+        record dovecotpassword;
+        userid varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT userid, passwd, smtp, pop3, imap, managesieve
+              FROM users, service_set, domain_data
+             WHERE users.gid = (SELECT gid
+                                  FROM domain_name
+                                 WHERE domainname = the_domain)
+               AND local_part = localpart
+               AND service_set.ssid = users.ssid
+               AND users.gid = domain_data.gid
+               AND CASE WHEN
+                  users.ssid IS NOT NULL
+                  THEN
+                    service_set.ssid = users.ssid
+                  ELSE
+                    service_set.ssid = domain_data.ssid
+                  END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Nearly the same as function dovecotuser below. It returns additionally the
+-- field quota_rule.
+-- ---
+CREATE OR REPLACE FUNCTION dovecotquotauser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotquotauser
+AS $$
+    DECLARE
+        record dovecotquotauser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail, '*:bytes=' ||
+                   bytes || ':messages=' || messages AS quota_rule
+              FROM users, domain_data, mailboxformat, maillocation, quotalimit
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+               AND CASE WHEN
+                     users.qid IS NOT NULL
+                   THEN
+                     quotalimit.qid = users.qid
+                   ELSE
+                     quotalimit.qid = domain_data.qid
+                   END
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from login name [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: dovecotuser records
+-- ---
+CREATE OR REPLACE FUNCTION dovecotuser(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotuser
+AS $$
+    DECLARE
+        record dovecotuser;
+        userid varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT userid, uid, did, domaindir || '/' || uid AS home,
+                   format || ':~/' || directory AS mail
+              FROM users, domain_data, mailboxformat, maillocation
+             WHERE users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+               AND mailboxformat.fid = maillocation.fid
+               AND domain_data.gid = did
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_relocated_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        record recipient_destination;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient, destination
+              FROM relocated
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from _sender_ address (MAIL FROM) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: SASL _login_ names that own _sender_ addresses (MAIL FROM):
+--      set of sender_login records.
+-- ---
+CREATE OR REPLACE FUNCTION postfix_smtpd_sender_login_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF sender_login
+AS $$
+    DECLARE
+        rec sender_login;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        sender varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        -- Get all addresses for 'localpart' in the primary and aliased domains
+        FOR rec IN
+            SELECT sender, local_part || '@' || domainname
+              FROM domain_name, users
+             WHERE domain_name.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        IF NOT FOUND THEN
+            -- Loop over the alias addresses for localpart@the_domain
+            FOR rec IN
+                SELECT DISTINCT sender, destination
+                  FROM alias
+                 WHERE alias.gid = did
+                   AND alias.address = localpart
+                LOOP
+                    RETURN NEXT rec;
+                END LOOP;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_transport records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_transport_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_transport
+AS $$
+    DECLARE
+        record recipient_transport;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
+        transport_id bigint;
+    BEGIN
+        IF did IS NULL THEN
+            RETURN;
+        END IF;
+
+        SELECT tid INTO transport_id
+          FROM users
+         WHERE gid = did AND local_part = localpart;
+
+        IF transport_id IS NULL THEN
+            SELECT tid INTO STRICT transport_id
+              FROM domain_data
+             WHERE gid = did;
+        END IF;
+
+        FOR record IN
+            SELECT recipient, transport
+              FROM transport
+             WHERE tid = transport_id
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_destination records
+-- ---
+CREATE OR REPLACE FUNCTION _interpolate_destination(
+    IN destination varchar, localpart varchar, IN the_domain varchar)
+    RETURNS varchar
+AS $$
+    DECLARE
+        result varchar(320);
+    BEGIN
+        IF position('%' in destination) = 0 THEN
+            RETURN destination;
+        END IF;
+        result := replace(destination, '%n', localpart);
+        result := replace(result, '%d', the_domain);
+        result := replace(result, '%=', localpart || '=' || the_domain);
+        RETURN result;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+
+CREATE OR REPLACE FUNCTION postfix_virtual_alias_map(
+    IN localpart varchar, IN the_domain varchar)
+    RETURNS SETOF recipient_destination
+AS $$
+    DECLARE
+        recordc recipient_destination;
+        record recipient_destination;
+        catchall_cursor refcursor;
+        recipient varchar(320) := localpart || '@' || the_domain;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+    BEGIN
+        FOR record IN
+            SELECT recipient,
+                _interpolate_destination(destination, localpart, the_domain)
+              FROM alias
+             WHERE gid = did
+               AND address = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+
+        IF NOT FOUND THEN
+            -- There is no matching virtual_alias. If there are no catchall
+            -- records for this domain, we can just return NULL since Postfix
+            -- will then later consult mailboxes/relocated itself. But if
+            -- there is a catchall destination, then it would take precedence
+            -- over mailboxes/relocated, which is not what we want. Therefore,
+            -- we must first find out if the query is for an existing mailbox
+            -- or relocated entry and return the identity mapping if that is
+            -- the case
+            OPEN catchall_cursor FOR
+                SELECT recipient,
+                    _interpolate_destination(destination, localpart, the_domain)
+                  FROM catchall
+                 WHERE gid = did;
+            FETCH NEXT FROM catchall_cursor INTO recordc;
+
+            IF recordc IS NOT NULL THEN
+                -- Since there are catchall records for this domain
+                -- check the mailbox and relocated records and return identity
+                -- if a matching record exists.
+                FOR record IN
+                    SELECT recipient, recipient as destination
+                      FROM users
+                    WHERE gid = did
+                      AND local_part = localpart
+                    UNION SELECT recipient, recipient as destination
+                      FROM relocated
+                    WHERE gid = did
+                      AND address = localpart
+                    LOOP
+                        RETURN NEXT record;
+                    END LOOP;
+
+                IF NOT FOUND THEN
+                    -- There were no records found for mailboxes/relocated,
+                    -- so now we can actually iterate the cursor and populate
+                    -- the return set
+                    LOOP
+                        RETURN NEXT recordc;
+                        FETCH NEXT FROM catchall_cursor INTO recordc;
+                        EXIT WHEN recordc IS NULL;
+                    END LOOP;
+                END IF;
+            END IF;
+            CLOSE catchall_cursor;
+        END IF;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: address_maildir records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_mailbox_map(
+   IN localpart varchar, IN the_domain varchar) RETURNS SETOF address_maildir
+AS $$
+    DECLARE
+        rec address_maildir;
+        did bigint := (SELECT gid FROM domain_name WHERE domainname=the_domain);
+        address varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR rec IN
+            SELECT address, domaindir||'/'||users.uid||'/'||directory||'/'
+              FROM domain_data, users, maillocation
+             WHERE domain_data.gid = did
+               AND users.gid = did
+               AND users.local_part = localpart
+               AND maillocation.mid = users.mid
+            LOOP
+                RETURN NEXT rec;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
+-- ---
+-- Parameters (from recipients address (MAIL TO) [localpart@the_domain]):
+--      varchar localpart
+--      varchar the_domain
+-- Returns: recipient_uid records
+-- ---
+CREATE OR REPLACE FUNCTION postfix_virtual_uid_map(
+    IN localpart varchar, IN the_domain varchar) RETURNS SETOF recipient_uid
+AS $$
+    DECLARE
+        record recipient_uid;
+        recipient varchar(320) := localpart || '@' || the_domain;
+    BEGIN
+        FOR record IN
+            SELECT recipient, uid
+              FROM users
+             WHERE gid = (SELECT gid
+                            FROM domain_name
+                           WHERE domainname = the_domain)
+               AND local_part = localpart
+            LOOP
+                RETURN NEXT record;
+            END LOOP;
+        RETURN;
+    END;
+$$ LANGUAGE plpgsql STABLE
+RETURNS NULL ON NULL INPUT
+EXTERNAL SECURITY INVOKER;
--- a/pgsql/update_tables_0.5.x_for_dovecot-1.2.x.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
--- ---
--- with Dovecot v1.2.x the service managesieve was renamed to sieve
--- ---
-ALTER TABLE users RENAME managesieve TO sieve;
-
-DROP VIEW dovecot_password;
-CREATE OR REPLACE VIEW dovecot_password AS
-    SELECT local_part || '@' || domain_name.domainname AS "user",
-           passwd AS "password", smtp, pop3, imap, sieve
-      FROM users
-           LEFT JOIN domain_name USING (gid);
-
--- a/pgsql/update_types_and_functions_0.5.x_for_dovecot-1.2.x.pgsql	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
--- ---
--- Clean out the old stuff
--- ---
-DROP TYPE dovecotpassword CASCADE;
-
--- ---
--- Data type for function dovecotpassword(varchar, varchar)
--- ---
-CREATE TYPE dovecotpassword AS (
-    userid    varchar(320),
-    password  varchar(74),
-    smtp      boolean,
-    pop3      boolean,
-    imap      boolean,
-    sieve     boolean
-);
-
--- ---
--- Parameters (from login name [localpart@the_domain]):
---      varchar localpart
---      varchar the_domain
--- Returns: dovecotpassword records
---
--- Required access privileges for your dovecot database user:
---      GRANT SELECT ON users, domain_name TO dovecot;
---
--- For more details see http://wiki.dovecot.org/AuthDatabase/SQL
--- ---
-CREATE OR REPLACE FUNCTION dovecotpassword(
-    IN localpart varchar, IN the_domain varchar) RETURNS SETOF dovecotpassword
-AS $$
-    DECLARE
-        record dovecotpassword;
-        userid varchar(320) := localpart || '@' || the_domain;
-    BEGIN
-        FOR record IN
-            SELECT userid, passwd, smtp, pop3, imap, sieve
-              FROM users
-             WHERE gid = (SELECT gid
-                            FROM domain_name
-                           WHERE domainname = the_domain)
-               AND local_part = localpart
-            LOOP
-                RETURN NEXT record;
-            END LOOP;
-        RETURN;
-    END;
-$$ LANGUAGE plpgsql STABLE
-RETURNS NULL ON NULL INPUT
-EXTERNAL SECURITY INVOKER;
-
--- a/po/de.po	Mon Nov 07 03:22:15 2011 +0000
+++ b/po/de.po	Thu Jun 28 19:26:50 2012 +0000
@@ -1,515 +1,799 @@
 # German translations for vmm package.
-# Copyright (C) 2009 Pascal Volk
+# Copyright (C) 2009 Free Software Foundation, Inc.
+# This file is distributed under the same license as the vmm package.
 # Pascal Volk <p.volk@veb-it.de>, 2009.
+# Mario Blättermann <mario.blaettermann@gmail.com>, 2011.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vmm 0.5.2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-25 06:07+0200\n"
-"PO-Revision-Date: 2009-08-25 06:11+0200\n"
-"Last-Translator: Pascal Volk <p.volk@veb-it.de>\n"
-"Language-Team: German\n"
+"Project-Id-Version: vmm 0.6.0\n"
+"Report-Msgid-Bugs-To: user+vmm/tp@localhost.localdomain.org\n"
+"POT-Creation-Date: 2011-11-07 05:20+0100\n"
+"PO-Revision-Date: 2011-11-07 12:57+0100\n"
+"Last-Translator: Mario Blättermann <mario.blaettermann@gmail.com>\n"
+"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
 
-#: VirtualMailManager/Account.py:36 VirtualMailManager/Relocated.py:44
+#. 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
+#: VirtualMailManager/account.py:58 VirtualMailManager/alias.py:35
+#: VirtualMailManager/domain.py:120 VirtualMailManager/relocated.py:38
 #, python-format
-msgid "There is already an alias with the address “%s”."
-msgstr "Es existiert bereits ein Alias mit der Adresse „%s“."
+msgid "The domain '%s' does not exist."
+msgstr "Die Domain »%s« existiert nicht."
 
-#: VirtualMailManager/Account.py:41 VirtualMailManager/Alias.py:45
+#: VirtualMailManager/account.py:106
 #, python-format
-msgid "There is already a relocated user with the address “%s”."
-msgstr "Es gibt bereits ein relocated User mit der Adresse „%s“."
+msgid "The mailbox format '%(mbfmt)s' requires Dovecot >= v%(version)s."
+msgstr "Das Postfachformat »%(mbfmt)s« benötigt Dovecot >= v%(version)s."
 
-#: VirtualMailManager/Account.py:61 VirtualMailManager/Alias.py:61
-#: VirtualMailManager/Domain.py:163 VirtualMailManager/Domain.py:189
-#: VirtualMailManager/Domain.py:220 VirtualMailManager/Relocated.py:60
+#: VirtualMailManager/account.py:113 VirtualMailManager/account.py:305
 #, python-format
-msgid "The domain “%s” doesn't exist yet."
-msgstr "Die Domain „%s“ existiert noch nicht."
+msgid "Invalid transport '%(transport)s' for mailbox format '%(mbfmt)s'."
+msgstr "Ungültiger Transport »%(transport)s« für Postfachformat »%(mbfmt)s«."
 
-#: VirtualMailManager/Account.py:80
+#: VirtualMailManager/account.py:153 VirtualMailManager/cli/handler.py:93
+#: VirtualMailManager/handler.py:628 VirtualMailManager/handler.py:679
+#: VirtualMailManager/handler.py:705 VirtualMailManager/handler.py:716
+#: VirtualMailManager/handler.py:727 VirtualMailManager/handler.py:739
+#: VirtualMailManager/handler.py:753
 #, python-format
-msgid "Unknown service “%s”."
-msgstr "Unbekannter Service „%s“."
+msgid "The account '%s' does not exist."
+msgstr "Das Konto »%s« existiert nicht."
 
-#: VirtualMailManager/Account.py:83 VirtualMailManager/Account.py:150
-#: VirtualMailManager/Account.py:178 VirtualMailManager/Account.py:212
+#: VirtualMailManager/account.py:204 VirtualMailManager/account.py:214
+#: VirtualMailManager/cli/handler.py:77 VirtualMailManager/handler.py:596
 #, python-format
-msgid "The account “%s” doesn't exists."
-msgstr "Der Account „%s“ existiert nicht."
+msgid "The account '%s' already exists."
+msgstr "Das Konto »%s« existiert bereits."
+
+#: VirtualMailManager/account.py:207 VirtualMailManager/handler.py:701
+#, python-format
+msgid "Could not accept password: '%s'"
+msgstr "Passwort konnte nicht akzeptiert werden: »%s«"
 
-#: VirtualMailManager/Account.py:145
+#: VirtualMailManager/account.py:217
 #, python-format
-msgid "The account “%s” already exists."
-msgstr "Der Account „%s“ existiert bereits."
+msgid "No password set for account: '%s'"
+msgstr "Kein Passwort festgelegt für Konto: »%s«"
 
-#: VirtualMailManager/Account.py:186
-msgid "enabled"
-msgstr "aktiviert"
+#: VirtualMailManager/account.py:245
+#, python-format
+msgid "Unknown field: '%s'"
+msgstr "Unbekanntes Feld: »%s«"
 
-#: VirtualMailManager/Account.py:188
+#: VirtualMailManager/account.py:267 VirtualMailManager/domain.py:292
+msgid "PostgreSQL-based dictionary quota requires Dovecot >= v1.1.2."
+msgstr "PostgreSQL-basierte Datenbank-Nutzungsbeschränkung benötigt Dovecot >= v1.1.2."
+
+#. TP: A service (e.g. pop3 or imap) may be enabled/usable or
+#. disabled/unusable for a user.
+#: VirtualMailManager/account.py:332
 msgid "disabled"
 msgstr "deaktiviert"
 
-#: VirtualMailManager/Account.py:233
+#: VirtualMailManager/account.py:332
+msgid "enabled"
+msgstr "aktiviert"
+
+#: VirtualMailManager/account.py:343
 #, python-format
-msgid "There are %(count)d aliases with the destination address “%(address)s”."
-msgstr "Es gibt %(count)d Alias(e) mit der Zieladresse „%(address)s“."
+msgid "Could not fetch information for account: '%s'"
+msgstr "Informationen zum Konto konnten nicht geholt werden: »%s«"
 
-#: VirtualMailManager/Account.py:241
-msgid "uid must be an int/long."
+#: VirtualMailManager/account.py:387
+#, python-format
+msgid "There are %(count)d aliases with the destination address '%(address)s'."
+msgstr "Es gibt %(count)d Alias(e) mit der Zieladresse »%(address)s«."
+
+#: VirtualMailManager/account.py:416
+msgid "UID must be an int/long."
 msgstr "Die UID muss eine Ganzzahl sein."
 
-#: VirtualMailManager/Account.py:243
-msgid "uid must be greater than 0."
+#: VirtualMailManager/account.py:418
+msgid "UID must be greater than 0."
 msgstr "Die UID muss größer als 0 sein."
 
-#: VirtualMailManager/Account.py:251
+#: VirtualMailManager/account.py:427
 #, python-format
-msgid "There is no account with the UID “%d”."
-msgstr "Es existiert kein Account mit der UID „%d“."
+msgid "There is no account with the UID: '%d'"
+msgstr "Es existiert kein Konto mit der UID »%d«."
 
-#: VirtualMailManager/Alias.py:30 VirtualMailManager/Relocated.py:30
-msgid "Address and destination are identical."
-msgstr "Alias- und Ziel-Adresse sind identisch."
-
-#: VirtualMailManager/Alias.py:40 VirtualMailManager/Relocated.py:39
-#, python-format
-msgid "There is already an account with address “%s”."
-msgstr "Es gibt bereits einen Account mit der Adresse „%s“."
-
-#: VirtualMailManager/Alias.py:71
+#: VirtualMailManager/alias.py:60
 #, python-format
 msgid ""
-"Can't add new destination to alias “%(address)s”.\n"
-"Currently this alias expands into %(count)i recipients.\n"
-"One more destination will render this alias unusable.\n"
-"Hint: Increase Postfix' virtual_alias_expansion_limit\n"
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"Currently this alias expands into %(count)i/%(limit)i recipients.\n"
+"%(count_new)i additional destination(s) will render this alias unusable.\n"
+"Hint: Increase Postfix' virtual_alias_expansion_limit"
 msgstr ""
-"Dem Alias „%(address)s“ kann keine weitere Ziel-Adresse hinzugefügt werden.\n"
-"Derzeit verweist der Alias auf %(count)i Empfänger.\n"
-"Eine weitere Ziel-Adresse würde diesen Alias unbrauchbar machen.\n"
-"Tipp: Erhöhen Sie Postfix' virtual_alias_expansion_limit\n"
-
-#: VirtualMailManager/Alias.py:80
-msgid "No destination address for alias denoted."
-msgstr "Keine Ziel-Adresse für den Alias angegeben."
-
-#: VirtualMailManager/Alias.py:91
-#, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” already exists."
-msgstr "Der Alias „%(a)s“ mit der Ziel-Adresse „%(d)s“ existiert bereits."
-
-#: VirtualMailManager/Alias.py:106 VirtualMailManager/Alias.py:123
-#, python-format
-msgid "The alias “%s” doesn't exists."
-msgstr "Der Alias „%s“ existiert nicht."
-
-#: VirtualMailManager/Alias.py:125
-#, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
-msgstr "Der Alias „%(a)s“ mit der Ziel-Adresse „%(d)s“ existiert nicht."
+"%(count_new)i neu(e) Ziel(e) konnten nicht zum Alias »%(address)s«\n"
+"hinzugefügt werden. Gegenwärtig gilt dieser Alias für %(count)i/%(limit)i\n"
+"Empfänger. Durch Hinzufügen von %(count_new)i Ziel(en) wird dieser Alias\n"
+"unbenutzbar.\n"
+"Hinweis: Erhöhen Sie den Wert für »virtual_alias_expansion_limit« in Postfix."
 
-#: VirtualMailManager/AliasDomain.py:32
-#, python-format
-msgid "The domain “%s” is a primary domain."
-msgstr "Die Domain „%s“ ist eine primäre Domain."
-
-#: VirtualMailManager/AliasDomain.py:37
-#, python-format
-msgid "The alias domain “%s” already exists."
-msgstr "Die Alias-Domain „%s“ existiert bereits."
-
-#: VirtualMailManager/AliasDomain.py:40 VirtualMailManager/AliasDomain.py:70
-msgid "No destination domain for alias domain denoted."
-msgstr "Keine Ziel-Domain für die Alias-Domain angegeben."
-
-#: VirtualMailManager/AliasDomain.py:43 VirtualMailManager/AliasDomain.py:73
-#, python-format
-msgid "The target domain “%s” doesn't exist yet."
-msgstr "Die Ziel-Domain „%s“ existiert noch nicht."
-
-#: VirtualMailManager/AliasDomain.py:62
-#, python-format
-msgid "There is no primary domain for the alias domain “%s”."
-msgstr "Es gibt keine primäre Domain für die Alias-Domain „%s“."
-
-#: VirtualMailManager/AliasDomain.py:65 VirtualMailManager/AliasDomain.py:76
-#: VirtualMailManager/AliasDomain.py:99
-#, python-format
-msgid "The alias domain “%s” doesn't exist yet."
-msgstr "Die Alias-Domain „%s“ existiert noch nicht."
-
-#: VirtualMailManager/AliasDomain.py:79
+#: VirtualMailManager/alias.py:67
 #, python-format
 msgid ""
-"The alias domain “%(alias)s” is already assigned to the domain “%(domain)s”."
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"This alias already exceeds its expansion limit (%(count)i/%(limit)i).\n"
+"So its unusable, all messages addressed to this alias will be bounced.\n"
+"Hint: Delete some destination addresses."
 msgstr ""
-"Die Alias-Domain „%(alias)s“ ist bereits der Domain „%(domain)s“ zugeordnet."
-
-#: VirtualMailManager/Config.py:102 VirtualMailManager/Config.py:137
-#, python-format
-msgid "Using configuration file: %s\n"
-msgstr "Verwende Konfigurationsdatei: %s\n"
-
-#: VirtualMailManager/Config.py:106
-#, python-format
-msgid "missing section: %s\n"
-msgstr "Fehlender Abschnitt: %s\n"
-
-#: VirtualMailManager/Config.py:108
-#, python-format
-msgid "missing options in section %s:\n"
-msgstr "Fehlende Optionen im Abschnitt %s:\n"
-
-#: VirtualMailManager/Config.py:140
-#, python-format
-msgid "* Config section: “%s”"
-msgstr "* Konfigurations Abschnitt: „%s“"
-
-#: VirtualMailManager/Config.py:143
-#, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
-msgstr "Neuer Wert für Option %(opt)s [%(val)s]: "
-
-#: VirtualMailManager/Domain.py:39
-#, python-format
-msgid "The domain “%s” is an alias domain."
-msgstr "Die Domain „%s“ ist eine Alias-Domain."
-
-#: VirtualMailManager/Domain.py:124
-msgid "There are accounts and aliases."
-msgstr "Es sind noch Accounts und Aliase vorhanden."
-
-#: VirtualMailManager/Domain.py:127
-msgid "There are accounts."
-msgstr "Es sind noch Accounts vorhanden."
+"%(count_new)i neu(e) Ziel(e) konnten nicht zu Alias »%(address)s«\n"
+"hinzugefügt werden. Dieser Alias übersteigt bereits die Begrenzung\n"
+"auf (%(count)i/%(limit)i). Daher wird er unbenutzbar, und alle an\n"
+"diesen Alias adressierten Nachrichten werden zurückgehalten.\n"
+"Hinweis: Löschen Sie einige Zieladressen."
 
-#: VirtualMailManager/Domain.py:130
-msgid "There are aliases."
-msgstr "Es sind noch Aliase vorhanden."
-
-#: VirtualMailManager/Domain.py:145
-#, python-format
-msgid "The domain “%s” already exists."
-msgstr "Die Domain „%s“ existiert bereits."
-
-#: VirtualMailManager/EmailAddress.py:46
+#: VirtualMailManager/alias.py:142 VirtualMailManager/alias.py:154
+#: VirtualMailManager/alias.py:161 VirtualMailManager/handler.py:657
 #, python-format
-msgid "Missing '@' sign in e-mail address “%s”."
-msgstr "In der E-Mail-Adresse „%s“ fehlt das '@'-Zeichen."
-
-#: VirtualMailManager/EmailAddress.py:49
-#, python-format
-msgid "“%s” looks not like an e-mail address."
-msgstr "„%s“ sieht nicht wie eine E-Mail-Adresse aus."
-
-#: VirtualMailManager/EmailAddress.py:54
-#, python-format
-msgid "Missing domain name after “%s@”."
-msgstr "Der Domain-Name nach „%s@“ fehlt."
+msgid "The alias '%s' does not exist."
+msgstr "Der Alias »%s« existiert nicht."
 
-#: VirtualMailManager/EmailAddress.py:66
-msgid "No localpart specified."
-msgstr "Kein local-part angegeben."
-
-#: VirtualMailManager/EmailAddress.py:69
-#, python-format
-msgid "The local part “%s” is too long"
-msgstr "Der local-part „%s“ ist zu lang"
-
-#: VirtualMailManager/EmailAddress.py:76
+#: VirtualMailManager/alias.py:145
 #, python-format
-msgid "The local part “%(lpart)s” contains invalid characters: %(ichrs)s"
-msgstr "Der local-part „%(lpart)s“ enthält ungültige Zeichen: %(ichrs)s"
+msgid "The address '%(addr)s' is not a destination of the alias '%(alias)s'."
+msgstr "Die Adresse »%(addr)s« ist kein Ziel für den Alias »%(alias)s«."
 
-#: VirtualMailManager/MailLocation.py:32
-msgid "Either mid or maillocation must be specified."
-msgstr "Entweder mid oder maillocation muss angegeben werden."
+#: VirtualMailManager/aliasdomain.py:50
+#, python-format
+msgid "The domain '%s' is a primary domain."
+msgstr "Die Domain »%s« ist eine primäre Domain."
 
-#: VirtualMailManager/MailLocation.py:38
-msgid "mid must be an int/long."
-msgstr "Die MID muss eine Ganzzahl sein."
-
-#: VirtualMailManager/MailLocation.py:46
+#: VirtualMailManager/aliasdomain.py:69
 #, python-format
-msgid ""
-"Invalid folder name “%s”, it may consist only of\n"
-"1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
-msgstr ""
-"Unzulässiger Verzeichnisname „%s“, dieser darf nur aus\n"
-"1 - 20 Einzelbytezeichen (A-Z, a-z, 0-9 und _) bestehen."
-
-#: VirtualMailManager/MailLocation.py:59
-msgid "Unknown mid specified."
-msgstr "Unbekannte MID angegeben."
+msgid "The alias domain '%s' already exists."
+msgstr "Die Alias-Domain »%s« existiert bereits."
 
-#: VirtualMailManager/Relocated.py:65
-msgid "No destination address for relocated user denoted."
-msgstr "Keine Ziel-Adresse für den relocated User angegeben."
+#: VirtualMailManager/aliasdomain.py:72 VirtualMailManager/aliasdomain.py:106
+msgid "No destination domain set for the alias domain."
+msgstr "Keine Ziel-Domain für die Alias-Domain angegeben."
 
-#: VirtualMailManager/Relocated.py:75
-#, python-format
-msgid "The relocated user “%s” already exists."
-msgstr "Der relocated User „%s“ existiert bereits."
-
-#: VirtualMailManager/Relocated.py:89 VirtualMailManager/Relocated.py:102
+#: VirtualMailManager/aliasdomain.py:75 VirtualMailManager/aliasdomain.py:109
 #, python-format
-msgid "The relocated user “%s” doesn't exists."
-msgstr "Der relocated User „%s“ existiert nicht."
-
-#: VirtualMailManager/Transport.py:29
-msgid "Either tid or transport must be specified."
-msgstr "Entweder tid oder transport muss angegeben werden."
-
-#: VirtualMailManager/Transport.py:35
-msgid "tid must be an int/long."
-msgstr "Die tid muss eine Ganzzahl sein."
-
-#: VirtualMailManager/Transport.py:63
-msgid "Unknown tid specified."
-msgstr "Unbekannte tid angegeben."
-
-#: VirtualMailManager/VirtualMailManager.py:54
-msgid ""
-"You are not root.\n"
-"\tGood bye!\n"
-msgstr ""
-"Sie sind nicht root.\n"
-"\tAuf Wiedersehen.\n"
+msgid "The target domain '%s' does not exist."
+msgstr "Die Ziel-Domain »%s« existiert nicht."
 
-#: VirtualMailManager/VirtualMailManager.py:74
-msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
-msgstr "Keine „vmm.cfg“ gefunden in: /root:/usr/local/etc:/etc“"
-
-#: VirtualMailManager/VirtualMailManager.py:85
+#: VirtualMailManager/aliasdomain.py:88 VirtualMailManager/aliasdomain.py:112
+#: VirtualMailManager/aliasdomain.py:133
 #, python-format
-msgid ""
-"fix permissions (%(perms)s) for “%(file)s”\n"
-"`chmod 0600 %(file)s` would be great."
-msgstr ""
-"Bitte Zugriffsrechte (%(perms)s) für „%(file)s“ anpassen\n"
-"`chmod 0600 %(file)s` wäre großartig."
+msgid "The alias domain '%s' does not exist."
+msgstr "Die Alias-Domain »%s« existiert nicht."
 
-#: VirtualMailManager/VirtualMailManager.py:100
-#, python-format
-msgid ""
-"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
-msgstr ""
-"„%s“ ist kein Verzeichnis.\n"
-"(vmm.cfg: Abschnitt \"domdir\", Option \"base\")"
-
-#: VirtualMailManager/VirtualMailManager.py:105
+#: VirtualMailManager/aliasdomain.py:98
 #, python-format
-msgid ""
-"“%(binary)s” doesn't exists.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
-msgstr ""
-"„%(binary)s“ existiert nicht.\n"
-"(vmm.cfg: Abschnitt \"bin\", Option \"%(option)s\")"
+msgid "There is no primary domain for the alias domain '%s'."
+msgstr "Es gibt keine primäre Domain für die Alias-Domain »%s«."
 
-#: VirtualMailManager/VirtualMailManager.py:109
+#: VirtualMailManager/aliasdomain.py:115
 #, python-format
-msgid ""
-"“%(binary)s” is not executable.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
-msgstr ""
-"„%(binary)s“ ist nicht ausführbar.\n"
-"(vmm.cfg: Abschnitt \"bin\", Option \"%(option)s\")"
+msgid "The alias domain '%(alias)s' is already assigned to the domain '%(domain)s'."
+msgstr "Die Alias-Domain »%(alias)s« ist bereits der Domain »%(domain)s« zugeordnet."
 
-#: VirtualMailManager/VirtualMailManager.py:166
-msgid "The domain name is too long."
-msgstr "Der Domain-Name ist zu lang."
-
-#: VirtualMailManager/VirtualMailManager.py:169
-#, python-format
-msgid "The domain name “%s” is invalid."
-msgstr "Der Domain-Name „%s“ ist ungültig."
-
-#: VirtualMailManager/VirtualMailManager.py:209
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:78
 msgid "Enter new password: "
 msgstr "Neues Passwort eingeben: "
 
-#: VirtualMailManager/VirtualMailManager.py:210
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:80
 msgid "Retype new password: "
 msgstr "Neues Passwort wiederholen: "
 
-#: VirtualMailManager/VirtualMailManager.py:212
-msgid "Sorry, passwords do not match"
-msgstr "Entschuldigung, die Passwörter stimmen nicht überein"
+#: VirtualMailManager/cli/__init__.py:85 VirtualMailManager/cli/config.py:53
+msgid "Too many failures - try again later."
+msgstr "Zu viele Fehlschläge - versuchen Sie es später erneut."
+
+#: VirtualMailManager/cli/__init__.py:91
+msgid "Sorry, passwords do not match."
+msgstr "Entschuldigung, die Passwörter stimmen nicht überein."
+
+#: VirtualMailManager/cli/__init__.py:95
+msgid "Sorry, empty passwords are not permitted."
+msgstr "Entschuldigung, leere Passwörter sind nicht zulässig."
+
+#: VirtualMailManager/cli/config.py:32
+#, python-format
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
+msgstr "Neuer Wert für Option %(option)s [%(current_value)s]: "
+
+#: VirtualMailManager/cli/config.py:36
+#, python-format
+msgid "Using configuration file: %s\n"
+msgstr "Konfigurationsdatei wird verwendet: %s\n"
+
+#: VirtualMailManager/cli/config.py:38
+#, python-format
+msgid "* Configuration section: '%s'"
+msgstr "* Konfigurationsabschnitt: »%s«"
+
+#: VirtualMailManager/cli/config.py:50
+#, python-format
+msgid "Warning: %s"
+msgstr "Warnung: %s"
+
+#: VirtualMailManager/cli/handler.py:66
+#, python-format
+msgid "Invalid section: '%s'"
+msgstr "Ungültiger Abschnitt: »%s«"
+
+#: VirtualMailManager/cli/main.py:32 VirtualMailManager/cli/main.py:65
+#: VirtualMailManager/cli/main.py:68 VirtualMailManager/cli/subcommands.py:629
+#: VirtualMailManager/cli/subcommands.py:649
+#, python-format
+msgid "Error: %s"
+msgstr "Fehler: %s"
+
+#: VirtualMailManager/cli/main.py:41
+msgid "You must specify a subcommand at least."
+msgstr "Sie müssen mindestens einen Unterbefehl angeben."
+
+#: VirtualMailManager/cli/main.py:53
+#, python-format
+msgid "Unknown subcommand: '%s'"
+msgstr "Unbekannter Unterbefehl: »%s«"
+
+#. TP: We have to cry, because root has killed/interrupted vmm
+#. with Ctrl+C or Ctrl+D.
+#: VirtualMailManager/cli/main.py:62
+msgid "Ouch!"
+msgstr "Autsch!"
+
+#: VirtualMailManager/cli/main.py:71
+#, python-format
+msgid "Error: Unknown section: '%s'"
+msgstr "Fehler: Unbekannter Abschnitt: »%s«"
+
+#: VirtualMailManager/cli/main.py:74
+#, python-format
+msgid "Error: No option '%(option)s' in section: '%(section)s'"
+msgstr "Fehler: Keine Option »%(option)s« im Abschnitt: »%(section)s«"
 
-#: VirtualMailManager/VirtualMailManager.py:216
-msgid "Sorry, empty passwords are not permitted"
-msgstr "Entschuldigung, leere Passwörter sind nicht zulässig"
+#: VirtualMailManager/cli/main.py:77
+msgid "Warnings:"
+msgstr "Warnungen:"
+
+#: VirtualMailManager/cli/subcommands.py:78
+#, python-format
+msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
+msgstr "Plan A ist gescheitert … Plan B wird versucht: %(subcommand)s %(object)s"
+
+#: VirtualMailManager/cli/subcommands.py:92
+msgid "Missing alias address and destination."
+msgstr "Alias- und Ziel-Adresse fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:95
+#: VirtualMailManager/cli/subcommands.py:453
+msgid "Missing destination address."
+msgstr "Die Ziel-Adresse fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:102
+#: VirtualMailManager/cli/subcommands.py:112
+msgid "Missing alias address."
+msgstr "Die Alias-Adresse fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:134
+#: VirtualMailManager/cli/subcommands.py:168
+msgid "Missing alias domain name and destination domain name."
+msgstr "Domain-Namen für Alias- und Ziel-Domain fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:137
+#: VirtualMailManager/cli/subcommands.py:171
+msgid "Missing destination domain name."
+msgstr "Keine Ziel-Domain angegeben."
 
-#: VirtualMailManager/VirtualMailManager.py:265
-#: VirtualMailManager/VirtualMailManager.py:352
+#: VirtualMailManager/cli/subcommands.py:145
+#: VirtualMailManager/cli/subcommands.py:152
+msgid "Missing alias domain name."
+msgstr "Keine Alias-Domain angegeben."
+
+#: VirtualMailManager/cli/subcommands.py:179
+msgid "Missing option name."
+msgstr "Kein Optionsname angegeben."
+
+#: VirtualMailManager/cli/subcommands.py:195
+msgid "Missing option and new value."
+msgstr "Option und neuer Wert fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:197
+msgid "Missing new configuration value."
+msgstr "Neuer Konfigurationswert fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:213
+#: VirtualMailManager/cli/subcommands.py:229
+#: VirtualMailManager/cli/subcommands.py:242
+#: VirtualMailManager/cli/subcommands.py:331
+msgid "Missing domain name."
+msgstr "Kein Domain-Name angegeben."
+
+#: VirtualMailManager/cli/subcommands.py:219
 #, python-format
-msgid "No such directory: %s"
-msgstr "Verzeichnis nicht gefunden: %s"
+msgid "Creating account for postmaster@%s"
+msgstr "Konto für postmaster@%s wird angelegt"
+
+#: VirtualMailManager/cli/subcommands.py:235
+#: VirtualMailManager/cli/subcommands.py:249
+#: VirtualMailManager/cli/subcommands.py:322
+#: VirtualMailManager/cli/subcommands.py:343
+#: VirtualMailManager/cli/subcommands.py:372
+#: VirtualMailManager/cli/subcommands.py:509
+#: VirtualMailManager/cli/subcommands.py:522 VirtualMailManager/handler.py:453
+#: VirtualMailManager/handler.py:466 VirtualMailManager/handler.py:481
+#: VirtualMailManager/handler.py:510 VirtualMailManager/handler.py:674
+#, python-format
+msgid "Invalid argument: '%s'"
+msgstr "Ungültiges Argument: »%s«"
 
-#: VirtualMailManager/VirtualMailManager.py:340
-msgid "Found \"..\" in home directory path."
-msgstr "\"..\" im Pfad zum Benutzerverzeichnis entdeckt."
+#: VirtualMailManager/cli/subcommands.py:267
+#: VirtualMailManager/cli/subcommands.py:273
+msgid "Domain"
+msgstr "Domain"
+
+#: VirtualMailManager/cli/subcommands.py:275
+#: VirtualMailManager/cli/subcommands.py:284
+msgid "accounts"
+msgstr "Konten"
+
+#: VirtualMailManager/cli/subcommands.py:277
+#: VirtualMailManager/cli/subcommands.py:283
+#: VirtualMailManager/cli/subcommands.py:831
+msgid "alias domains"
+msgstr "Alias-Domains"
+
+#: VirtualMailManager/cli/subcommands.py:279
+#: VirtualMailManager/cli/subcommands.py:285
+msgid "aliases"
+msgstr "Aliase"
+
+#: VirtualMailManager/cli/subcommands.py:281
+#: VirtualMailManager/cli/subcommands.py:286
+msgid "relocated users"
+msgstr "Verschobene Benutzer"
+
+#: VirtualMailManager/cli/subcommands.py:292
+msgid "Missing domain name and storage value."
+msgstr "Domain-Name und neuer Speicher-Wert fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:295
+#: VirtualMailManager/cli/subcommands.py:582
+msgid "Missing storage value."
+msgstr "Speicher-Wert fehlt."
 
-#: VirtualMailManager/VirtualMailManager.py:348
-msgid "Owner/group mismatch in home directory detected."
-msgstr "Benutzerverzeichnis gehört dem/der falschen Benutzer/Gruppe."
+#: VirtualMailManager/cli/subcommands.py:301
+#: VirtualMailManager/cli/subcommands.py:586
+#, python-format
+msgid "Invalid storage value: '%s'"
+msgstr "Ungültiger Speicher-Wert: »%s«"
+
+#: VirtualMailManager/cli/subcommands.py:311
+#, python-format
+msgid "Neither a valid number of messages nor the keyword 'force': '%s'"
+msgstr "Weder eine gültige Nachrichtenanzahl noch das Schlüsselwort »force«: »%s«"
+
+#: VirtualMailManager/cli/subcommands.py:319
+#: VirtualMailManager/cli/subcommands.py:595
+#, python-format
+msgid "Not a valid number of messages: '%s'"
+msgstr "Ungültige Nachrichtenanzahl: »%s«"
+
+#: VirtualMailManager/cli/subcommands.py:354
+#: VirtualMailManager/cli/subcommands.py:609
+#, python-format
+msgid "Invalid service arguments: %s"
+msgstr "Ungültige Dienst-Argumente: »%s«"
+
+#: VirtualMailManager/cli/subcommands.py:363
+msgid "Missing domain name and new transport."
+msgstr "Domain-Name und neuer Transport fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:366
+msgid "Missing new transport."
+msgstr "Neuer Transport fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:380
+msgid "Missing UID."
+msgstr "Keine UID angegeben."
+
+#: VirtualMailManager/cli/subcommands.py:381
+#: VirtualMailManager/cli/subcommands.py:545
+#: VirtualMailManager/cli/subcommands.py:551
+msgid "Account"
+msgstr "Konto"
 
-#: VirtualMailManager/VirtualMailManager.py:364
-msgid "FATAL: \"..\" in domain directory path detected."
-msgstr "FATAL: \"..\" im Pfad zum Domain-Verzeichnis entdeckt."
+#: VirtualMailManager/cli/subcommands.py:396
+#, python-format
+msgid "Unknown help topic: '%s'"
+msgstr "Unbekanntes Hilfethema: »%s«"
+
+#: VirtualMailManager/cli/subcommands.py:409
+msgid "List of available subcommands:"
+msgstr "Liste der verfügbaren Unterbefehle:"
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable encoding suffixes:"
+msgstr "Verwendbare Encoding-Suffixe:"
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable password schemes:"
+msgstr "Verfügbare Passwort-Schemata:"
+
+#: VirtualMailManager/cli/subcommands.py:451
+msgid "Missing relocated address and destination."
+msgstr "Die Adresse des verschobenen Benutzers und Ziel-Adresse fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:460
+#: VirtualMailManager/cli/subcommands.py:467
+msgid "Missing relocated address."
+msgstr "Die Adresse des verschobenen Benutzers fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:490
+#: VirtualMailManager/cli/subcommands.py:503
+#: VirtualMailManager/cli/subcommands.py:516
+#: VirtualMailManager/cli/subcommands.py:568
+#: VirtualMailManager/cli/subcommands.py:603
+msgid "Missing e-mail address."
+msgstr "E-Mail-Adresse fehlt."
 
-#: VirtualMailManager/VirtualMailManager.py:370
-msgid "FATAL: group mismatch in domain directory detected"
-msgstr "FATAL: Domain-Verzeichnis gehört der falschen Gruppe"
+#: VirtualMailManager/cli/subcommands.py:497
+#, python-format
+msgid "Generated password: %s"
+msgstr "Erzeugtes Passwort: %s"
+
+#: VirtualMailManager/cli/subcommands.py:552
+msgid "alias addresses"
+msgstr "Alias-Adressen"
+
+#: VirtualMailManager/cli/subcommands.py:558
+msgid "Missing e-mail address and user's name."
+msgstr "E-Mail-Adresse und der Name des Benutzers fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:561
+msgid "Missing user's name."
+msgstr "Name des Benutzers fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:579
+msgid "Missing e-mail address and storage value."
+msgstr "E-Mail-Adresse und Speicher-Wert fehlen."
 
-#: VirtualMailManager/VirtualMailManager.py:457
+#: VirtualMailManager/cli/subcommands.py:617
+msgid "Missing e-mail address and transport."
+msgstr "E-Mail-Adresse und Transport fehlen."
+
+#: VirtualMailManager/cli/subcommands.py:620
+msgid "Missing transport."
+msgstr "Transport fehlt."
+
+#: VirtualMailManager/cli/subcommands.py:630
+msgid "usage: "
+msgstr "Aufruf:"
+
+#. 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]
+#: VirtualMailManager/cli/subcommands.py:640
 #, python-format
 msgid ""
-"Configurtion error: \"%s\"\n"
-"(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
+"usage: %s subcommand arguments\n"
+"  short long\n"
+"  subcommand                arguments\n"
 msgstr ""
-"Konfigurations Fehler: \"%s\"\n"
-"(im Abschnitt \"connfig\", Option \"done\") Siehe auch: vmm.cfg(5)\n"
+"Aufruf: %s Unterbefehl Argumente\n"
+"  kurz lang\n"
+"  Unterbefehl                Argumente\n"
+
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "from"
+msgstr "vom"
+
+#. 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
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "version"
+msgstr "Version"
+
+#: VirtualMailManager/cli/subcommands.py:662
+msgid "on"
+msgstr "auf"
+
+#: VirtualMailManager/cli/subcommands.py:664
+msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
+msgstr "ist freie Software und wird OHNE JEGLICHE GARANTIE bereitgestellt."
+
+#: VirtualMailManager/cli/subcommands.py:672
+msgid "uid"
+msgstr "uid"
+
+#: VirtualMailManager/cli/subcommands.py:673
+msgid "get the address of the user with the given UID"
+msgstr "Die Adresse des Benutzers mit der angegebenen UID ermitteln"
+
+#: VirtualMailManager/cli/subcommands.py:674
+#: VirtualMailManager/cli/subcommands.py:684
+msgid "address [password]"
+msgstr "address [Passwort]"
+
+#: VirtualMailManager/cli/subcommands.py:675
+msgid "create a new e-mail user with the given address"
+msgstr "Einen neuen E-Mail-Benutzer mit der angegebenen Adresse anlegen"
+
+#: VirtualMailManager/cli/subcommands.py:677
+#: VirtualMailManager/cli/subcommands.py:704
+#: VirtualMailManager/cli/subcommands.py:744
+#: VirtualMailManager/cli/subcommands.py:746
+msgid "address"
+msgstr "Adresse"
+
+#: VirtualMailManager/cli/subcommands.py:678
+msgid "delete the specified user"
+msgstr "Den angegebenen Benutzer löschen"
+
+#: VirtualMailManager/cli/subcommands.py:679
+msgid "address [details]"
+msgstr "address [Details]"
 
-#: VirtualMailManager/VirtualMailManager.py:477
-#, python-format
-msgid "Invalid section: “%s”"
-msgstr "Ungültiger Abschnitt: „%s“"
+#: VirtualMailManager/cli/subcommands.py:680
+msgid "display information about the given address"
+msgstr "Informationen über die angegebene Adresse anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:681
+msgid "address name"
+msgstr "address Name"
+
+#: VirtualMailManager/cli/subcommands.py:682
+msgid "set or update the real name for an address"
+msgstr "Den echten Namen für eine Adresse festlegen oder aktualisieren"
+
+#: VirtualMailManager/cli/subcommands.py:685
+msgid "update the password for the given address"
+msgstr "Das Passwort für die angegebene Adresse aktualisieren"
+
+#: VirtualMailManager/cli/subcommands.py:687
+msgid "address storage [messages]"
+msgstr "address storage [Nachrichten]"
+
+#: VirtualMailManager/cli/subcommands.py:688
+msgid "update the quota limit for the given address"
+msgstr "Die Nutzungsbeschränkung für die angegebene Adresse aktualisieren"
+
+#: VirtualMailManager/cli/subcommands.py:690
+msgid "address [service ...]"
+msgstr "address [Dienst …]"
+
+#: VirtualMailManager/cli/subcommands.py:691
+msgid "enables the specified services and disables all not specified services"
+msgstr "Die angegebenen Dienste aktivieren und alle nicht angegebenen Dienste deaktivieren"
 
-#: VirtualMailManager/VirtualMailManager.py:487
-#: VirtualMailManager/VirtualMailManager.py:497
-#: VirtualMailManager/VirtualMailManager.py:516
-#: VirtualMailManager/VirtualMailManager.py:624
-#: VirtualMailManager/VirtualMailManager.py:655
-#, python-format
-msgid "Invalid argument: “%s”"
-msgstr "Ungültiges Argument: „%s“"
+#: VirtualMailManager/cli/subcommands.py:694
+msgid "address transport"
+msgstr "address Transport"
+
+#: VirtualMailManager/cli/subcommands.py:695
+msgid "update the transport of the given address"
+msgstr "Den Transport für die angegebene Adresse aktualisieren"
+
+#: VirtualMailManager/cli/subcommands.py:697
+msgid "address destination ..."
+msgstr "address Ziel …"
+
+#: VirtualMailManager/cli/subcommands.py:698
+msgid "create a new alias e-mail address with one or more destinations"
+msgstr "Eine neue Alias-E-Mail-Adresse mit einem oder mehreren Zielen anlegen"
+
+#: VirtualMailManager/cli/subcommands.py:701
+msgid "address [destination]"
+msgstr "address [Ziel]"
+
+#: VirtualMailManager/cli/subcommands.py:702
+msgid "delete the specified alias e-mail address or one of its destinations"
+msgstr "Die angegebene Alias-E-Mail-Adresse oder eines ihrer Ziele löschen"
+
+#: VirtualMailManager/cli/subcommands.py:705
+msgid "show the destination(s) of the specified alias"
+msgstr "Das/die Ziel(e) des angegebenen Alias anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:708
+#: VirtualMailManager/cli/subcommands.py:717
+msgid "fqdn destination"
+msgstr "fqdn Ziel"
 
-#: VirtualMailManager/VirtualMailManager.py:520
-msgid ""
-"The keyword “detailed” is deprecated and will be removed in a future "
-"release.\n"
-"   Please use the keyword “full” to get full details."
-msgstr ""
-"Das Schlüsselwort „detailed“ ist veraltet und wird in einer zukünftigen\n"
-"   Version entfernt werden.\n"
-"   Verwenden Sie bitte das Schlüsselwort „full“, um alle Details zu erhalten."
+#: VirtualMailManager/cli/subcommands.py:709
+msgid "create a new alias for an existing domain"
+msgstr "Einen neuen Alias für eine existierende Domain anlegen"
+
+#: VirtualMailManager/cli/subcommands.py:711
+#: VirtualMailManager/cli/subcommands.py:714
+#: VirtualMailManager/cli/subcommands.py:723
+msgid "fqdn"
+msgstr "fqdn"
+
+#: VirtualMailManager/cli/subcommands.py:712
+msgid "delete the specified alias domain"
+msgstr "Die angegebene Domain löschen"
+
+#: VirtualMailManager/cli/subcommands.py:715
+msgid "show the destination of the given alias domain"
+msgstr "Das Ziel der angegebenen Alias-Domain anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:718
+msgid "assign the given alias domain to an other domain"
+msgstr "Die angegebene Alias-Domain einer anderen Domain zuordnen"
+
+#: VirtualMailManager/cli/subcommands.py:720
+msgid "fqdn [transport]"
+msgstr "fqdn [Transport]"
+
+#: VirtualMailManager/cli/subcommands.py:721
+msgid "create a new domain"
+msgstr "Eine neue Domain anlegen"
+
+#: VirtualMailManager/cli/subcommands.py:724
+msgid "delete the given domain and all its alias domains"
+msgstr "Die angegebene Domain und alle ihre Alias-Domains löschen"
+
+#: VirtualMailManager/cli/subcommands.py:725
+msgid "fqdn [details]"
+msgstr "fqdn [Details]"
+
+#: VirtualMailManager/cli/subcommands.py:726
+msgid "display information about the given domain"
+msgstr "Informationen über die angegebene Domain anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:728
+msgid "fqdn storage [messages]"
+msgstr "fqdn storage [Nachrichten]"
+
+#: VirtualMailManager/cli/subcommands.py:729
+msgid "update the quota limit of the specified domain"
+msgstr "Nutzungsbeschränkung der angegebenen Domain aktualisieren"
+
+#: VirtualMailManager/cli/subcommands.py:731
+msgid "fqdn [service ...]"
+msgstr "fqdn [Dienst …]"
+
+#: VirtualMailManager/cli/subcommands.py:732
+msgid "enables the specified services and disables all not specified services of the given domain"
+msgstr "Aktiviert die angegebenen Dienste und deaktiviert alle nicht angegebenen Dienste der angegebenen Domain"
+
+#: VirtualMailManager/cli/subcommands.py:735
+msgid "fqdn transport"
+msgstr "fqdn Transport"
+
+#: VirtualMailManager/cli/subcommands.py:736
+msgid "update the transport of the specified domain"
+msgstr "Den Transport der angegebenen Domain aktualisieren"
 
-#: VirtualMailManager/VirtualMailManager.py:593
-#, python-format
-msgid "The pattern “%s” contains invalid characters."
-msgstr "Das Muster „%s“ enthält ungültige Zeichen."
+#: VirtualMailManager/cli/subcommands.py:737
+msgid "[pattern]"
+msgstr "[Muster]"
+
+#: VirtualMailManager/cli/subcommands.py:738
+msgid "list all domains / search domains by pattern"
+msgstr "Alle Domains/Suchdomains nach Muster auflisten"
+
+#: VirtualMailManager/cli/subcommands.py:741
+msgid "address newaddress"
+msgstr "address Neue_Adresse"
+
+#: VirtualMailManager/cli/subcommands.py:742
+msgid "create a new record for a relocated user"
+msgstr "Einen neuen Datensatz für einen verschobenen Benutzer anlegen"
+
+#: VirtualMailManager/cli/subcommands.py:745
+msgid "delete the record of the relocated user"
+msgstr "Den Datensatz eines verschobenen Benutzers löschen"
+
+#: VirtualMailManager/cli/subcommands.py:747
+msgid "print information about a relocated user"
+msgstr "Informationen über einen verschobenen Benutzer anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:749
+msgid "option"
+msgstr "Option"
+
+#: VirtualMailManager/cli/subcommands.py:750
+msgid "show the actual value of the configuration option"
+msgstr "Den tatsächlichen Wert der Konfigurationsoption anzeigen"
 
-#: VirtualMailManager/VirtualMailManager.py:619
-#, python-format
-msgid "The destination account/alias “%s” doesn't exists yet."
-msgstr "Der Ziel-Account/-Alias „%s“ existiert noch nicht."
+#: VirtualMailManager/cli/subcommands.py:751
+msgid "option value"
+msgstr "Optionswert"
+
+#: VirtualMailManager/cli/subcommands.py:752
+msgid "set a new value for the configuration option"
+msgstr "Einen neuen Wert für die Konfigurationsoption festlegen"
+
+#: VirtualMailManager/cli/subcommands.py:753
+msgid "[section]"
+msgstr "[Abschnitt]"
+
+#: VirtualMailManager/cli/subcommands.py:754
+msgid "start interactive configuration modus"
+msgstr "Den interaktiven Konfigurationsmodus starten"
 
-#: VirtualMailManager/VirtualMailManager.py:636
+#: VirtualMailManager/cli/subcommands.py:756
+msgid "lists all usable password schemes and password encoding suffixes"
+msgstr "Alle verwendbaren Passwort-Schemata und Encodierungs-Suffixe für Passwörter auflisten"
+
+#: VirtualMailManager/cli/subcommands.py:758
+msgid "[subcommand]"
+msgstr "[Unterbefehl]"
+
+#: VirtualMailManager/cli/subcommands.py:759
+msgid "show a help overview or help for the given subcommand"
+msgstr "Eine Hilfe-Übersicht oder Hilfe für einen angegebenen Unterbefehl anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:761
+msgid "show version and copyright information"
+msgstr "Version und urheberrechtliche Informationen anzeigen"
+
+#: VirtualMailManager/cli/subcommands.py:809
 #, python-format
-msgid ""
-"The account has been successfully deleted from the database.\n"
-"    But an error occurred while deleting the following directory:\n"
-"    “%(directory)s”\n"
-"    Reason: %(raeson)s"
-msgstr ""
-"Der Account wurde erfolgreich aus der Datenbank gelöscht.\n"
-"    Aber es trat ein Fehler auf beim Löschen des folgenden Verzeichnisses:\n"
-"    „%(directory)s“\n"
-"    Grund: %(raeson)s"
-
-#: VirtualMailManager/VirtualMailManager.py:676
-msgid "Account doesn't exists"
-msgstr "Der Account existiert nicht"
+msgid "[%(percent)s%%] %(used)s/%(limit)s"
+msgstr "[%(percent)s%%] %(used)s/%(limit)s"
 
-#: VirtualMailManager/VirtualMailManager.py:692
-#: VirtualMailManager/VirtualMailManager.py:702
-msgid ""
-"The service name “managesieve” is deprecated and will be removed\n"
-"   in a future release.\n"
-"   Please use the service name “sieve” instead."
-msgstr ""
-"Der Servicename „managesieve“ ist veraltet und wird in einer zukünftigen\n"
-"   Version entfernt werden.\n"
-"   Verwenden Sie stattdessen bitte den Servicename „sieve“."
-
-#: VirtualMailManager/ext/Postconf.py:44
-#, python-format
-msgid ""
-"The value “%s” looks not like a valid postfix configuration parameter name."
-msgstr ""
-"„%s“ sieht nicht wie ein gültiger Postfix Konfigurationsparametername aus."
-
-#: vmm:34
-#, python-format
-msgid ""
-"Usage: %s SUBCOMMAND OBJECT ARGS*\n"
-"  short long\n"
-"  subcommand               object             args (* = optional)\n"
-msgstr ""
-"Verwendung: %s UNTERBEFEHL OBJEKT ARGS*\n"
-"  kurz  lang\n"
-"  Unterbefehl              Objekt             args (* = optional)\n"
-
-#: vmm:73 vmm:84 vmm:494
-msgid "Error"
-msgstr "Fehler"
-
-#: vmm:111
+#. TP: used in e.g. 'Domain information' or 'Account information'
+#: VirtualMailManager/cli/subcommands.py:815
 msgid "information"
 msgstr "Informationen"
 
-#: vmm:121
-msgid "Available"
-msgstr "Verfügbare"
+#. TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
+#: VirtualMailManager/cli/subcommands.py:828
+msgid "Existing"
+msgstr "Vorhandene"
 
-#: vmm:124 vmm:223 vmm:229
-msgid "alias domains"
-msgstr "Alias-Domains"
-
-#: vmm:134 vmm:145 vmm:169
+#: VirtualMailManager/cli/subcommands.py:841
+#: VirtualMailManager/cli/subcommands.py:883
 msgid "\tNone"
 msgstr "\tKeine"
 
-#: vmm:138
+#: VirtualMailManager/cli/subcommands.py:846
 msgid "Alias information"
-msgstr "Alias Informationen"
+msgstr "Alias-Informationen"
 
-#: vmm:140
+#: VirtualMailManager/cli/subcommands.py:848
 #, python-format
 msgid "\tMail for %s will be redirected to:"
 msgstr "\tE-Mails für %s werden weitergeleitet an:"
 
-#: vmm:149
+#: VirtualMailManager/cli/subcommands.py:855
 msgid "Relocated information"
-msgstr "Relocated Informationen"
+msgstr "Verschiebe-Informationen"
 
-#: vmm:151
+#: VirtualMailManager/cli/subcommands.py:857
 #, python-format
-msgid "\tUser “%(addr)s” has moved to “%(dest)s”"
-msgstr "\tDer Benutzer „%(addr)s“ ist erreichbar unter „%(dest)s“"
+msgid "\tUser '%(addr)s' has moved to '%(dest)s'"
+msgstr "\tDer Benutzer »%(addr)s« wurde nach »%(dest)s« verschoben"
 
-#: vmm:164
-msgid "Available domains"
-msgstr "Verfügbare Domains"
-
-#: vmm:166
+#: VirtualMailManager/cli/subcommands.py:872
 msgid "Matching domains"
 msgstr "Übereinstimmende Domains"
 
-#: vmm:180
+#: VirtualMailManager/cli/subcommands.py:874
+msgid "Existing domains"
+msgstr "Existierende Domains"
+
+#: VirtualMailManager/cli/subcommands.py:889
 msgid "Alias domain information"
 msgstr "Alias-Domain Informationen"
 
-#: vmm:186
+#: VirtualMailManager/cli/subcommands.py:894
 #, python-format
 msgid ""
 "\tThe alias domain %(alias)s belongs to:\n"
@@ -518,114 +802,346 @@
 "\tDie Alias-Domain %(alias)s gehört zu:\n"
 "\t    * %(domain)s"
 
-#: vmm:197 vmm:205 vmm:213
-msgid "Missing domain name."
-msgstr "Kein Domain-Name angegeben."
+#: VirtualMailManager/common.py:63
+#, python-format
+msgid "No such file: '%s'"
+msgstr "Datei nicht gefunden: »%s«"
+
+#: VirtualMailManager/common.py:66
+#, python-format
+msgid "File is not executable: '%s'"
+msgstr "Datei ist nicht ausführbar: »%s«"
+
+#: VirtualMailManager/common.py:82
+msgid "GiB"
+msgstr "GiB"
+
+#: VirtualMailManager/common.py:82
+msgid "TiB"
+msgstr "TiB"
 
-#: vmm:215 vmm:219
-msgid "Domain"
-msgstr "Domain"
+#: VirtualMailManager/common.py:83
+msgid "KiB"
+msgstr "KiB"
+
+#: VirtualMailManager/common.py:83
+msgid "MiB"
+msgstr "MiB"
 
-#: vmm:221 vmm:230
-msgid "accounts"
-msgstr "Accounts"
+#. TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
+#: VirtualMailManager/common.py:87
+#, python-format
+msgid "%(size)s %(prefix)s"
+msgstr "%(size)s %(prefix)s"
+
+#: VirtualMailManager/config.py:89
+#, python-format
+msgid "Not a boolean: '%s'"
+msgstr "Kein boolescher Wert: »%s«"
 
-#: vmm:225 vmm:231
-msgid "aliases"
-msgstr "Aliase"
+#: VirtualMailManager/config.py:127
+#, python-format
+msgid "Bad format: '%s' - expected: section.option"
+msgstr "Falsches Format: »%s« - erwartet: Abschnitt.Option"
 
-#: vmm:227 vmm:232
-msgid "relocated users"
-msgstr "Relocated Users"
+#: VirtualMailManager/config.py:380
+#, python-format
+msgid "* Section: %s\n"
+msgstr "* Abschnitt: %s\n"
+
+#: VirtualMailManager/config.py:390 VirtualMailManager/config.py:398
+#, python-format
+msgid "Check of configuration file %s failed.\n"
+msgstr "Überprüfen der Konfigurationsdatei %s ist fehlgeschlagen.\n"
+
+#: VirtualMailManager/config.py:392
+msgid "Missing options, which have no default value.\n"
+msgstr "Optionen fehlen, für die kein Vorgabewert besteht.\n"
 
-#: vmm:236
-msgid "Missing domain name and new transport."
-msgstr "Domain-Name und neuer Transport fehlen."
+#: VirtualMailManager/config.py:400 VirtualMailManager/config.py:402
+msgid "Invalid configuration values.\n"
+msgstr "Ungültige Konfigurationswerte.\n"
+
+#: VirtualMailManager/config.py:441 VirtualMailManager/config.py:525
+#, python-format
+msgid "Not a valid Dovecot version: '%s'"
+msgstr "Keine gültige Dovecot-Version: »%s«"
 
-#: vmm:238
-msgid "Missing new transport."
-msgstr "Neuer Transport fehlt."
+#: VirtualMailManager/config.py:447 VirtualMailManager/config.py:482
+#, python-format
+msgid "Unsupported database module: '%s'"
+msgstr "Nicht unterstütztes Datenbankmodul: »%s«"
+
+#: VirtualMailManager/config.py:452 VirtualMailManager/config.py:490
+#, python-format
+msgid "Unknown pgsql SSL mode: '%s'"
+msgstr "Unbekannter SSL-Modus für PostgreSQL: »%s«"
+
+#: VirtualMailManager/config.py:459 VirtualMailManager/config.py:503
+#: VirtualMailManager/maillocation.py:70
+#, python-format
+msgid "Unsupported mailbox format: '%s'"
+msgstr "Nicht unterstütztes Postfachformat: »%s«"
 
-#: vmm:247 vmm:262
-msgid "Missing alias domain name and target domain name."
-msgstr "Domain-Namen für Alias- und Ziel-Domain fehlen."
+#: VirtualMailManager/config.py:475 VirtualMailManager/handler.py:283
+#: VirtualMailManager/handler.py:357 VirtualMailManager/handler.py:362
+#: VirtualMailManager/handler.py:390
+#, python-format
+msgid "No such directory: %s"
+msgstr "Ordner nicht gefunden: %s"
+
+#: VirtualMailManager/config.py:514
+#, python-format
+msgid "Not a valid size value: '%s'"
+msgstr "Ungültiger Größenwert: »%s«"
+
+#: VirtualMailManager/domain.py:78
+#, python-format
+msgid "The domain '%s' is an alias domain."
+msgstr "Die Domain »%s« ist eine Alias-Domain."
 
-#: vmm:249 vmm:264
-msgid "Missing target domain name."
-msgstr "Keine Ziel-Domain angegeben."
+#: VirtualMailManager/domain.py:108
+#, python-format
+msgid "There are %(account_count)u accounts, %(alias_count)u aliases and %(relocated_count)u relocated users."
+msgstr "Es gibt %(account_count)u Konten, %(alias_count)u Aliase und %(relocated_count)u verschobene Benutzer."
+
+#: VirtualMailManager/domain.py:123
+#, python-format
+msgid "The domain '%s' already exists."
+msgstr "Die Domain »%s« existiert bereits."
 
-#: vmm:255 vmm:270
-msgid "Missing alias domain name."
-msgstr "Keine Alias-Domain angegeben."
+#: VirtualMailManager/domain.py:437
+msgid "The domain name is too long"
+msgstr "Der Domain-Name ist zu lang."
+
+#: VirtualMailManager/domain.py:439
+#, python-format
+msgid "The domain name '%s' is invalid"
+msgstr "Der Domain-Name »%s« ist ungültig"
+
+#: VirtualMailManager/emailaddress.py:73
+#, python-format
+msgid "Missing the '@' sign in address: '%s'"
+msgstr "In der Adresse fehlt das @-Zeichen: »%s«"
 
-#: vmm:276 vmm:285 vmm:293 vmm:323 vmm:331 vmm:339
-msgid "Missing e-mail address."
-msgstr "E-Mail-Adresse fehlt."
+#: VirtualMailManager/emailaddress.py:76
+#, python-format
+msgid "Too many '@' signs in address: '%s'"
+msgstr "Zu viele @-Zeichen in Adresse: »%s«"
+
+#: VirtualMailManager/emailaddress.py:79
+#, python-format
+msgid "Missing local-part in address: '%s'"
+msgstr "Fehlender local-part in Adresse: »%s«"
 
-#: vmm:301
-msgid "alias addresses"
-msgstr "Alias-Adressen"
+#: VirtualMailManager/emailaddress.py:82
+#, python-format
+msgid "Missing domain name in address: '%s'"
+msgstr "Domain-Name in Adresse fehlt: »%s«"
+
+#: VirtualMailManager/emailaddress.py:145
+#, python-format
+msgid "The local-part '%s' is too long."
+msgstr "Der local-part »%s« ist zu lang."
+
+#: VirtualMailManager/emailaddress.py:150
+#, python-format
+msgid "The local-part '%(l_part)s' contains invalid characters: %(i_chars)s"
+msgstr "Der local-part »%(l_part)s« enthält ungültige Zeichen: %(i_chars)s"
 
-#: vmm:307
-msgid "Missing e-mail address and users name."
-msgstr "E-Mail-Adresse und der Name des Benutzers fehlen."
+#: VirtualMailManager/ext/postconf.py:84
+#, python-format
+msgid "The value '%s' does not look like a valid postfix configuration parameter name."
+msgstr "»%s« sieht nicht wie ein gültiger Konfigurationsparametername für Postfix aus."
+
+#: VirtualMailManager/handler.py:56
+msgid "an account"
+msgstr "ein Konto"
 
-#: vmm:309
-msgid "Missing users name."
-msgstr "Name des Benutzers fehlt."
+#: VirtualMailManager/handler.py:57
+msgid "an alias"
+msgstr "ein Alias"
+
+#: VirtualMailManager/handler.py:58
+msgid "a relocated user"
+msgstr "ein verschobener Benutzer"
+
+#: VirtualMailManager/handler.py:84
+msgid ""
+"You are not root.\n"
+"\tGood bye!\n"
+msgstr ""
+"Sie sind kein Administrator.\n"
+"\tAuf Wiedersehen.\n"
 
-#: vmm:315
-msgid "Missing e-mail address and transport."
-msgstr "E-Mail-Adresse und Transport fehlen."
+#: VirtualMailManager/handler.py:104
+#, python-format
+msgid "Could not find '%(cfg_file)s' in: '%(cfg_path)s'"
+msgstr "»%(cfg_file)s« konnte nicht in »%(cfg_path)s« gefunden werden"
+
+#: VirtualMailManager/handler.py:115
+#, python-format
+msgid ""
+"wrong permissions for '%(file)s': %(perms)s\n"
+"`chmod 0600 %(file)s` would be great."
+msgstr ""
+"Bitte Zugriffsrechte (%(perms)s) für »%(file)s« anpassen\n"
+"»chmod 0600 %(file)s« wäre großartig."
 
-#: vmm:317
-msgid "Missing transport."
-msgstr "Transport fehlt."
+#: VirtualMailManager/handler.py:135
+#, python-format
+msgid ""
+"'%(path)s' is not a directory.\n"
+"(%(cfg_file)s: section 'misc', option 'base_directory')"
+msgstr ""
+"»%(path)s« ist kein Ordner.\n"
+"(%(cfg_file)s: Abschnitt »misc«, Option »base_directory«)"
 
-#: vmm:348
-msgid "Missing alias address and destination."
-msgstr "Alias- und Ziel-Adresse fehlen."
+#: VirtualMailManager/handler.py:144
+#, python-format
+msgid ""
+"\n"
+"(%(cfg_file)s: section 'bin', option '%(option)s')"
+msgstr ""
+"\n"
+"(%(cfg_file)s: Abschnitt »bin«, Option »%(option)s«)"
+
+#: VirtualMailManager/handler.py:158 VirtualMailManager/handler.py:165
+#, python-format
+msgid "Unable to import database module '%s'."
+msgstr "Datenbankmodul »%s« konnte nicht importiert werden."
 
-#: vmm:350 vmm:373
-msgid "Missing destination address."
-msgstr "Die Ziel-Adresse fehlt."
+#. TP: %(a_type)s will be one of: 'an account', 'an alias' or
+#. 'a relocated user'
+#: VirtualMailManager/handler.py:244
+#, python-format
+msgid "There is already %(a_type)s with the address '%(address)s'."
+msgstr "Es existiert bereits %(a_type)s mit der Adresse »%(address)s«."
 
-#: vmm:356 vmm:362
-msgid "Missing alias address"
-msgstr "Die Alias-Adresse fehlt."
+#: VirtualMailManager/handler.py:297
+#, python-format
+msgid "'%s' is not a directory."
+msgstr "»%s« ist kein Ordner."
+
+#: VirtualMailManager/handler.py:300
+#, python-format
+msgid "The file/directory '%s' already exists."
+msgstr "Die Datei oder der Ordner »%s« existiert bereits."
+
+#: VirtualMailManager/handler.py:329
+msgid "Skipped mailbox folders:"
+msgstr "Übersprungene Postfach-Ordner:"
 
-#: vmm:371
-msgid "Missing relocated address and destination."
-msgstr "Die Adresse des relocated Users und Ziel-Adresse fehlen."
+#: VirtualMailManager/handler.py:349
+#, python-format
+msgid "UID '%(uid)u' and/or GID '%(gid)u' are less than %(min_uid)u/%(min_gid)u."
+msgstr "UID »%(uid)u« und/oder GID »%(gid)u« sind kleiner als %(min_uid)u/%(min_gid)u."
+
+#: VirtualMailManager/handler.py:354 VirtualMailManager/handler.py:387
+#, python-format
+msgid "Found \"..\" in domain directory path: %s"
+msgstr "»..« im Pfad zum Benutzerordner entdeckt: %s"
 
-#: vmm:379 vmm:387
-msgid "Missing relocated address"
-msgstr "Die Adresse des relocated Users fehlt."
+#: VirtualMailManager/handler.py:367
+msgid "Detected owner/group mismatch in home directory."
+msgstr "Benutzerordner gehört dem/der falschen Benutzer/Gruppe."
+
+#: VirtualMailManager/handler.py:383
+#, python-format
+msgid "GID '%(gid)u' is less than '%(min_gid)u'."
+msgstr "GID »%(gid)u« ist kleiner als »%(min_gid)u«."
+
+#: VirtualMailManager/handler.py:394
+#, python-format
+msgid "Detected group mismatch in domain directory: %s"
+msgstr "Domain-Ordner gehört der falschen Gruppe: %s"
 
-#: vmm:393
-msgid "Missing userid"
-msgstr "Keine UID angegeben."
+#: VirtualMailManager/handler.py:470 VirtualMailManager/handler.py:748
+#, python-format
+msgid "Unknown service: '%s'"
+msgstr "Unbekannter Dienst: »%s«."
+
+#: VirtualMailManager/handler.py:587
+#, python-format
+msgid "The pattern '%s' contains invalid characters."
+msgstr "Das Muster »%s« enthält ungültige Zeichen."
+
+#: VirtualMailManager/handler.py:614
+msgid "Ignored destination addresses:"
+msgstr "Ziel-Adressen werden ignoriert:"
+
+#: VirtualMailManager/handler.py:619 VirtualMailManager/handler.py:769
+#, python-format
+msgid "The destination account/alias '%s' does not exist."
+msgstr "Das/der Ziel-konto/-Alias »%s« existiert nicht."
 
-#: vmm:406
-msgid "Warnings:"
-msgstr "Warnungen:"
+#: VirtualMailManager/handler.py:641
+#, python-format
+msgid ""
+"The account has been successfully deleted from the database.\n"
+"    But an error occurred while deleting the following directory:\n"
+"    '%(directory)s'\n"
+"    Reason: %(reason)s"
+msgstr ""
+"Das Konto wurde erfolgreich aus der Datenbank gelöscht.\n"
+"    Aber es trat ein Fehler auf beim Löschen des folgenden Ordners:\n"
+"    „%(directory)s“\n"
+"    Grund: %(reason)s"
 
-#: vmm:412
-msgid "from"
-msgstr "vom"
+#: VirtualMailManager/handler.py:712
+#, python-format
+msgid "Could not accept name: '%s'"
+msgstr "Name kann nicht akzeptiert werden: »%s«"
+
+#: VirtualMailManager/handler.py:735
+#, python-format
+msgid "Could not accept transport: '%s'"
+msgstr "Transport kann nicht akzeptiert werden: »%s«"
 
-#: vmm:412
-msgid "version"
-msgstr "Version"
+#: VirtualMailManager/handler.py:779 VirtualMailManager/relocated.py:98
+#: VirtualMailManager/relocated.py:105
+#, python-format
+msgid "The relocated user '%s' does not exist."
+msgstr "Der verschobene Benutzer »%s« existiert nicht."
 
-#: vmm:414
-msgid "on"
-msgstr "auf"
+#: VirtualMailManager/mailbox.py:260
+#, python-format
+msgid "Failed to create mailboxes: %r\n"
+msgstr "Postfächer konnten nicht angelegt werden: %r\n"
+
+#: VirtualMailManager/maillocation.py:74
+msgid "Empty directory name"
+msgstr "Ordnername ist leer"
+
+#: VirtualMailManager/maillocation.py:76
+#, python-format
+msgid "Directory name is too long: '%s'"
+msgstr "Ordnername ist zu lang: »%s«"
 
-#: vmm:488
-msgid "Unknown subcommand"
-msgstr "Unbekannter Unterbefehl"
+#: VirtualMailManager/password.py:388
+#, python-format
+msgid "Unsupported password scheme: '%s'"
+msgstr "Nicht unterstütztes Passwortschema: »%s«"
+
+#: VirtualMailManager/password.py:391
+#, python-format
+msgid "The password scheme '%(scheme)s' requires Dovecot >= v%(version)s."
+msgstr "Das Passworschema »%(scheme)s« benötigt Dovecot >= v%(version)s."
+
+#: VirtualMailManager/password.py:397
+msgid "Encoding suffixes for password schemes require Dovecot >= v1.1.alpha1."
+msgstr "Encoding-Suffixe für Passwort-Schemata benötigen Dovecot >= v1.1.alpha1."
 
-#: vmm:491
-msgid "Ouch"
-msgstr "Autsch"
+#: VirtualMailManager/password.py:400
+#, python-format
+msgid "Unsupported password encoding: '%s'"
+msgstr "Nicht unterstützte Encodierung des Passworts: »%s«"
+
+#: VirtualMailManager/relocated.py:71
+msgid "Address and destination are identical."
+msgstr "Alias- und Ziel-Adresse sind identisch."
+
+#: VirtualMailManager/relocated.py:75
+#, python-format
+msgid "The relocated user '%s' already exists."
+msgstr "Der verschobene Benutzer »%s« existiert bereits."
--- a/po/fi.po	Mon Nov 07 03:22:15 2011 +0000
+++ b/po/fi.po	Thu Jun 28 19:26:50 2012 +0000
@@ -1,526 +1,801 @@
 # Finnish messages for vmm.
-# Copyright © 2010 Free Software Foundation, Inc.
+# Copyright © 2010, 2012 Free Software Foundation, Inc.
 # This file is distributed under the same license as the vmm package.
-# Jorma Karvonen <karvonen.jorma@gmail.com>, 2010.
+# Jorma Karvonen <karvonen.jorma@gmail.com>, 2010, 2012.
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: vmm 0.5.2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-25 06:07+0200\n"
-"PO-Revision-Date: 2010-12-15 18:19+0200\n"
+"Project-Id-Version: vmm 0.6.0\n"
+"Report-Msgid-Bugs-To: user+vmm/tp@localhost.localdomain.org\n"
+"POT-Creation-Date: 2011-11-07 05:20+0100\n"
+"PO-Revision-Date: 2012-03-13 21:27+0200\n"
 "Last-Translator: Jorma Karvonen <karvonen.jorma@gmail.com>\n"
 "Language-Team: Finnish <translation-team-fi@lists.sourceforge.net>\n"
+"Language: fi\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: fi\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: VirtualMailManager/Account.py:36 VirtualMailManager/Relocated.py:44
+#. 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
+#: VirtualMailManager/account.py:58 VirtualMailManager/alias.py:35
+#: VirtualMailManager/domain.py:120 VirtualMailManager/relocated.py:38
 #, python-format
-msgid "There is already an alias with the address “%s”."
-msgstr "On jo olemassa alias osoitteella “%s”."
+msgid "The domain '%s' does not exist."
+msgstr "Verkkotunnus ’%s’ ei ole vielä olemassa."
 
-#: VirtualMailManager/Account.py:41 VirtualMailManager/Alias.py:45
+#: VirtualMailManager/account.py:106
 #, python-format
-msgid "There is already a relocated user with the address “%s”."
-msgstr "On jo sijoitettu käyttäjä osoitteella “%s”."
+msgid "The mailbox format '%(mbfmt)s' requires Dovecot >= v%(version)s."
+msgstr "Sähköpostilaatikkomuoto ’%(mbfmt)s’ vaatii Dovecot >= v%(version)s."
 
-#: VirtualMailManager/Account.py:61 VirtualMailManager/Alias.py:61
-#: VirtualMailManager/Domain.py:163 VirtualMailManager/Domain.py:189
-#: VirtualMailManager/Domain.py:220 VirtualMailManager/Relocated.py:60
+#: VirtualMailManager/account.py:113 VirtualMailManager/account.py:305
 #, python-format
-msgid "The domain “%s” doesn't exist yet."
-msgstr "Verkkotunnus “%s” ei ole vielä olemassa."
+msgid "Invalid transport '%(transport)s' for mailbox format '%(mbfmt)s'."
+msgstr "Virheellinen siirto ’%(transport)s’ sähköpostilaatikkomuodolle ’%(mbfmt)s’."
 
-#: VirtualMailManager/Account.py:80
+#: VirtualMailManager/account.py:153 VirtualMailManager/cli/handler.py:93
+#: VirtualMailManager/handler.py:628 VirtualMailManager/handler.py:679
+#: VirtualMailManager/handler.py:705 VirtualMailManager/handler.py:716
+#: VirtualMailManager/handler.py:727 VirtualMailManager/handler.py:739
+#: VirtualMailManager/handler.py:753
 #, python-format
-msgid "Unknown service “%s”."
-msgstr "Tuntematon palvelu “%s”."
+msgid "The account '%s' does not exist."
+msgstr "Tiliä ’%s’ ei ole olemassa."
 
-#: VirtualMailManager/Account.py:83 VirtualMailManager/Account.py:150
-#: VirtualMailManager/Account.py:178 VirtualMailManager/Account.py:212
+#: VirtualMailManager/account.py:204 VirtualMailManager/account.py:214
+#: VirtualMailManager/cli/handler.py:77 VirtualMailManager/handler.py:596
 #, python-format
-msgid "The account “%s” doesn't exists."
-msgstr "Tiliä “%s” ei ole vielä olemassa."
+msgid "The account '%s' already exists."
+msgstr "Tili ’%s’ on jo olemassa."
+
+#: VirtualMailManager/account.py:207 VirtualMailManager/handler.py:701
+#, python-format
+msgid "Could not accept password: '%s'"
+msgstr "Ei voitu hyväksyä salasanaa: ’%s’"
 
-#: VirtualMailManager/Account.py:145
+#: VirtualMailManager/account.py:217
 #, python-format
-msgid "The account “%s” already exists."
-msgstr "Tili “%s” on jo olemassa."
+msgid "No password set for account: '%s'"
+msgstr "Tilille ei ole asetettu salasanaa: ’%s’"
 
-#: VirtualMailManager/Account.py:186
-msgid "enabled"
-msgstr "otettu käyttöön"
+#: VirtualMailManager/account.py:245
+#, python-format
+msgid "Unknown field: '%s'"
+msgstr "Tuntematon kenttä: ’%s’"
 
-#: VirtualMailManager/Account.py:188
+#: VirtualMailManager/account.py:267 VirtualMailManager/domain.py:292
+msgid "PostgreSQL-based dictionary quota requires Dovecot >= v1.1.2."
+msgstr "PostgreSQL-perustainen sanakirjakiintiö vaatii Dovecot >= v1.1.2"
+
+#. TP: A service (e.g. pop3 or imap) may be enabled/usable or
+#. disabled/unusable for a user.
+#: VirtualMailManager/account.py:332
 msgid "disabled"
 msgstr "poistettu käytöstä"
 
-#  Alla "count" ja "address" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/Account.py:233
-#, python-format
-msgid "There are %(count)d aliases with the destination address “%(address)s”."
-msgstr "Jo %(count)d aliaksella on kohdeosoite “%(address)s”."
+#: VirtualMailManager/account.py:332
+msgid "enabled"
+msgstr "otettu käyttöön"
 
-#: VirtualMailManager/Account.py:241
-msgid "uid must be an int/long."
-msgstr "uid-käyttäjätunnisteen on oltava tyypiltään int/long."
-
-#: VirtualMailManager/Account.py:243
-msgid "uid must be greater than 0."
-msgstr "uid-käyttäjätunnisteen on oltava suurempi kuin 0."
+#: VirtualMailManager/account.py:343
+#, python-format
+msgid "Could not fetch information for account: '%s'"
+msgstr "Ei voitu noutaa tietoa tilille: ’%s’"
 
-#: VirtualMailManager/Account.py:251
+#  Alla "count" ja "address" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#: VirtualMailManager/account.py:387
 #, python-format
-msgid "There is no account with the UID “%d”."
-msgstr "Ei ole tiliä, jonka UID-käyttäjätunniste on “%d”."
+msgid "There are %(count)d aliases with the destination address '%(address)s'."
+msgstr "On %(count)d aliasta kohdeosoitteella ’%(address)s’."
 
-#: VirtualMailManager/Alias.py:30 VirtualMailManager/Relocated.py:30
-msgid "Address and destination are identical."
-msgstr "Osoite ja kohde ovat identtisiä."
+#: VirtualMailManager/account.py:416
+msgid "UID must be an int/long."
+msgstr "UID-käyttäjätunnisteen on oltava tyypiltään int/long."
 
-#: VirtualMailManager/Alias.py:40 VirtualMailManager/Relocated.py:39
+#: VirtualMailManager/account.py:418
+msgid "UID must be greater than 0."
+msgstr "UID-käyttäjätunnisteen on oltava suurempi kuin 0."
+
+#: VirtualMailManager/account.py:427
 #, python-format
-msgid "There is already an account with address “%s”."
-msgstr "On jo olemassa tili osoitteella “%s”."
+msgid "There is no account with the UID: '%d'"
+msgstr "Ei ole tiliä UID-käyttäjätunnisteella: ’%d’"
 
 #  Alla "address" ja "count" ovat muuttuja, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/Alias.py:71
+#: VirtualMailManager/alias.py:60
+#, python-format
+msgid ""
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"Currently this alias expands into %(count)i/%(limit)i recipients.\n"
+"%(count_new)i additional destination(s) will render this alias unusable.\n"
+"Hint: Increase Postfix' virtual_alias_expansion_limit"
+msgstr ""
+"Ei voida lisätä %(count_new)i uutta kohdetta aliakselle ’%(address)s’.\n"
+"Nykyisin tämä alias laajenee %(count)i/%(limit)i vastaanottajaan.\n"
+"%(count_new)i lisäkohdetta tekee tämän aliaksen käyttökelvottomaksi.\n"
+"Vihje: Kasvata Postfix-arvoa virtual_alias_expansion_limit"
+
+#: VirtualMailManager/alias.py:67
 #, python-format
 msgid ""
-"Can't add new destination to alias “%(address)s”.\n"
-"Currently this alias expands into %(count)i recipients.\n"
-"One more destination will render this alias unusable.\n"
-"Hint: Increase Postfix' virtual_alias_expansion_limit\n"
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"This alias already exceeds its expansion limit (%(count)i/%(limit)i).\n"
+"So its unusable, all messages addressed to this alias will be bounced.\n"
+"Hint: Delete some destination addresses."
 msgstr ""
-"Aliakselle “%(address)s” ei voi lisätä uutta kohdetta,\n"
-"koska sillä on %(count)i vastaanottajaa.\n"
-"Yksi lisäys tekee tämän aliaksen käyttökelvottomaksi.\n"
-"Vihje: Kasvata Postfix-arvoa virtual_alias_expansion_limit\n"
-
-#: VirtualMailManager/Alias.py:80
-msgid "No destination address for alias denoted."
-msgstr "Aliakselle ei ole osoitettu kohdeosoitetta."
+"Ei voida lisätä %(count_new)i uutta kohdetta aliakseen '%(address)s'.\n"
+"Tämä alias ylittää jo laajennusrajansa (%(count)i/%(limit)i).\n"
+"Joten se on käyttökelvoton. Kaikki tähän osoitteeseen suunnatut viestit\n"
+"ponnahtavat takaisin.\n"
+"Vihje: Poista joitakin kohdeosoitteita."
 
-#  Alla "a" ja "d" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/Alias.py:91
+#: VirtualMailManager/alias.py:142 VirtualMailManager/alias.py:154
+#: VirtualMailManager/alias.py:161 VirtualMailManager/handler.py:657
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” already exists."
-msgstr "Alias “%(a)s” kohteella “%(d)s” on jo olemassa."
+msgid "The alias '%s' does not exist."
+msgstr "Alias ’%s’ ei ole olemassa."
 
-#: VirtualMailManager/Alias.py:106 VirtualMailManager/Alias.py:123
+#: VirtualMailManager/alias.py:145
 #, python-format
-msgid "The alias “%s” doesn't exists."
-msgstr "Alias “%s” ei ole olemassa."
+msgid "The address '%(addr)s' is not a destination of the alias '%(alias)s'."
+msgstr "Osoite ’%(addr)s’ ei ole aliaksen ’%(alias)s’ kohde."
 
-#  Alla "a" ja "d" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/Alias.py:125
+#: VirtualMailManager/aliasdomain.py:50
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
-msgstr "Alias “%(a)s” kohteella “%(d)s” ei ole olemassa."
+msgid "The domain '%s' is a primary domain."
+msgstr "Verkkotunnus ’%s’ on ensisijainen verkkotunnus."
 
-#: VirtualMailManager/AliasDomain.py:32
+#: VirtualMailManager/aliasdomain.py:69
 #, python-format
-msgid "The domain “%s” is a primary domain."
-msgstr "Verkkotunnus “%s” on ensisijainen verkkotunnus."
+msgid "The alias domain '%s' already exists."
+msgstr "Aliasverkkotunnus ’%s’ on jo olemassa."
 
-#: VirtualMailManager/AliasDomain.py:37
-#, python-format
-msgid "The alias domain “%s” already exists."
-msgstr "Aliasverkkotunnus “%s” on jo olemassa."
+#: VirtualMailManager/aliasdomain.py:72 VirtualMailManager/aliasdomain.py:106
+msgid "No destination domain set for the alias domain."
+msgstr "Ei kohdeverkkotunnusjoukkoa aliasverkkotunnukselle."
 
-#: VirtualMailManager/AliasDomain.py:40 VirtualMailManager/AliasDomain.py:70
-msgid "No destination domain for alias domain denoted."
-msgstr "Kohdeverkkotunnukselle ei ole osoitettu aliasverkkotunnusta."
-
-#: VirtualMailManager/AliasDomain.py:43 VirtualMailManager/AliasDomain.py:73
+#: VirtualMailManager/aliasdomain.py:75 VirtualMailManager/aliasdomain.py:109
 #, python-format
-msgid "The target domain “%s” doesn't exist yet."
-msgstr "Kohdeverkkotunnusta “%s” ei vielä ole."
+msgid "The target domain '%s' does not exist."
+msgstr "Kohdeverkkotunnusta ’%s’ ei ole."
 
-#: VirtualMailManager/AliasDomain.py:62
+#: VirtualMailManager/aliasdomain.py:88 VirtualMailManager/aliasdomain.py:112
+#: VirtualMailManager/aliasdomain.py:133
 #, python-format
-msgid "There is no primary domain for the alias domain “%s”."
-msgstr "Ei ole ensisijaista verkkotunnusta aliasverkkotunnukselle “%s”."
+msgid "The alias domain '%s' does not exist."
+msgstr "Aliasverkkotunnusta ’%s’ ei ole."
 
-#: VirtualMailManager/AliasDomain.py:65 VirtualMailManager/AliasDomain.py:76
-#: VirtualMailManager/AliasDomain.py:99
+#: VirtualMailManager/aliasdomain.py:98
 #, python-format
-msgid "The alias domain “%s” doesn't exist yet."
-msgstr "Aliasverkkotunnusta “%s” ei vielä ole."
+msgid "There is no primary domain for the alias domain '%s'."
+msgstr "Ei ole ensisijaista verkkotunnusta aliasverkkotunnukselle ’%s’."
 
 #  Alla "alias" ja "domain" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/AliasDomain.py:79
+#: VirtualMailManager/aliasdomain.py:115
 #, python-format
-msgid "The alias domain “%(alias)s” is already assigned to the domain “%(domain)s”."
-msgstr "Aliasverkkotunnus “%(alias)s” on jo liitetty verkkotunnukseen “%(domain)s”."
+msgid "The alias domain '%(alias)s' is already assigned to the domain '%(domain)s'."
+msgstr "Aliasverkkotunnus ’%(alias)s’ on jo liitetty verkkotunnukseen ’%(domain)s’."
+
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:78
+msgid "Enter new password: "
+msgstr "Uusi salasana: "
+
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:80
+msgid "Retype new password: "
+msgstr "Uusi salasana uudelleen: "
 
-#: VirtualMailManager/Config.py:102 VirtualMailManager/Config.py:137
+#: VirtualMailManager/cli/__init__.py:85 VirtualMailManager/cli/config.py:53
+msgid "Too many failures - try again later."
+msgstr "Liian monia häiriöitä - yritä uudelleen myöhemmin."
+
+#: VirtualMailManager/cli/__init__.py:91
+msgid "Sorry, passwords do not match."
+msgstr "Salasanat eivät täsmää."
+
+#: VirtualMailManager/cli/__init__.py:95
+msgid "Sorry, empty passwords are not permitted."
+msgstr "Tyhjät salasanat eivät ole sallittuja."
+
+#  Alla "opt" ja "val" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#: VirtualMailManager/cli/config.py:32
+#, python-format
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
+msgstr "Uusi arvo valitsimelle %(option)s [%(current_value)s]: "
+
+#: VirtualMailManager/cli/config.py:36
 #, python-format
 msgid "Using configuration file: %s\n"
 msgstr "Käytetään asetustiedostoa: %s\n"
 
-#: VirtualMailManager/Config.py:106
+#: VirtualMailManager/cli/config.py:38
 #, python-format
-msgid "missing section: %s\n"
-msgstr "puuttuva lohko: %s\n"
+msgid "* Configuration section: '%s'"
+msgstr "* Asetuslohko: ’%s’"
 
-#: VirtualMailManager/Config.py:108
+#: VirtualMailManager/cli/config.py:50
+#, python-format
+msgid "Warning: %s"
+msgstr "Varoitus: %s"
+
+#: VirtualMailManager/cli/handler.py:66
 #, python-format
-msgid "missing options in section %s:\n"
-msgstr "puuttuvia valitsimia lohkossa %s:\n"
+msgid "Invalid section: '%s'"
+msgstr "Virheellinen lohko: ’%s’"
 
-#: VirtualMailManager/Config.py:140
+#: VirtualMailManager/cli/main.py:32 VirtualMailManager/cli/main.py:65
+#: VirtualMailManager/cli/main.py:68 VirtualMailManager/cli/subcommands.py:629
+#: VirtualMailManager/cli/subcommands.py:649
 #, python-format
-msgid "* Config section: “%s”"
-msgstr "* Asetuslohko: “%s”"
+msgid "Error: %s"
+msgstr "Virhe: %s"
 
-#  Alla "opt" ja "val" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/Config.py:143
+#: VirtualMailManager/cli/main.py:41
+msgid "You must specify a subcommand at least."
+msgstr "Sinun on määriteltävä vähintään alikomento."
+
+#: VirtualMailManager/cli/main.py:53
 #, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
-msgstr "Uusi arvo valitsimelle %(opt)s [%(val)s]: "
+msgid "Unknown subcommand: '%s'"
+msgstr "Tuntematon alikomento: ’%s’"
 
-#: VirtualMailManager/Domain.py:39
+#. TP: We have to cry, because root has killed/interrupted vmm
+#. with Ctrl+C or Ctrl+D.
+#: VirtualMailManager/cli/main.py:62
+msgid "Ouch!"
+msgstr "Auh!"
+
+#: VirtualMailManager/cli/main.py:71
 #, python-format
-msgid "The domain “%s” is an alias domain."
-msgstr "Verkkotunnus “%s” on aliasverkkotunnus."
+msgid "Error: Unknown section: '%s'"
+msgstr "Virhe: Tuntematon lohko: ’%s’"
 
-#: VirtualMailManager/Domain.py:124
-msgid "There are accounts and aliases."
-msgstr "On tilejä ja aliaksia."
+#: VirtualMailManager/cli/main.py:74
+#, python-format
+msgid "Error: No option '%(option)s' in section: '%(section)s'"
+msgstr "Virhe: Ei valitsinta ’%(option)s’ lohkossa: ’%(section)s’"
+
+#: VirtualMailManager/cli/main.py:77
+msgid "Warnings:"
+msgstr "Varoitukset:"
 
-#: VirtualMailManager/Domain.py:127
-msgid "There are accounts."
-msgstr "Tilejä on."
+#: VirtualMailManager/cli/subcommands.py:78
+#, python-format
+msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
+msgstr "Suunnitelma A epäonnistui ... yritetään suunnitelmaa B: %(subcommand)s %(object)s"
 
-#: VirtualMailManager/Domain.py:130
-msgid "There are aliases."
-msgstr "Aliaksia on."
+#: VirtualMailManager/cli/subcommands.py:92
+msgid "Missing alias address and destination."
+msgstr "Puuttuva aliasosoite ja -kohde."
+
+#: VirtualMailManager/cli/subcommands.py:95
+#: VirtualMailManager/cli/subcommands.py:453
+msgid "Missing destination address."
+msgstr "Puuttuva kohdeosoite."
 
-#: VirtualMailManager/Domain.py:145
-#, python-format
-msgid "The domain “%s” already exists."
-msgstr "Verkkotunnus “%s” on jo olemassa."
+#: VirtualMailManager/cli/subcommands.py:102
+#: VirtualMailManager/cli/subcommands.py:112
+msgid "Missing alias address."
+msgstr "Puuttuva aliasosoite."
+
+#: VirtualMailManager/cli/subcommands.py:134
+#: VirtualMailManager/cli/subcommands.py:168
+msgid "Missing alias domain name and destination domain name."
+msgstr "Puuttuva aliasverkkotunnusnimi ja kohdeverkkotunnusnimi."
+
+#: VirtualMailManager/cli/subcommands.py:137
+#: VirtualMailManager/cli/subcommands.py:171
+msgid "Missing destination domain name."
+msgstr "Puuttuva kohdeverkkotunnuksen nimi."
+
+#: VirtualMailManager/cli/subcommands.py:145
+#: VirtualMailManager/cli/subcommands.py:152
+msgid "Missing alias domain name."
+msgstr "Puuttuva aliasverkkotunnuksen nimi."
 
-#: VirtualMailManager/EmailAddress.py:46
-#, python-format
-msgid "Missing '@' sign in e-mail address “%s”."
-msgstr "Puuttuva ’@’-merkki sähköpostiosoitteessa “%s”."
+#: VirtualMailManager/cli/subcommands.py:179
+msgid "Missing option name."
+msgstr "Puuttuva valitsinnimi."
+
+#: VirtualMailManager/cli/subcommands.py:195
+msgid "Missing option and new value."
+msgstr "Puuttuva valitsin ja uusi arvo."
 
-#: VirtualMailManager/EmailAddress.py:49
-#, python-format
-msgid "“%s” looks not like an e-mail address."
-msgstr "“%s” ei näytä sähköpostiosoitteelta."
+#: VirtualMailManager/cli/subcommands.py:197
+msgid "Missing new configuration value."
+msgstr "Puuuttuva uusi asetusarvo."
 
-#: VirtualMailManager/EmailAddress.py:54
+#: VirtualMailManager/cli/subcommands.py:213
+#: VirtualMailManager/cli/subcommands.py:229
+#: VirtualMailManager/cli/subcommands.py:242
+#: VirtualMailManager/cli/subcommands.py:331
+msgid "Missing domain name."
+msgstr "Puuttuva verkkotunnusnimi."
+
+#: VirtualMailManager/cli/subcommands.py:219
 #, python-format
-msgid "Missing domain name after “%s@”."
-msgstr "Puuttuu verkkotunnusnimi “%s@”-merkkijonon jäljestä."
+msgid "Creating account for postmaster@%s"
+msgstr "Luodaan tili postmaster@%s-osoitteelle"
 
-#: VirtualMailManager/EmailAddress.py:66
-msgid "No localpart specified."
-msgstr "Paikallisosaa ei ole määritelty."
-
-#: VirtualMailManager/EmailAddress.py:69
+#: VirtualMailManager/cli/subcommands.py:235
+#: VirtualMailManager/cli/subcommands.py:249
+#: VirtualMailManager/cli/subcommands.py:322
+#: VirtualMailManager/cli/subcommands.py:343
+#: VirtualMailManager/cli/subcommands.py:372
+#: VirtualMailManager/cli/subcommands.py:509
+#: VirtualMailManager/cli/subcommands.py:522 VirtualMailManager/handler.py:453
+#: VirtualMailManager/handler.py:466 VirtualMailManager/handler.py:481
+#: VirtualMailManager/handler.py:510 VirtualMailManager/handler.py:674
 #, python-format
-msgid "The local part “%s” is too long"
-msgstr "Paikallisosa “%s” on liian pitkä"
+msgid "Invalid argument: '%s'"
+msgstr "Virheellinen argumentti: ’%s’"
 
-#  Alla "lpart" ja "ichrs" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/EmailAddress.py:76
-#, python-format
-msgid "The local part “%(lpart)s” contains invalid characters: %(ichrs)s"
-msgstr "Paikallisosa “%(lpart)s” sisältää virheellisiä merkkejä: %(ichrs)s"
+#: VirtualMailManager/cli/subcommands.py:267
+#: VirtualMailManager/cli/subcommands.py:273
+msgid "Domain"
+msgstr "Verkkotunnus"
 
-#: VirtualMailManager/MailLocation.py:32
-msgid "Either mid or maillocation must be specified."
-msgstr "Joko mid-tunniste tai sähköpostisijainti on määriteltävä."
+#: VirtualMailManager/cli/subcommands.py:275
+#: VirtualMailManager/cli/subcommands.py:284
+msgid "accounts"
+msgstr "tilit"
 
-#: VirtualMailManager/MailLocation.py:38
-msgid "mid must be an int/long."
-msgstr "tunnisteen mid on oltava tyypiltään int/long."
+#: VirtualMailManager/cli/subcommands.py:277
+#: VirtualMailManager/cli/subcommands.py:283
+#: VirtualMailManager/cli/subcommands.py:831
+msgid "alias domains"
+msgstr "aliasverkkotunnukset"
+
+#: VirtualMailManager/cli/subcommands.py:279
+#: VirtualMailManager/cli/subcommands.py:285
+msgid "aliases"
+msgstr "aliakset"
 
-#: VirtualMailManager/MailLocation.py:46
+#: VirtualMailManager/cli/subcommands.py:281
+#: VirtualMailManager/cli/subcommands.py:286
+msgid "relocated users"
+msgstr "sijoitetut käyttäjät"
+
+#: VirtualMailManager/cli/subcommands.py:292
+msgid "Missing domain name and storage value."
+msgstr "Puuttuva verkkotunnusnimi ja tallennusarvo."
+
+#: VirtualMailManager/cli/subcommands.py:295
+#: VirtualMailManager/cli/subcommands.py:582
+msgid "Missing storage value."
+msgstr "Puuttuva tallennusarvo."
+
+#: VirtualMailManager/cli/subcommands.py:301
+#: VirtualMailManager/cli/subcommands.py:586
 #, python-format
-msgid ""
-"Invalid folder name “%s”, it may consist only of\n"
-"1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
-msgstr ""
-"Virheellinen kansionimi “%s”, se voi sisältää vain\n"
-"1 - 20 yhden tavun mittaisia merkkejä (A-Z, a-z, 0-9 ja _)."
-
-#: VirtualMailManager/MailLocation.py:59
-msgid "Unknown mid specified."
-msgstr "Tuntematon mid-määritelty."
+msgid "Invalid storage value: '%s'"
+msgstr "Virheellinen argumentti: ’%s’"
 
-#: VirtualMailManager/Relocated.py:65
-msgid "No destination address for relocated user denoted."
-msgstr "Sijoitetulle käyttäjälle ei ole osoitettu kohdeosoitetta."
+#: VirtualMailManager/cli/subcommands.py:311
+#, python-format
+msgid "Neither a valid number of messages nor the keyword 'force': '%s'"
+msgstr "Ei ole kelvollinen viestien lukumäärä eikä avainsana ’force’: ’%s’"
 
-#: VirtualMailManager/Relocated.py:75
+#: VirtualMailManager/cli/subcommands.py:319
+#: VirtualMailManager/cli/subcommands.py:595
 #, python-format
-msgid "The relocated user “%s” already exists."
-msgstr "Sijoitettu käyttäjä “%s” on jo olemassa."
+msgid "Not a valid number of messages: '%s'"
+msgstr "Viestien lukumäärä ei ole kelvollinen: ’%s’"
+
+#: VirtualMailManager/cli/subcommands.py:354
+#: VirtualMailManager/cli/subcommands.py:609
+#, python-format
+msgid "Invalid service arguments: %s"
+msgstr "Virheelliset palveluargumentit: %s"
 
-#: VirtualMailManager/Relocated.py:89 VirtualMailManager/Relocated.py:102
+#: VirtualMailManager/cli/subcommands.py:363
+msgid "Missing domain name and new transport."
+msgstr "Puuttuva verkkotunnusnimi ja uusi siirto."
+
+#: VirtualMailManager/cli/subcommands.py:366
+msgid "Missing new transport."
+msgstr "Puuttuva uusi siirto."
+
+#: VirtualMailManager/cli/subcommands.py:380
+msgid "Missing UID."
+msgstr "Puuttuva käyttäjätunniste."
+
+#: VirtualMailManager/cli/subcommands.py:381
+#: VirtualMailManager/cli/subcommands.py:545
+#: VirtualMailManager/cli/subcommands.py:551
+msgid "Account"
+msgstr "Tilit"
+
+#: VirtualMailManager/cli/subcommands.py:396
 #, python-format
-msgid "The relocated user “%s” doesn't exists."
-msgstr "Sijoitettua käyttäjää “%s” ei ole olemassa."
-
-#: VirtualMailManager/Transport.py:29
-msgid "Either tid or transport must be specified."
-msgstr "Joko tid-siirtotunniste tai siirto on määriteltävä."
+msgid "Unknown help topic: '%s'"
+msgstr "Tuntematon opasteaihe: ’%s’"
 
-#: VirtualMailManager/Transport.py:35
-msgid "tid must be an int/long."
-msgstr "tunnisteen tid on oltava tyypiltään int/long."
+#: VirtualMailManager/cli/subcommands.py:409
+msgid "List of available subcommands:"
+msgstr "Käytettävissä olevien alikomentojen luettelo:"
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable encoding suffixes:"
+msgstr "Käyttökelpoiset koodausloppuliitteet:"
 
-#: VirtualMailManager/Transport.py:63
-msgid "Unknown tid specified."
-msgstr "Tuntematon tid-siirtotunniste määritelty."
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable password schemes:"
+msgstr "Käyttökelpoiset salasanakaavat:"
+
+#: VirtualMailManager/cli/subcommands.py:451
+msgid "Missing relocated address and destination."
+msgstr "Puuttuva sijoitusosoite ja -kohde."
 
-#: VirtualMailManager/VirtualMailManager.py:54
-msgid ""
-"You are not root.\n"
-"\tGood bye!\n"
-msgstr ""
-"Et ole root-käyttäjä.\n"
-"\tNäkemiin!\n"
+#: VirtualMailManager/cli/subcommands.py:460
+#: VirtualMailManager/cli/subcommands.py:467
+msgid "Missing relocated address."
+msgstr "Puuttuva sijoitusosoite."
 
-#: VirtualMailManager/VirtualMailManager.py:74
-msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
-msgstr "Tiedostoa “vmm.cfg” ei löytynyt hakemistoista: /root:/usr/local/etc:/etc"
+#: VirtualMailManager/cli/subcommands.py:490
+#: VirtualMailManager/cli/subcommands.py:503
+#: VirtualMailManager/cli/subcommands.py:516
+#: VirtualMailManager/cli/subcommands.py:568
+#: VirtualMailManager/cli/subcommands.py:603
+msgid "Missing e-mail address."
+msgstr "Puuttuva sähköpostiosoite."
+
+#: VirtualMailManager/cli/subcommands.py:497
+#, python-format
+msgid "Generated password: %s"
+msgstr "Tuotettu salasana: %s"
 
-#  Alla "perms" ja "file" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla. Lainausmerkit jätettiin paikallistamatta, koska ne saattavat olla skriptin toiminnan kannalta kriittisiä.
-#: VirtualMailManager/VirtualMailManager.py:85
-#, python-format
-msgid ""
-"fix permissions (%(perms)s) for “%(file)s”\n"
-"`chmod 0600 %(file)s` would be great."
-msgstr ""
-"korjaa käyttöoikeudet (%(perms)s) tiedostolle “%(file)s”\n"
-"`chmod 0600 %(file)s` olisi erinomainen."
+#: VirtualMailManager/cli/subcommands.py:552
+msgid "alias addresses"
+msgstr "aliasosoitteet"
+
+#: VirtualMailManager/cli/subcommands.py:558
+msgid "Missing e-mail address and user's name."
+msgstr "Puuttuva sähköpostiosoite ja käyttäjän nimi."
+
+#: VirtualMailManager/cli/subcommands.py:561
+msgid "Missing user's name."
+msgstr "Puuttuva käyttäjän nimi."
+
+#: VirtualMailManager/cli/subcommands.py:579
+msgid "Missing e-mail address and storage value."
+msgstr "Puuttuva sähköpostiosoite ja käyttäjän nimi."
 
-#: VirtualMailManager/VirtualMailManager.py:100
-#, python-format
-msgid ""
-"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
-msgstr ""
-"“%s” ei ole hakemisto.\n"
-"(vmm.cfg: lohko ”domdir”, valitsin ”base”)"
+#: VirtualMailManager/cli/subcommands.py:617
+msgid "Missing e-mail address and transport."
+msgstr "Puuttuva sähköpostiosoite ja siirto."
+
+#: VirtualMailManager/cli/subcommands.py:620
+msgid "Missing transport."
+msgstr "Puuttuva siirto."
 
-#  Alla "options" on muuttuja, joka korvautuu suorituksen yhteydessä koodilla.
-#: VirtualMailManager/VirtualMailManager.py:105
+#: VirtualMailManager/cli/subcommands.py:630
+msgid "usage: "
+msgstr "käyttö: "
+
+#. 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]
+#: VirtualMailManager/cli/subcommands.py:640
 #, python-format
 msgid ""
-"“%(binary)s” doesn't exists.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+"usage: %s subcommand arguments\n"
+"  short long\n"
+"  subcommand                arguments\n"
 msgstr ""
-"“%(binary)s” ei ole olemassa.\n"
-"(vmm.cfg: lohko ”bin”, valitsin ”%(option)s”)"
+"käyttö: %s alikomento argumentit\n"
+"  lyhyt pitkä\n"
+"  alikomento                argumentit\n"
+
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "from"
+msgstr "kohteesta"
+
+#. 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
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "version"
+msgstr "versio"
+
+#: VirtualMailManager/cli/subcommands.py:662
+msgid "on"
+msgstr "kohteessa"
+
+#: VirtualMailManager/cli/subcommands.py:664
+msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
+msgstr "on vapaa ohjelmisto ja tulee EHDOTTOMASTI ILMAN TAKUUTA."
 
-#  Alla "options" on muuttuja, joka korvautuu suorituksen yhteydessä koodilla.
-#: VirtualMailManager/VirtualMailManager.py:109
-#, python-format
-msgid ""
-"“%(binary)s” is not executable.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
-msgstr ""
-"“%(binary)s” ei ole suoritettava tiedosto.\n"
-"(vmm.cfg: lohko ”bin”, valitsin ”%(option)s”)"
+#: VirtualMailManager/cli/subcommands.py:672
+msgid "uid"
+msgstr "käyttäjätunniste"
+
+#: VirtualMailManager/cli/subcommands.py:673
+msgid "get the address of the user with the given UID"
+msgstr "hae käyttäjän osoite annetulla käyttäjätunnuksella"
+
+#: VirtualMailManager/cli/subcommands.py:674
+#: VirtualMailManager/cli/subcommands.py:684
+msgid "address [password]"
+msgstr "osoite [salasana]"
+
+#: VirtualMailManager/cli/subcommands.py:675
+msgid "create a new e-mail user with the given address"
+msgstr "luo uusi sähköpostikäyttäj annetulla osoitteella"
+
+#: VirtualMailManager/cli/subcommands.py:677
+#: VirtualMailManager/cli/subcommands.py:704
+#: VirtualMailManager/cli/subcommands.py:744
+#: VirtualMailManager/cli/subcommands.py:746
+msgid "address"
+msgstr "osoite"
+
+#: VirtualMailManager/cli/subcommands.py:678
+msgid "delete the specified user"
+msgstr "poista määritelty käyttäjä"
+
+#: VirtualMailManager/cli/subcommands.py:679
+msgid "address [details]"
+msgstr "osoite [yksityiskohdat]"
 
-#: VirtualMailManager/VirtualMailManager.py:166
-msgid "The domain name is too long."
-msgstr "Verkkotunnusnimi on liian pitkä."
+#: VirtualMailManager/cli/subcommands.py:680
+msgid "display information about the given address"
+msgstr "näytä tietoja annetusta osoitteesta"
+
+#: VirtualMailManager/cli/subcommands.py:681
+msgid "address name"
+msgstr "osoitenimi"
+
+#: VirtualMailManager/cli/subcommands.py:682
+msgid "set or update the real name for an address"
+msgstr "aseta tai päivitä osoitteen todellinen nimi"
+
+#: VirtualMailManager/cli/subcommands.py:685
+msgid "update the password for the given address"
+msgstr "päivitä annetun osoitteen salasana"
 
-#: VirtualMailManager/VirtualMailManager.py:169
-#, python-format
-msgid "The domain name “%s” is invalid."
-msgstr "Verkkotunnusnimi “%s” on virheellinen."
+#: VirtualMailManager/cli/subcommands.py:687
+msgid "address storage [messages]"
+msgstr "osoitetallennus [viestit]"
+
+#: VirtualMailManager/cli/subcommands.py:688
+msgid "update the quota limit for the given address"
+msgstr "päivitä annetun osoitteen kiintiöraja"
+
+#: VirtualMailManager/cli/subcommands.py:690
+msgid "address [service ...]"
+msgstr "osoite [palvelu ...]"
+
+#: VirtualMailManager/cli/subcommands.py:691
+msgid "enables the specified services and disables all not specified services"
+msgstr "ottaa käyttöön määritellyt palvelut ja ottaa pois käytöstä määrittelemättömät palvelut"
 
-#: VirtualMailManager/VirtualMailManager.py:209
-msgid "Enter new password: "
-msgstr "Uusi salasana: "
+#: VirtualMailManager/cli/subcommands.py:694
+msgid "address transport"
+msgstr "osoitesiirto"
+
+#: VirtualMailManager/cli/subcommands.py:695
+msgid "update the transport of the given address"
+msgstr "päivitä annetun osoitteen siirto"
+
+#: VirtualMailManager/cli/subcommands.py:697
+msgid "address destination ..."
+msgstr "osoitekohde ..."
+
+#: VirtualMailManager/cli/subcommands.py:698
+msgid "create a new alias e-mail address with one or more destinations"
+msgstr "luo uusi aliassähköpostiosoite yhdellä tai useammalla kohteella"
 
-#: VirtualMailManager/VirtualMailManager.py:210
-msgid "Retype new password: "
-msgstr "Uusi salasana uudelleen: "
+#: VirtualMailManager/cli/subcommands.py:701
+msgid "address [destination]"
+msgstr "osoite [kohde]"
+
+#: VirtualMailManager/cli/subcommands.py:702
+msgid "delete the specified alias e-mail address or one of its destinations"
+msgstr "poista määritellyn aliaksen sähköpostiosoite tai yksi sen kohteista"
 
-#: VirtualMailManager/VirtualMailManager.py:212
-msgid "Sorry, passwords do not match"
-msgstr "Salasanat eivät täsmää"
+#: VirtualMailManager/cli/subcommands.py:705
+msgid "show the destination(s) of the specified alias"
+msgstr "näytä määritellyn aliaksen kohteet"
+
+#: VirtualMailManager/cli/subcommands.py:708
+#: VirtualMailManager/cli/subcommands.py:717
+msgid "fqdn destination"
+msgstr "fqdn-kohde"
 
-#: VirtualMailManager/VirtualMailManager.py:216
-msgid "Sorry, empty passwords are not permitted"
-msgstr "Tyhjät salasanat eivät ole sallittuja"
+#: VirtualMailManager/cli/subcommands.py:709
+msgid "create a new alias for an existing domain"
+msgstr "luotu uusi alias olemassaolevaan verkkotunnukseen"
+
+#: VirtualMailManager/cli/subcommands.py:711
+#: VirtualMailManager/cli/subcommands.py:714
+#: VirtualMailManager/cli/subcommands.py:723
+msgid "fqdn"
+msgstr "fqdn"
+
+#: VirtualMailManager/cli/subcommands.py:712
+msgid "delete the specified alias domain"
+msgstr "poista määritelty aliasverkkotunnus"
+
+#: VirtualMailManager/cli/subcommands.py:715
+msgid "show the destination of the given alias domain"
+msgstr "näytä annetun aliasverkkotunnuksen kohde"
+
+#: VirtualMailManager/cli/subcommands.py:718
+msgid "assign the given alias domain to an other domain"
+msgstr "liitä annettu aliasverkkotunnus toiseen verkkotunnukseen"
+
+#: VirtualMailManager/cli/subcommands.py:720
+msgid "fqdn [transport]"
+msgstr "fqdn [siirto]"
+
+#: VirtualMailManager/cli/subcommands.py:721
+msgid "create a new domain"
+msgstr "luo uusi verkkotunnus"
 
-#  Tässä argumentti saattaa olla hakemistonimi tai sitten virheilmoitus.
-#: VirtualMailManager/VirtualMailManager.py:265
-#: VirtualMailManager/VirtualMailManager.py:352
-#, python-format
-msgid "No such directory: %s"
-msgstr "Ei löydy hakemistoa: %s"
+#: VirtualMailManager/cli/subcommands.py:724
+msgid "delete the given domain and all its alias domains"
+msgstr "poista annettu verkkotunnus ja kaikki sen aliasverkkotunnukset"
+
+#: VirtualMailManager/cli/subcommands.py:725
+msgid "fqdn [details]"
+msgstr "fqdn [yksityiskohdat]"
+
+#: VirtualMailManager/cli/subcommands.py:726
+msgid "display information about the given domain"
+msgstr "näytä tiedot annetusta verkkotunnuksesta"
+
+#: VirtualMailManager/cli/subcommands.py:728
+msgid "fqdn storage [messages]"
+msgstr "fqdn-tallennus [viestit]"
 
-#: VirtualMailManager/VirtualMailManager.py:340
-msgid "Found \"..\" in home directory path."
-msgstr "Löytyi ”..” kotihakemistopolussa."
+#: VirtualMailManager/cli/subcommands.py:729
+msgid "update the quota limit of the specified domain"
+msgstr "päivitä määritellyn verkkotunnuksen kiintiöraja"
+
+#: VirtualMailManager/cli/subcommands.py:731
+msgid "fqdn [service ...]"
+msgstr "fqdn [palvelu ...]"
 
-#: VirtualMailManager/VirtualMailManager.py:348
-msgid "Owner/group mismatch in home directory detected."
-msgstr "Kotihakemiston omistaja ja ryhmä eivät täsmää."
+#: VirtualMailManager/cli/subcommands.py:732
+msgid "enables the specified services and disables all not specified services of the given domain"
+msgstr "ottaa käyttöön määritellyt palvelut ja ottaa pois käytöstä kaikki määrittelemättömät annetun verkkotunnuksen palvelut"
+
+#: VirtualMailManager/cli/subcommands.py:735
+msgid "fqdn transport"
+msgstr "fqdn-siirto"
+
+#: VirtualMailManager/cli/subcommands.py:736
+msgid "update the transport of the specified domain"
+msgstr "päivitä määritellyn verkkotunnuksen siirto"
 
-#: VirtualMailManager/VirtualMailManager.py:364
-msgid "FATAL: \"..\" in domain directory path detected."
-msgstr "VAKAVA VIRHE: havaittu ”..” verkkotunnushakemistopolkussa."
+#: VirtualMailManager/cli/subcommands.py:737
+msgid "[pattern]"
+msgstr "[malli]"
+
+#: VirtualMailManager/cli/subcommands.py:738
+msgid "list all domains / search domains by pattern"
+msgstr "luettele kaikki mallin mukaiset verkkotunnukset / hakuverkkotunnukset"
+
+#: VirtualMailManager/cli/subcommands.py:741
+msgid "address newaddress"
+msgstr "osoite newaddress"
+
+#: VirtualMailManager/cli/subcommands.py:742
+msgid "create a new record for a relocated user"
+msgstr "luo uusi tietue sijoitettavalle käyttäjälle"
 
-#: VirtualMailManager/VirtualMailManager.py:370
-msgid "FATAL: group mismatch in domain directory detected"
-msgstr "VAKAVA VIRHE: havaittu ryhmätäsmäämättömyys verkkotunnushakemistossa"
+#: VirtualMailManager/cli/subcommands.py:745
+msgid "delete the record of the relocated user"
+msgstr "poista sijoitetun käyttäjän tietue"
+
+#: VirtualMailManager/cli/subcommands.py:747
+msgid "print information about a relocated user"
+msgstr "tulosta tiedot sijoitetusta käyttäjästä"
+
+#: VirtualMailManager/cli/subcommands.py:749
+msgid "option"
+msgstr "valitsin"
+
+#: VirtualMailManager/cli/subcommands.py:750
+msgid "show the actual value of the configuration option"
+msgstr "näytä asetusvalitsimen todellinen arvo"
 
-#  Tässä on ilmeisesti kirjoitusvirhe (Configurtion -> Configuration).
-#: VirtualMailManager/VirtualMailManager.py:457
-#, python-format
-msgid ""
-"Configurtion error: \"%s\"\n"
-"(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
-msgstr ""
-"Asetusvirhe: ”%s”\n"
-"(lohkossa ”connfig”, valitsin ”done”) katso myös: vmm.cfg(5)\n"
+#: VirtualMailManager/cli/subcommands.py:751
+msgid "option value"
+msgstr "valitsinarvo"
+
+#: VirtualMailManager/cli/subcommands.py:752
+msgid "set a new value for the configuration option"
+msgstr "aseta asetusvalitsimelle uusi arvo"
+
+#: VirtualMailManager/cli/subcommands.py:753
+msgid "[section]"
+msgstr "[lohko]"
+
+#: VirtualMailManager/cli/subcommands.py:754
+msgid "start interactive configuration modus"
+msgstr "aloita vuorovaikutteinen asetustila"
 
-#: VirtualMailManager/VirtualMailManager.py:477
+#: VirtualMailManager/cli/subcommands.py:756
+msgid "lists all usable password schemes and password encoding suffixes"
+msgstr "luettelee kaikki käytettävät salasanakaavat ja salasanakoodausloppuliitteet"
+
+#: VirtualMailManager/cli/subcommands.py:758
+msgid "[subcommand]"
+msgstr "[alikomento]"
+
+#: VirtualMailManager/cli/subcommands.py:759
+msgid "show a help overview or help for the given subcommand"
+msgstr "näytä opasteyhteenveto tai opaste annetusta alikomennosta"
+
+#: VirtualMailManager/cli/subcommands.py:761
+msgid "show version and copyright information"
+msgstr "näytä versio ja copyright-tiedot"
+
+#: VirtualMailManager/cli/subcommands.py:809
 #, python-format
-msgid "Invalid section: “%s”"
-msgstr "Virheellinen lohko: ’%s’"
-
-#: VirtualMailManager/VirtualMailManager.py:487
-#: VirtualMailManager/VirtualMailManager.py:497
-#: VirtualMailManager/VirtualMailManager.py:516
-#: VirtualMailManager/VirtualMailManager.py:624
-#: VirtualMailManager/VirtualMailManager.py:655
-#, python-format
-msgid "Invalid argument: “%s”"
-msgstr "Virheellinen argumentti: “%s“"
-
-#: VirtualMailManager/VirtualMailManager.py:520
-msgid ""
-"The keyword “detailed” is deprecated and will be removed in a future release.\n"
-"   Please use the keyword “full” to get full details."
-msgstr ""
-"Avainsana “detailed” on vanhentunut ja poistetaan tulevassa julkaisussa.\n"
-"   Saat kaikki yksityiskohdat käyttämällä avainsanaa “full”."
-
-#: VirtualMailManager/VirtualMailManager.py:593
-#, python-format
-msgid "The pattern “%s” contains invalid characters."
-msgstr "Säännöllinen lauseke “%s” sisältää virheellisiä merkkejä."
-
-#: VirtualMailManager/VirtualMailManager.py:619
-#, python-format
-msgid "The destination account/alias “%s” doesn't exists yet."
-msgstr "Kohdetili/-alias “%s” ei ole vielä olemassa."
+msgid "[%(percent)s%%] %(used)s/%(limit)s"
+msgstr "[%(percent)s%%] %(used)s/%(limit)s"
 
-#  Alla "directory" ja "raeson" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: VirtualMailManager/VirtualMailManager.py:636
-#, python-format
-msgid ""
-"The account has been successfully deleted from the database.\n"
-"    But an error occurred while deleting the following directory:\n"
-"    “%(directory)s”\n"
-"    Reason: %(raeson)s"
-msgstr ""
-"Tili on poistettu onnistuneesti tietokannasta.\n"
-"    Mutta tapahtui virhe, kun poistettiin seuraavaa hakemistoa:\n"
-"    “%(directory)s”\n"
-"    Syy: %(raeson)s"
-
-#: VirtualMailManager/VirtualMailManager.py:676
-msgid "Account doesn't exists"
-msgstr "Tiliä ei ole"
-
-#: VirtualMailManager/VirtualMailManager.py:692
-#: VirtualMailManager/VirtualMailManager.py:702
-msgid ""
-"The service name “managesieve” is deprecated and will be removed\n"
-"   in a future release.\n"
-"   Please use the service name “sieve” instead."
-msgstr ""
-"Palvelunimi “managesieve” on vanhentunut ja poistetaan\n"
-"   tulevassa julkaisussa.\n"
-"   Käytä sen sijaan palvelunimeä “sieve”."
-
-#: VirtualMailManager/ext/Postconf.py:44
-#, python-format
-msgid "The value “%s” looks not like a valid postfix configuration parameter name."
-msgstr "Arvo “%s” ei näytä olevan kelvollinen postfix-asetusparametrinimi."
-
-#: vmm:34
-#, python-format
-msgid ""
-"Usage: %s SUBCOMMAND OBJECT ARGS*\n"
-"  short long\n"
-"  subcommand               object             args (* = optional)\n"
-msgstr ""
-"Käyttö: %s ALIKOMENTO OBJEKTI ARGUMENTIT*\n"
-"  lyhyt pitkä\n"
-"  alikomento               objekti             argumentti (* = valinnainen)\n"
-
-#: vmm:73 vmm:84 vmm:494
-msgid "Error"
-msgstr "Virhe"
-
-#: vmm:111
+#. TP: used in e.g. 'Domain information' or 'Account information'
+#: VirtualMailManager/cli/subcommands.py:815
 msgid "information"
 msgstr "tiedot"
 
-#: vmm:121
-msgid "Available"
-msgstr "Käytettävissä"
+#. TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
+#: VirtualMailManager/cli/subcommands.py:828
+msgid "Existing"
+msgstr "Olemassa oleva"
 
-#: vmm:124 vmm:223 vmm:229
-msgid "alias domains"
-msgstr "aliasverkkotunnukset"
-
-#: vmm:134 vmm:145 vmm:169
+#: VirtualMailManager/cli/subcommands.py:841
+#: VirtualMailManager/cli/subcommands.py:883
 msgid "\tNone"
 msgstr "\tEi mitään"
 
-#: vmm:138
+#: VirtualMailManager/cli/subcommands.py:846
 msgid "Alias information"
 msgstr "Aliastiedot"
 
-#: vmm:140
+#: VirtualMailManager/cli/subcommands.py:848
 #, python-format
 msgid "\tMail for %s will be redirected to:"
 msgstr "\tSähköposti %s ohjataan edelleen osoitteeseen:"
 
-#: vmm:149
+#: VirtualMailManager/cli/subcommands.py:855
 msgid "Relocated information"
 msgstr "Sijaintitiedot"
 
 #  Alla "addr" ja "dest" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: vmm:151
+#: VirtualMailManager/cli/subcommands.py:857
 #, python-format
-msgid "\tUser “%(addr)s” has moved to “%(dest)s”"
-msgstr "\tKäyttäjä “%(addr)s” on siirtynyt kohteeseen “%(dest)s”"
+msgid "\tUser '%(addr)s' has moved to '%(dest)s'"
+msgstr "\tKäyttäjä ’%(addr)s’ on siirretty kohteeseen ’%(dest)s’"
 
-#: vmm:164
-msgid "Available domains"
-msgstr "Käytettävissä olevat verkkotunnukset"
-
-#: vmm:166
+#: VirtualMailManager/cli/subcommands.py:872
 msgid "Matching domains"
 msgstr "Täsmäävät verkkotunnukset"
 
-#: vmm:180
+#: VirtualMailManager/cli/subcommands.py:874
+msgid "Existing domains"
+msgstr "Olemassaolevat verkkotunnukset"
+
+#: VirtualMailManager/cli/subcommands.py:889
 msgid "Alias domain information"
 msgstr "Aliasverkkotunnustiedot"
 
 #  Alla "alias" ja "domain" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
-#: vmm:186
+#: VirtualMailManager/cli/subcommands.py:894
 #, python-format
 msgid ""
 "\tThe alias domain %(alias)s belongs to:\n"
@@ -529,116 +804,474 @@
 "\tAliasverkkotunnus %(alias)s kuuluu kohteeseen:\n"
 "\t    * %(domain)s"
 
-#: vmm:197 vmm:205 vmm:213
-msgid "Missing domain name."
-msgstr "Puuttuva verkkotunnusnimi."
+#: VirtualMailManager/common.py:63
+#, python-format
+msgid "No such file: '%s'"
+msgstr "Ei löydy tiedostoa: ’%s’"
+
+#: VirtualMailManager/common.py:66
+#, python-format
+msgid "File is not executable: '%s'"
+msgstr "Tiedosto ei ole suoritettava tiedosto: ’%s’"
+
+#: VirtualMailManager/common.py:82
+msgid "GiB"
+msgstr "gibitavua"
+
+#: VirtualMailManager/common.py:82
+msgid "TiB"
+msgstr "tebitavua"
+
+#: VirtualMailManager/common.py:83
+msgid "KiB"
+msgstr "kibitavua"
+
+#: VirtualMailManager/common.py:83
+msgid "MiB"
+msgstr "mebitavua"
 
-#: vmm:215 vmm:219
-msgid "Domain"
-msgstr "Verkkotunnus"
+#. TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
+#: VirtualMailManager/common.py:87
+#, python-format
+msgid "%(size)s %(prefix)s"
+msgstr "%(size)s %(prefix)s"
+
+#: VirtualMailManager/config.py:89
+#, python-format
+msgid "Not a boolean: '%s'"
+msgstr "Ei ole boolean-arvo: ’%s’"
 
-#: vmm:221 vmm:230
-msgid "accounts"
-msgstr "tilit"
+#: VirtualMailManager/config.py:127
+#, python-format
+msgid "Bad format: '%s' - expected: section.option"
+msgstr "Väärä muoto: ’%s’ - odotettiin: lohko.valitsin"
+
+#: VirtualMailManager/config.py:380
+#, python-format
+msgid "* Section: %s\n"
+msgstr "* Lohko: %s\n"
+
+#: VirtualMailManager/config.py:390 VirtualMailManager/config.py:398
+#, python-format
+msgid "Check of configuration file %s failed.\n"
+msgstr "Asetustiedoston %s tarkistus epäonnistui.\n"
+
+#: VirtualMailManager/config.py:392
+msgid "Missing options, which have no default value.\n"
+msgstr "Puuttuu valitsimia, joilla ei ole oletusarvoa.\n"
 
-#: vmm:225 vmm:231
-msgid "aliases"
-msgstr "aliakset"
+#: VirtualMailManager/config.py:400 VirtualMailManager/config.py:402
+msgid "Invalid configuration values.\n"
+msgstr "Virheelliset asetusarvot.\n"
+
+#: VirtualMailManager/config.py:441 VirtualMailManager/config.py:525
+#, python-format
+msgid "Not a valid Dovecot version: '%s'"
+msgstr "Ei ole kelvollinen Dovecot-versio: ’%s’"
+
+#: VirtualMailManager/config.py:447 VirtualMailManager/config.py:482
+#, python-format
+msgid "Unsupported database module: '%s'"
+msgstr "Tukematon tietokantamoduuli: ’%s’"
+
+#: VirtualMailManager/config.py:452 VirtualMailManager/config.py:490
+#, python-format
+msgid "Unknown pgsql SSL mode: '%s'"
+msgstr "Tuntematon pgsql SSL -tila: ’%s’"
+
+#: VirtualMailManager/config.py:459 VirtualMailManager/config.py:503
+#: VirtualMailManager/maillocation.py:70
+#, python-format
+msgid "Unsupported mailbox format: '%s'"
+msgstr "Tukematon sähköpostilaatikkomuoto: ’%s’"
 
-#: vmm:227 vmm:232
-msgid "relocated users"
-msgstr "sijoitetut käyttäjät"
+#  Tässä argumentti saattaa olla hakemistonimi tai sitten virheilmoitus.
+#: VirtualMailManager/config.py:475 VirtualMailManager/handler.py:283
+#: VirtualMailManager/handler.py:357 VirtualMailManager/handler.py:362
+#: VirtualMailManager/handler.py:390
+#, python-format
+msgid "No such directory: %s"
+msgstr "Ei löydy hakemistoa: %s"
+
+#: VirtualMailManager/config.py:514
+#, python-format
+msgid "Not a valid size value: '%s'"
+msgstr "Ei ole kelvollinen kokoarvo: ’%s’"
 
-#: vmm:236
-msgid "Missing domain name and new transport."
-msgstr "Puuttuva verkkotunnusnimi ja uusi siirto."
+#: VirtualMailManager/domain.py:78
+#, python-format
+msgid "The domain '%s' is an alias domain."
+msgstr "Verkkotunnus ’%s’ on aliasverkkotunnus."
+
+#: VirtualMailManager/domain.py:108
+#, python-format
+msgid "There are %(account_count)u accounts, %(alias_count)u aliases and %(relocated_count)u relocated users."
+msgstr "On %(account_count)u -tiliä, %(alias_count)u -aliasta ja %(relocated_count)u sijoitettua käyttäjää."
+
+#: VirtualMailManager/domain.py:123
+#, python-format
+msgid "The domain '%s' already exists."
+msgstr "Verkkotunnus ’%s’ on jo olemassa."
+
+#: VirtualMailManager/domain.py:437
+msgid "The domain name is too long"
+msgstr "Verkkotunnusnimi on liian pitkä"
 
-#: vmm:238
-msgid "Missing new transport."
-msgstr "Puuttuva uusi siirto."
+#: VirtualMailManager/domain.py:439
+#, python-format
+msgid "The domain name '%s' is invalid"
+msgstr "Verkkotunnusnimi ’%s’ on virheellinen"
+
+#: VirtualMailManager/emailaddress.py:73
+#, python-format
+msgid "Missing the '@' sign in address: '%s'"
+msgstr "Merkki ’@’ puuttuu sähköpostiosoitteesta: ’%s’."
+
+#: VirtualMailManager/emailaddress.py:76
+#, python-format
+msgid "Too many '@' signs in address: '%s'"
+msgstr "Liian monta ’@’-merkkiä sähköpostiosoitteessa: ’%s’."
+
+#: VirtualMailManager/emailaddress.py:79
+#, python-format
+msgid "Missing local-part in address: '%s'"
+msgstr "Puuttuu paikallisosa osoitteessa: ’%s’"
+
+#: VirtualMailManager/emailaddress.py:82
+#, python-format
+msgid "Missing domain name in address: '%s'"
+msgstr "Puuttuu verkkotunnusnimi osoitteessa: ’%s’"
 
-#: vmm:247 vmm:262
-msgid "Missing alias domain name and target domain name."
-msgstr "Puuttuva aliasverkkotunnusnimi ja kohdeverkkotunnusnimi."
+#: VirtualMailManager/emailaddress.py:145
+#, python-format
+msgid "The local-part '%s' is too long."
+msgstr "Paikallisosa ’%s’ on liian pitkä."
+
+#  Alla "l_part" ja "i_chars" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#: VirtualMailManager/emailaddress.py:150
+#, python-format
+msgid "The local-part '%(l_part)s' contains invalid characters: %(i_chars)s"
+msgstr "Paikallisosa ’%(l_part)s’ sisältää virheellisiä merkkejä: %(i_chars)s"
 
-#: vmm:249 vmm:264
-msgid "Missing target domain name."
-msgstr "Puuttuva kohdeverkkotunnuksen nimi."
+#: VirtualMailManager/ext/postconf.py:84
+#, python-format
+msgid "The value '%s' does not look like a valid postfix configuration parameter name."
+msgstr "Arvo ’%s’ ei näytä olevan kelvollinen postfix-asetusparametrinimi."
+
+#: VirtualMailManager/handler.py:56
+msgid "an account"
+msgstr "tili"
+
+#: VirtualMailManager/handler.py:57
+msgid "an alias"
+msgstr "alias"
+
+#: VirtualMailManager/handler.py:58
+msgid "a relocated user"
+msgstr "sijoitettu käyttäjä"
 
-#: vmm:255 vmm:270
-msgid "Missing alias domain name."
-msgstr "Puuttuva aliasverkkotunnuksen nimi."
+#: VirtualMailManager/handler.py:84
+msgid ""
+"You are not root.\n"
+"\tGood bye!\n"
+msgstr ""
+"Et ole root-käyttäjä.\n"
+"\tNäkemiin!\n"
+
+#: VirtualMailManager/handler.py:104
+#, python-format
+msgid "Could not find '%(cfg_file)s' in: '%(cfg_path)s'"
+msgstr "Ei voiru löytää ’%(cfg_file)s’ kohteessa: ’%(cfg_path)s’"
 
-#: vmm:276 vmm:285 vmm:293 vmm:323 vmm:331 vmm:339
-msgid "Missing e-mail address."
-msgstr "Puuttuva sähköpostiosoite."
+#  Alla "perms" ja "file" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla. Lainausmerkit jätettiin paikallistamatta, koska ne saattavat olla skriptin toiminnan kannalta kriittisiä.
+#: VirtualMailManager/handler.py:115
+#, python-format
+msgid ""
+"wrong permissions for '%(file)s': %(perms)s\n"
+"`chmod 0600 %(file)s` would be great."
+msgstr ""
+"korjaa käyttöoikeudet tiedostolle ’%(file)s’: %(perms)s\n"
+"’chmod 0600 %(file)s’ olisi erinomainen."
+
+#: VirtualMailManager/handler.py:135
+#, python-format
+msgid ""
+"'%(path)s' is not a directory.\n"
+"(%(cfg_file)s: section 'misc', option 'base_directory')"
+msgstr ""
+"’%(path)s’ ei ole hakemisto.\n"
+"(%(cfg_file)s: lohko ’misc’, valitsin ’base_directory’)"
 
-#: vmm:301
-msgid "alias addresses"
-msgstr "aliasosoitteet"
+#: VirtualMailManager/handler.py:144
+#, python-format
+msgid ""
+"\n"
+"(%(cfg_file)s: section 'bin', option '%(option)s')"
+msgstr ""
+"\n"
+"(%(cfg_file)s: lohko ’bin’, valitsin ’%(option)s’)"
+
+#: VirtualMailManager/handler.py:158 VirtualMailManager/handler.py:165
+#, python-format
+msgid "Unable to import database module '%s'."
+msgstr "Ei kyetä tuomaan tietokantamoduulia ’%s’."
 
-#  Tässä saattaa olla kirjoitusvirhe. Pitäisi ehkä olla "user's name".
-#: vmm:307
-msgid "Missing e-mail address and users name."
-msgstr "Puuttuva sähköpostiosoite ja käyttäjien nimi."
+#. TP: %(a_type)s will be one of: 'an account', 'an alias' or
+#. 'a relocated user'
+#: VirtualMailManager/handler.py:244
+#, python-format
+msgid "There is already %(a_type)s with the address '%(address)s'."
+msgstr "On jo %(a_type)s osoitteessa ’%(address)s’."
+
+#: VirtualMailManager/handler.py:297
+#, python-format
+msgid "'%s' is not a directory."
+msgstr "’%s’ ei ole hakemisto."
+
+#: VirtualMailManager/handler.py:300
+#, python-format
+msgid "The file/directory '%s' already exists."
+msgstr "Tiedosto/hakemisto ’%s’ on jo olemassa."
+
+#: VirtualMailManager/handler.py:329
+msgid "Skipped mailbox folders:"
+msgstr "Ohitettiin sähköpostilaatikkokansiot:"
 
-#  Tässä saattaa olla kirjoitusvirhe. Pitäisi ehkä olla "user's name".
-#: vmm:309
-msgid "Missing users name."
-msgstr "Puuttuva käyttäjien nimi."
+#: VirtualMailManager/handler.py:349
+#, python-format
+msgid "UID '%(uid)u' and/or GID '%(gid)u' are less than %(min_uid)u/%(min_gid)u."
+msgstr "Käyttäjätunniste ’%(uid)u’ ja/tai käyttäjätunniste ’%(gid)u’ ovat pienempiä kuin %(min_uid)u/%(min_gid)u."
+
+#: VirtualMailManager/handler.py:354 VirtualMailManager/handler.py:387
+#, python-format
+msgid "Found \"..\" in domain directory path: %s"
+msgstr "Löytyi ”..” verkkotunnuksen hakemistopolussa: %s"
+
+#: VirtualMailManager/handler.py:367
+msgid "Detected owner/group mismatch in home directory."
+msgstr "Havaittu omistaja-/ryhmätäsmäämättömyys kotihakemistossa."
+
+#: VirtualMailManager/handler.py:383
+#, python-format
+msgid "GID '%(gid)u' is less than '%(min_gid)u'."
+msgstr "Käyttäjätunniste ’%(gid)u’ on pienempi kuin ’%(min_gid)u’."
+
+#: VirtualMailManager/handler.py:394
+#, python-format
+msgid "Detected group mismatch in domain directory: %s"
+msgstr "Havaittu ryhmätäsmäämättömyys verkkotunnushakemistossa: %s"
 
-#: vmm:315
-msgid "Missing e-mail address and transport."
-msgstr "Puuttuva sähköpostiosoite ja siirto."
+#: VirtualMailManager/handler.py:470 VirtualMailManager/handler.py:748
+#, python-format
+msgid "Unknown service: '%s'"
+msgstr "Tuntematon palvelu: ’%s’"
+
+#: VirtualMailManager/handler.py:587
+#, python-format
+msgid "The pattern '%s' contains invalid characters."
+msgstr "Säännöllinen lauseke ’%s’ sisältää virheellisiä merkkejä."
+
+#: VirtualMailManager/handler.py:614
+msgid "Ignored destination addresses:"
+msgstr "Ohitetut kohdeosoitteet:"
 
-#: vmm:317
-msgid "Missing transport."
-msgstr "Puuttuva siirto."
+#: VirtualMailManager/handler.py:619 VirtualMailManager/handler.py:769
+#, python-format
+msgid "The destination account/alias '%s' does not exist."
+msgstr "Kohdetili/-alias ’%s’ ei ole olemassa."
+
+#  Alla "directory" ja "reason" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#: VirtualMailManager/handler.py:641
+#, python-format
+msgid ""
+"The account has been successfully deleted from the database.\n"
+"    But an error occurred while deleting the following directory:\n"
+"    '%(directory)s'\n"
+"    Reason: %(reason)s"
+msgstr ""
+"Tili on poistettu onnistuneesti tietokannasta.\n"
+"    Mutta tapahtui virhe, kun poistettiin seuraavaa hakemistoa:\n"
+"    ’%(directory)s’\n"
+"    Syy: %(reason)s"
 
-#: vmm:348
-msgid "Missing alias address and destination."
-msgstr "Puuttuva aliasosoite ja -kohde."
+#: VirtualMailManager/handler.py:712
+#, python-format
+msgid "Could not accept name: '%s'"
+msgstr "Ei voitu hyväksyä nimeä: ’%s’"
+
+#: VirtualMailManager/handler.py:735
+#, python-format
+msgid "Could not accept transport: '%s'"
+msgstr "Ei voitu hyväksyä siirtoa: ’%s’"
 
-#: vmm:350 vmm:373
-msgid "Missing destination address."
-msgstr "Puuttuva kohdeosoite."
+#: VirtualMailManager/handler.py:779 VirtualMailManager/relocated.py:98
+#: VirtualMailManager/relocated.py:105
+#, python-format
+msgid "The relocated user '%s' does not exist."
+msgstr "Sijoitettua käyttäjää ’%s’ ei ole olemassa."
+
+#: VirtualMailManager/mailbox.py:260
+#, python-format
+msgid "Failed to create mailboxes: %r\n"
+msgstr "Sähköpostilaatikoiden luominen epäonnistui: %r\n"
+
+#: VirtualMailManager/maillocation.py:74
+msgid "Empty directory name"
+msgstr "Tyhjä hakemistonimi"
+
+#: VirtualMailManager/maillocation.py:76
+#, python-format
+msgid "Directory name is too long: '%s'"
+msgstr "Verkkotunnusnimi on liian pitkä: ’%s’"
 
-#: vmm:356 vmm:362
-msgid "Missing alias address"
-msgstr "Puuttuva aliasosoite"
+#: VirtualMailManager/password.py:388
+#, python-format
+msgid "Unsupported password scheme: '%s'"
+msgstr "Tukematon salasanakaava: ’%s’"
+
+#: VirtualMailManager/password.py:391
+#, python-format
+msgid "The password scheme '%(scheme)s' requires Dovecot >= v%(version)s."
+msgstr "Salasanakaava '%(scheme)s' vaatii Dovecot >= v%(version)s."
+
+#: VirtualMailManager/password.py:397
+msgid "Encoding suffixes for password schemes require Dovecot >= v1.1.alpha1."
+msgstr "Koodausloppuliitteet salasanakaavoille vaativat Dovecot >= v1.1.alpha1."
 
-#: vmm:371
-msgid "Missing relocated address and destination."
-msgstr "Puuttuva sijoitusosoite ja -kohde."
+#: VirtualMailManager/password.py:400
+#, python-format
+msgid "Unsupported password encoding: '%s'"
+msgstr "Tukematon salasanakoodaus: ’%s’"
+
+#: VirtualMailManager/relocated.py:71
+msgid "Address and destination are identical."
+msgstr "Osoite ja kohde ovat identtisiä."
+
+#: VirtualMailManager/relocated.py:75
+#, python-format
+msgid "The relocated user '%s' already exists."
+msgstr "Sijoitettu käyttäjä ’%s’ on jo olemassa."
+
+#~ msgid "There is already a relocated user with the address “%s”."
+#~ msgstr "On jo sijoitettu käyttäjä osoitteella “%s”."
 
-#: vmm:379 vmm:387
-msgid "Missing relocated address"
-msgstr "Puuttuva sijoitusosoite"
+#~ msgid "There is already an account with address “%s”."
+#~ msgstr "On jo olemassa tili osoitteella “%s”."
+
+#~ msgid "No destination address for alias denoted."
+#~ msgstr "Aliakselle ei ole osoitettu kohdeosoitetta."
+
+#  Alla "a" ja "d" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#~ msgid "The alias “%(a)s” with destination “%(d)s” already exists."
+#~ msgstr "Alias “%(a)s” kohteella “%(d)s” on jo olemassa."
+
+#  Alla "a" ja "d" ovat muuttujia, jotka korvautuvat suorituksen yhteydessä koodilla.
+#~ msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
+#~ msgstr "Alias “%(a)s” kohteella “%(d)s” ei ole olemassa."
 
-#: vmm:393
-msgid "Missing userid"
-msgstr "Puuttuva käyttäjätunniste"
+#~ msgid "missing options in section %s:\n"
+#~ msgstr "puuttuvia valitsimia lohkossa %s:\n"
+
+#~ msgid "There are accounts and aliases."
+#~ msgstr "On tilejä ja aliaksia."
+
+#~ msgid "There are accounts."
+#~ msgstr "Tilejä on."
+
+#~ msgid "There are aliases."
+#~ msgstr "Aliaksia on."
+
+#~ msgid "“%s” looks not like an e-mail address."
+#~ msgstr "“%s” ei näytä sähköpostiosoitteelta."
 
-#: vmm:406
-msgid "Warnings:"
-msgstr "Varoitukset:"
+#~ msgid "No localpart specified."
+#~ msgstr "Paikallisosaa ei ole määritelty."
+
+#~ msgid "Either mid or maillocation must be specified."
+#~ msgstr "Joko mid-tunniste tai sähköpostisijainti on määriteltävä."
+
+#~ msgid "mid must be an int/long."
+#~ msgstr "tunnisteen mid on oltava tyypiltään int/long."
 
-#: vmm:412
-msgid "from"
-msgstr "kohteesta"
+#~ msgid ""
+#~ "Invalid folder name “%s”, it may consist only of\n"
+#~ "1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
+#~ msgstr ""
+#~ "Virheellinen kansionimi “%s”, se voi sisältää vain\n"
+#~ "1 - 20 yhden tavun mittaisia merkkejä (A-Z, a-z, 0-9 ja _)."
+
+#~ msgid "Unknown mid specified."
+#~ msgstr "Tuntematon mid-määritelty."
+
+#~ msgid "No destination address for relocated user denoted."
+#~ msgstr "Sijoitetulle käyttäjälle ei ole osoitettu kohdeosoitetta."
+
+#~ msgid "tid must be an int/long."
+#~ msgstr "tunnisteen tid on oltava tyypiltään int/long."
+
+#~ msgid "Unknown tid specified."
+#~ msgstr "Tuntematon tid-siirtotunniste määritelty."
+
+#~ msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
+#~ msgstr "Tiedostoa “vmm.cfg” ei löytynyt hakemistoista: /root:/usr/local/etc:/etc"
 
-#: vmm:412
-msgid "version"
-msgstr "versio"
+#  Alla "options" on muuttuja, joka korvautuu suorituksen yhteydessä koodilla.
+#~ msgid ""
+#~ "“%(binary)s” doesn't exists.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#~ msgstr ""
+#~ "“%(binary)s” ei ole olemassa.\n"
+#~ "(vmm.cfg: lohko ”bin”, valitsin ”%(option)s”)"
 
-#: vmm:414
-msgid "on"
-msgstr "kohteessa"
+#  Alla "options" on muuttuja, joka korvautuu suorituksen yhteydessä koodilla.
+#~ msgid ""
+#~ "“%(binary)s” is not executable.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#~ msgstr ""
+#~ "“%(binary)s” ei ole suoritettava tiedosto.\n"
+#~ "(vmm.cfg: lohko ”bin”, valitsin ”%(option)s”)"
+
+#~ msgid "FATAL: \"..\" in domain directory path detected."
+#~ msgstr "VAKAVA VIRHE: havaittu ”..” verkkotunnushakemistopolkussa."
+
+#  Tässä on ilmeisesti kirjoitusvirhe (Configurtion -> Configuration).
+#~ msgid ""
+#~ "Configurtion error: \"%s\"\n"
+#~ "(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
+#~ msgstr ""
+#~ "Asetusvirhe: ”%s”\n"
+#~ "(lohkossa ”connfig”, valitsin ”done”) katso myös: vmm.cfg(5)\n"
 
-#: vmm:488
-msgid "Unknown subcommand"
-msgstr "Tuntematon alikomento"
+#~ msgid ""
+#~ "The keyword “detailed” is deprecated and will be removed in a future release.\n"
+#~ "   Please use the keyword “full” to get full details."
+#~ msgstr ""
+#~ "Avainsana “detailed” on vanhentunut ja poistetaan tulevassa julkaisussa.\n"
+#~ "   Saat kaikki yksityiskohdat käyttämällä avainsanaa “full”."
+
+#~ msgid "Account doesn't exists"
+#~ msgstr "Tiliä ei ole"
 
-#: vmm:491
-msgid "Ouch"
-msgstr "Auh"
+#~ msgid ""
+#~ "The service name “managesieve” is deprecated and will be removed\n"
+#~ "   in a future release.\n"
+#~ "   Please use the service name “sieve” instead."
+#~ msgstr ""
+#~ "Palvelunimi “managesieve” on vanhentunut ja poistetaan\n"
+#~ "   tulevassa julkaisussa.\n"
+#~ "   Käytä sen sijaan palvelunimeä “sieve”."
+
+#~ msgid ""
+#~ "Usage: %s SUBCOMMAND OBJECT ARGS*\n"
+#~ "  short long\n"
+#~ "  subcommand               object             args (* = optional)\n"
+#~ msgstr ""
+#~ "Käyttö: %s ALIKOMENTO OBJEKTI ARGUMENTIT*\n"
+#~ "  lyhyt pitkä\n"
+#~ "  alikomento               objekti             argumentti (* = valinnainen)\n"
+
+#~ msgid "Available"
+#~ msgstr "Käytettävissä"
+
+#~ msgid "Available domains"
+#~ msgstr "Käytettävissä olevat verkkotunnukset"
--- a/po/fr.po	Mon Nov 07 03:22:15 2011 +0000
+++ b/po/fr.po	Thu Jun 28 19:26:50 2012 +0000
@@ -1,510 +1,819 @@
-# traduction franaise
+# traduction française
 # This file is distributed under the same license as the vmm package.
 # Copyright (C) 2009 Free Software Foundation, Inc.
 # Dimitri Duc <dimitri.duc@gmail.com>, 2009.
 msgid ""
 msgstr ""
-"Project-Id-Version: vmm 0.5.2\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-25 06:07+0200\n"
-"PO-Revision-Date: 2009-09-02 14:59+0100\n"
+"Project-Id-Version: vmm 0.6.0\n"
+"Report-Msgid-Bugs-To: user+vmm/tp@localhost.localdomain.org\n"
+"POT-Creation-Date: 2011-11-07 05:20+0100\n"
+"PO-Revision-Date: 2012-01-01 14:59+0100\n"
 "Last-Translator: Dimitri Duc <dimitri.duc@gmail.com>\n"
 "Language-Team: French <traduc@traduc.org>\n"
+"Language: fr\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "X-Poedit-Language: French\n"
 "X-Poedit-Country: FRANCE\n"
 
-#: VirtualMailManager/Account.py:36 VirtualMailManager/Relocated.py:44
-#, python-format
-msgid "There is already an alias with the address “%s”."
-msgstr "Il y a déjà un alias avec l'adresse “%s”."
-
-#: VirtualMailManager/Account.py:41 VirtualMailManager/Alias.py:45
-#, python-format
-msgid "There is already a relocated user with the address “%s”."
-msgstr "Il y a déjà un utilisateur relocalisé avec cette adresse “%s”."
-
-#: VirtualMailManager/Account.py:61 VirtualMailManager/Alias.py:61
-#: VirtualMailManager/Domain.py:163 VirtualMailManager/Domain.py:189
-#: VirtualMailManager/Domain.py:220 VirtualMailManager/Relocated.py:60
-#, python-format
-msgid "The domain “%s” doesn't exist yet."
+#. 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
+#: VirtualMailManager/account.py:58 VirtualMailManager/alias.py:35
+#: VirtualMailManager/domain.py:120 VirtualMailManager/relocated.py:38
+#, fuzzy, python-format
+msgid "The domain '%s' does not exist."
 msgstr "Le domaine “%s” n'existe pas encore."
 
-#: VirtualMailManager/Account.py:80
+#: VirtualMailManager/account.py:106
 #, python-format
-msgid "Unknown service “%s”."
-msgstr "Service “%s” inconnu."
+msgid "The mailbox format '%(mbfmt)s' requires Dovecot >= v%(version)s."
+msgstr ""
 
-#: VirtualMailManager/Account.py:83 VirtualMailManager/Account.py:150
-#: VirtualMailManager/Account.py:178 VirtualMailManager/Account.py:212
+#: VirtualMailManager/account.py:113 VirtualMailManager/account.py:305
 #, python-format
-msgid "The account “%s” doesn't exists."
+msgid "Invalid transport '%(transport)s' for mailbox format '%(mbfmt)s'."
+msgstr ""
+
+#: VirtualMailManager/account.py:153 VirtualMailManager/cli/handler.py:93
+#: VirtualMailManager/handler.py:628 VirtualMailManager/handler.py:679
+#: VirtualMailManager/handler.py:705 VirtualMailManager/handler.py:716
+#: VirtualMailManager/handler.py:727 VirtualMailManager/handler.py:739
+#: VirtualMailManager/handler.py:753
+#, fuzzy, python-format
+msgid "The account '%s' does not exist."
 msgstr "Le compte “%s” n'existe pas."
 
-#: VirtualMailManager/Account.py:145
-#, python-format
-msgid "The account “%s” already exists."
+#: VirtualMailManager/account.py:204 VirtualMailManager/account.py:214
+#: VirtualMailManager/cli/handler.py:77 VirtualMailManager/handler.py:596
+#, fuzzy, python-format
+msgid "The account '%s' already exists."
 msgstr "Le compte  “%s” existe déjà."
 
-#: VirtualMailManager/Account.py:186
+#: VirtualMailManager/account.py:207 VirtualMailManager/handler.py:701
+#, python-format
+msgid "Could not accept password: '%s'"
+msgstr ""
+
+#: VirtualMailManager/account.py:217
+#, python-format
+msgid "No password set for account: '%s'"
+msgstr ""
+
+#: VirtualMailManager/account.py:245
+#, python-format
+msgid "Unknown field: '%s'"
+msgstr ""
+
+#: VirtualMailManager/account.py:267 VirtualMailManager/domain.py:292
+msgid "PostgreSQL-based dictionary quota requires Dovecot >= v1.1.2."
+msgstr ""
+
+#. TP: A service (e.g. pop3 or imap) may be enabled/usable or
+#. disabled/unusable for a user.
+#: VirtualMailManager/account.py:332
+msgid "disabled"
+msgstr "désactiver"
+
+#: VirtualMailManager/account.py:332
 msgid "enabled"
 msgstr "activer"
 
-#: VirtualMailManager/Account.py:188
-msgid "disabled"
-msgstr "désactiver"
+#: VirtualMailManager/account.py:343
+#, python-format
+msgid "Could not fetch information for account: '%s'"
+msgstr ""
 
-#: VirtualMailManager/Account.py:233
-#, python-format
-msgid "There are %(count)d aliases with the destination address “%(address)s”."
+#: VirtualMailManager/account.py:387
+#, fuzzy, python-format
+msgid "There are %(count)d aliases with the destination address '%(address)s'."
 msgstr "Il y a %(count)d alias ayant comme adresse de destination “%(address)s”."
 
-#: VirtualMailManager/Account.py:241
-msgid "uid must be an int/long."
+#: VirtualMailManager/account.py:416
+#, fuzzy
+msgid "UID must be an int/long."
 msgstr "L'identifiant utilisateur doit être de type entier long."
 
-#: VirtualMailManager/Account.py:243
-msgid "uid must be greater than 0."
+#: VirtualMailManager/account.py:418
+#, fuzzy
+msgid "UID must be greater than 0."
 msgstr "L'idenfiant utilisateur doit être plus grand que 0."
 
-#: VirtualMailManager/Account.py:251
-#, python-format
-msgid "There is no account with the UID “%d”."
+#: VirtualMailManager/account.py:427
+#, fuzzy, python-format
+msgid "There is no account with the UID: '%d'"
 msgstr "Il n'y a pas de compte avec l'identifiant utilisateur “%d”."
 
-#: VirtualMailManager/Alias.py:30 VirtualMailManager/Relocated.py:30
-msgid "Address and destination are identical."
-msgstr "L'adresse et la destination sont identique. "
-
-#: VirtualMailManager/Alias.py:40 VirtualMailManager/Relocated.py:39
-#, python-format
-msgid "There is already an account with address “%s”."
-msgstr "Il y a déjà un compte avec l'adresse  “%s”."
-
-#: VirtualMailManager/Alias.py:71
-#, python-format
+#: VirtualMailManager/alias.py:60
+#, fuzzy, python-format
 msgid ""
-"Can't add new destination to alias “%(address)s”.\n"
-"Currently this alias expands into %(count)i recipients.\n"
-"One more destination will render this alias unusable.\n"
-"Hint: Increase Postfix' virtual_alias_expansion_limit\n"
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"Currently this alias expands into %(count)i/%(limit)i recipients.\n"
+"%(count_new)i additional destination(s) will render this alias unusable.\n"
+"Hint: Increase Postfix' virtual_alias_expansion_limit"
 msgstr ""
 "Impossible d'ajouter une nouvelle destination à l'alias “%(address)s”.\n"
 "Actuellement cet alias s'étend sur %(count)i destinataires.\n"
 "Une destination de plus rendra cet alias inutilisable.\n"
 "Conseil: Augmenter 'virtual_alias_expansion_limit' dans Postfix\n"
 
-#: VirtualMailManager/Alias.py:80
-msgid "No destination address for alias denoted."
-msgstr "Pas de destination à l'adresse de l'alias relevé."
-
-#: VirtualMailManager/Alias.py:91
+#: VirtualMailManager/alias.py:67
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” already exists."
-msgstr "L'alias “%(a)s” avec la destination “%(d)s” existe déjà."
+msgid ""
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"This alias already exceeds its expansion limit (%(count)i/%(limit)i).\n"
+"So its unusable, all messages addressed to this alias will be bounced.\n"
+"Hint: Delete some destination addresses."
+msgstr ""
 
-#: VirtualMailManager/Alias.py:106 VirtualMailManager/Alias.py:123
-#, python-format
-msgid "The alias “%s” doesn't exists."
+#: VirtualMailManager/alias.py:142 VirtualMailManager/alias.py:154
+#: VirtualMailManager/alias.py:161 VirtualMailManager/handler.py:657
+#, fuzzy, python-format
+msgid "The alias '%s' does not exist."
 msgstr "L'alias “%s”  n'existe pas."
 
-#: VirtualMailManager/Alias.py:125
+#: VirtualMailManager/alias.py:145
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
-msgstr "L'alias “%(a)s” avec la destination “%(d)s”  n'existe pas."
+msgid "The address '%(addr)s' is not a destination of the alias '%(alias)s'."
+msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:32
-#, python-format
-msgid "The domain “%s” is a primary domain."
+#: VirtualMailManager/aliasdomain.py:50
+#, fuzzy, python-format
+msgid "The domain '%s' is a primary domain."
 msgstr "Le domaine “%s” est le domaine principale."
 
-#: VirtualMailManager/AliasDomain.py:37
-#, python-format
-msgid "The alias domain “%s” already exists."
+#: VirtualMailManager/aliasdomain.py:69
+#, fuzzy, python-format
+msgid "The alias domain '%s' already exists."
 msgstr "L'alias de domane “%s” existe déjà."
 
-#: VirtualMailManager/AliasDomain.py:40 VirtualMailManager/AliasDomain.py:70
-msgid "No destination domain for alias domain denoted."
+#: VirtualMailManager/aliasdomain.py:72 VirtualMailManager/aliasdomain.py:106
+#, fuzzy
+msgid "No destination domain set for the alias domain."
 msgstr "Pas de destination pour l'alias de domaine relevé."
 
-#: VirtualMailManager/AliasDomain.py:43 VirtualMailManager/AliasDomain.py:73
-#, python-format
-msgid "The target domain “%s” doesn't exist yet."
+#: VirtualMailManager/aliasdomain.py:75 VirtualMailManager/aliasdomain.py:109
+#, fuzzy, python-format
+msgid "The target domain '%s' does not exist."
 msgstr "La domaine ciblé “%s” n'existe pas encore."
 
-#: VirtualMailManager/AliasDomain.py:62
-#, python-format
-msgid "There is no primary domain for the alias domain “%s”."
+#: VirtualMailManager/aliasdomain.py:88 VirtualMailManager/aliasdomain.py:112
+#: VirtualMailManager/aliasdomain.py:133
+#, fuzzy, python-format
+msgid "The alias domain '%s' does not exist."
+msgstr "L'alias de domaine “%s” n'existe pas encore."
+
+#: VirtualMailManager/aliasdomain.py:98
+#, fuzzy, python-format
+msgid "There is no primary domain for the alias domain '%s'."
 msgstr "Il n'y a pas de domaine principale pour l'alias de domaine “%s”."
 
-#: VirtualMailManager/AliasDomain.py:65 VirtualMailManager/AliasDomain.py:76
-#: VirtualMailManager/AliasDomain.py:99
-#, python-format
-msgid "The alias domain “%s” doesn't exist yet."
-msgstr "L'alias de domaine “%s” n'existe pas encore."
-
-#: VirtualMailManager/AliasDomain.py:79
-#, python-format
-msgid "The alias domain “%(alias)s” is already assigned to the domain “%(domain)s”."
+#: VirtualMailManager/aliasdomain.py:115
+#, fuzzy, python-format
+msgid "The alias domain '%(alias)s' is already assigned to the domain '%(domain)s'."
 msgstr "L'alias de domaine “%(alias)s” est déjà assigné au domaine “%(domain)s”."
 
-#: VirtualMailManager/Config.py:102 VirtualMailManager/Config.py:137
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:78
+msgid "Enter new password: "
+msgstr "Entrer un nouveau mot de passe:"
+
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:80
+msgid "Retype new password: "
+msgstr "Re-écrire le mot de passe:"
+
+#: VirtualMailManager/cli/__init__.py:85 VirtualMailManager/cli/config.py:53
+msgid "Too many failures - try again later."
+msgstr ""
+
+#: VirtualMailManager/cli/__init__.py:91
+#, fuzzy
+msgid "Sorry, passwords do not match."
+msgstr "Le mot de passe ne correspondent pas"
+
+#: VirtualMailManager/cli/__init__.py:95
+#, fuzzy
+msgid "Sorry, empty passwords are not permitted."
+msgstr "Les mots de passe vides ne sont pas autorisées"
+
+#: VirtualMailManager/cli/config.py:32
+#, fuzzy, python-format
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
+msgstr "Entrer une nouvelle valeur pour l'option %(opt)s [%(val)s]: "
+
+#: VirtualMailManager/cli/config.py:36
 #, python-format
 msgid "Using configuration file: %s\n"
 msgstr "Utilisation du fichier configuration: %s\n"
 
-#: VirtualMailManager/Config.py:106
-#, python-format
-msgid "missing section: %s\n"
-msgstr "absence de la section: %s\n"
-
-#: VirtualMailManager/Config.py:108
-#, python-format
-msgid "missing options in section %s:\n"
-msgstr "absence des options dans la section %s:\n"
-
-#: VirtualMailManager/Config.py:140
-#, python-format
-msgid "* Config section: “%s”"
+#: VirtualMailManager/cli/config.py:38
+#, fuzzy, python-format
+msgid "* Configuration section: '%s'"
 msgstr "* Configure la section: “%s”"
 
-#: VirtualMailManager/Config.py:143
-#, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
-msgstr "Entrer une nouvelle valeur pour l'option %(opt)s [%(val)s]: "
+#: VirtualMailManager/cli/config.py:50
+#, fuzzy, python-format
+msgid "Warning: %s"
+msgstr "Avertissements:"
+
+#: VirtualMailManager/cli/handler.py:66
+#, fuzzy, python-format
+msgid "Invalid section: '%s'"
+msgstr "Section invalide:  “%s”"
 
-#: VirtualMailManager/Domain.py:39
-#, python-format
-msgid "The domain “%s” is an alias domain."
-msgstr "Le domaine “%s” est un alias de domaine."
+#: VirtualMailManager/cli/main.py:32 VirtualMailManager/cli/main.py:65
+#: VirtualMailManager/cli/main.py:68 VirtualMailManager/cli/subcommands.py:629
+#: VirtualMailManager/cli/subcommands.py:649
+#, fuzzy, python-format
+msgid "Error: %s"
+msgstr "Erreur"
 
-#: VirtualMailManager/Domain.py:124
-msgid "There are accounts and aliases."
-msgstr "Il y a des comptes et des alias."
+#: VirtualMailManager/cli/main.py:41
+msgid "You must specify a subcommand at least."
+msgstr ""
+
+#: VirtualMailManager/cli/main.py:53
+#, fuzzy, python-format
+msgid "Unknown subcommand: '%s'"
+msgstr "Sous-commande inconue."
 
-#: VirtualMailManager/Domain.py:127
-msgid "There are accounts."
-msgstr "Il y a des comptes."
+#. TP: We have to cry, because root has killed/interrupted vmm
+#. with Ctrl+C or Ctrl+D.
+#: VirtualMailManager/cli/main.py:62
+#, fuzzy
+msgid "Ouch!"
+msgstr "Aie"
 
-#: VirtualMailManager/Domain.py:130
-msgid "There are aliases."
-msgstr "Il y a des alias."
+#: VirtualMailManager/cli/main.py:71
+#, python-format
+msgid "Error: Unknown section: '%s'"
+msgstr ""
 
-#: VirtualMailManager/Domain.py:145
+#: VirtualMailManager/cli/main.py:74
 #, python-format
-msgid "The domain “%s” already exists."
-msgstr "Le domaine “%s” existe déjà."
+msgid "Error: No option '%(option)s' in section: '%(section)s'"
+msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:46
+#: VirtualMailManager/cli/main.py:77
+msgid "Warnings:"
+msgstr "Avertissements:"
+
+#: VirtualMailManager/cli/subcommands.py:78
 #, python-format
-msgid "Missing '@' sign in e-mail address “%s”."
-msgstr "Absence du signe '@' dans l'adresse e-mail “%s”. "
+msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
+msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:49
-#, python-format
-msgid "“%s” looks not like an e-mail address."
-msgstr "“%s” ne ressemble pas à une adresse e-mail."
+#: VirtualMailManager/cli/subcommands.py:92
+msgid "Missing alias address and destination."
+msgstr "Absence de l'adresse alias et de la destination."
+
+#: VirtualMailManager/cli/subcommands.py:95
+#: VirtualMailManager/cli/subcommands.py:453
+msgid "Missing destination address."
+msgstr "Absence de l'adresse destination."
 
-#: VirtualMailManager/EmailAddress.py:54
-#, python-format
-msgid "Missing domain name after “%s@”."
-msgstr "Absence d'un nom de domaine après “%s@”"
+#: VirtualMailManager/cli/subcommands.py:102
+#: VirtualMailManager/cli/subcommands.py:112
+#, fuzzy
+msgid "Missing alias address."
+msgstr "Absence de l'adresse alias"
 
-#: VirtualMailManager/EmailAddress.py:66
-msgid "No localpart specified."
-msgstr "Pas de partie locale spécifié."
+#: VirtualMailManager/cli/subcommands.py:134
+#: VirtualMailManager/cli/subcommands.py:168
+#, fuzzy
+msgid "Missing alias domain name and destination domain name."
+msgstr "Absence du nom de l'alias de domaine et du nom cible de domaine"
+
+#: VirtualMailManager/cli/subcommands.py:137
+#: VirtualMailManager/cli/subcommands.py:171
+#, fuzzy
+msgid "Missing destination domain name."
+msgstr "Asbence du nom cible de domaine"
 
-#: VirtualMailManager/EmailAddress.py:69
-#, python-format
-msgid "The local part “%s” is too long"
-msgstr "La partie locale “%s” est trop longue."
+#: VirtualMailManager/cli/subcommands.py:145
+#: VirtualMailManager/cli/subcommands.py:152
+msgid "Missing alias domain name."
+msgstr "Absence du nom de l'alias de domaine."
 
-#: VirtualMailManager/EmailAddress.py:76
-#, python-format
-msgid "The local part “%(lpart)s” contains invalid characters: %(ichrs)s"
-msgstr "La partie locale “%(lpart)s” contient des caractères invalide: %(ichrs)s"
+#: VirtualMailManager/cli/subcommands.py:179
+#, fuzzy
+msgid "Missing option name."
+msgstr "Absence de nom de domaine."
 
-#: VirtualMailManager/MailLocation.py:32
-msgid "Either mid or maillocation must be specified."
-msgstr "Soit l'identifiant de l'emplacement e-mail ou l'emplacement e-mail doit être spécifié. "
+#: VirtualMailManager/cli/subcommands.py:195
+#, fuzzy
+msgid "Missing option and new value."
+msgstr "Absence de nom de domaine et de nouveau transport."
 
-#: VirtualMailManager/MailLocation.py:38
-msgid "mid must be an int/long."
-msgstr "L'identiant de l'emplcement e-mail doit être un entier long."
+#: VirtualMailManager/cli/subcommands.py:197
+#, fuzzy
+msgid "Missing new configuration value."
+msgstr "Utilisation du fichier configuration: %s\n"
 
-#: VirtualMailManager/MailLocation.py:46
+#: VirtualMailManager/cli/subcommands.py:213
+#: VirtualMailManager/cli/subcommands.py:229
+#: VirtualMailManager/cli/subcommands.py:242
+#: VirtualMailManager/cli/subcommands.py:331
+msgid "Missing domain name."
+msgstr "Absence de nom de domaine."
+
+#: VirtualMailManager/cli/subcommands.py:219
 #, python-format
-msgid ""
-"Invalid folder name “%s”, it may consist only of\n"
-"1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
+msgid "Creating account for postmaster@%s"
 msgstr ""
-"Le nom du fichier “%s” est invalid, il peut comporté\n"
-"entre 1 à 20 caractères (A-Z, a-z, 0-9 est _)."
+
+#: VirtualMailManager/cli/subcommands.py:235
+#: VirtualMailManager/cli/subcommands.py:249
+#: VirtualMailManager/cli/subcommands.py:322
+#: VirtualMailManager/cli/subcommands.py:343
+#: VirtualMailManager/cli/subcommands.py:372
+#: VirtualMailManager/cli/subcommands.py:509
+#: VirtualMailManager/cli/subcommands.py:522 VirtualMailManager/handler.py:453
+#: VirtualMailManager/handler.py:466 VirtualMailManager/handler.py:481
+#: VirtualMailManager/handler.py:510 VirtualMailManager/handler.py:674
+#, fuzzy, python-format
+msgid "Invalid argument: '%s'"
+msgstr "Paramètre invalide:  “%s”"
+
+#: VirtualMailManager/cli/subcommands.py:267
+#: VirtualMailManager/cli/subcommands.py:273
+msgid "Domain"
+msgstr "Domaine"
 
-#: VirtualMailManager/MailLocation.py:59
-msgid "Unknown mid specified."
-msgstr "Identifiant de l'emplacement e-mail specifié est inconnu."
+#: VirtualMailManager/cli/subcommands.py:275
+#: VirtualMailManager/cli/subcommands.py:284
+msgid "accounts"
+msgstr "comptes"
 
-#: VirtualMailManager/Relocated.py:65
-msgid "No destination address for relocated user denoted."
-msgstr "Pas d'adresse destination pour l'utilisateur relocalisé."
+#: VirtualMailManager/cli/subcommands.py:277
+#: VirtualMailManager/cli/subcommands.py:283
+#: VirtualMailManager/cli/subcommands.py:831
+msgid "alias domains"
+msgstr "Alias des domaines"
+
+#: VirtualMailManager/cli/subcommands.py:279
+#: VirtualMailManager/cli/subcommands.py:285
+msgid "aliases"
+msgstr "Alias"
+
+#: VirtualMailManager/cli/subcommands.py:281
+#: VirtualMailManager/cli/subcommands.py:286
+msgid "relocated users"
+msgstr "Utilisateurs relocalisés"
 
-#: VirtualMailManager/Relocated.py:75
-#, python-format
-msgid "The relocated user “%s” already exists."
-msgstr "Le transfére de l'utilisateur “%s” existe déjà."
+#: VirtualMailManager/cli/subcommands.py:292
+#, fuzzy
+msgid "Missing domain name and storage value."
+msgstr "Absence de nom de domaine et de nouveau transport."
 
-#: VirtualMailManager/Relocated.py:89 VirtualMailManager/Relocated.py:102
+#: VirtualMailManager/cli/subcommands.py:295
+#: VirtualMailManager/cli/subcommands.py:582
+#, fuzzy
+msgid "Missing storage value."
+msgstr "Absence du nom des utilisateurs."
+
+#: VirtualMailManager/cli/subcommands.py:301
+#: VirtualMailManager/cli/subcommands.py:586
+#, fuzzy, python-format
+msgid "Invalid storage value: '%s'"
+msgstr "Paramètre invalide:  “%s”"
+
+#: VirtualMailManager/cli/subcommands.py:311
 #, python-format
-msgid "The relocated user “%s” doesn't exists."
-msgstr "L'emplacement de l'utilisateur “%s” n'existe pas."
+msgid "Neither a valid number of messages nor the keyword 'force': '%s'"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:319
+#: VirtualMailManager/cli/subcommands.py:595
+#, python-format
+msgid "Not a valid number of messages: '%s'"
+msgstr ""
 
-#: VirtualMailManager/Transport.py:29
-msgid "Either tid or transport must be specified."
-msgstr "Soit l'identifiant du transport ou le transport doit être spécifié. "
+#: VirtualMailManager/cli/subcommands.py:354
+#: VirtualMailManager/cli/subcommands.py:609
+#, fuzzy, python-format
+msgid "Invalid service arguments: %s"
+msgstr "Paramètre invalide:  “%s”"
 
-#: VirtualMailManager/Transport.py:35
-msgid "tid must be an int/long."
-msgstr "L'identifiant du transport doit être un entier long."
+#: VirtualMailManager/cli/subcommands.py:363
+msgid "Missing domain name and new transport."
+msgstr "Absence de nom de domaine et de nouveau transport."
+
+#: VirtualMailManager/cli/subcommands.py:366
+msgid "Missing new transport."
+msgstr "Absence de nouveau transport."
 
-#: VirtualMailManager/Transport.py:63
-msgid "Unknown tid specified."
-msgstr "L'identifiant du transport spécifié est inconnu."
+#: VirtualMailManager/cli/subcommands.py:380
+#, fuzzy
+msgid "Missing UID."
+msgstr "Absence d'identiant utilisateur"
 
-#: VirtualMailManager/VirtualMailManager.py:54
-msgid ""
-"You are not root.\n"
-"\tGood bye!\n"
-msgstr ""
-"Vous n'êtes pas super-utilisateur (root).\n"
-"\tAu revoir!\n"
+#: VirtualMailManager/cli/subcommands.py:381
+#: VirtualMailManager/cli/subcommands.py:545
+#: VirtualMailManager/cli/subcommands.py:551
+#, fuzzy
+msgid "Account"
+msgstr "comptes"
 
-#: VirtualMailManager/VirtualMailManager.py:74
-msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
-msgstr "Pas de “vmm.cfg” trouvé dans:  /root:/usr/local/etc:/etc"
-
-#: VirtualMailManager/VirtualMailManager.py:85
+#: VirtualMailManager/cli/subcommands.py:396
 #, python-format
-msgid ""
-"fix permissions (%(perms)s) for “%(file)s”\n"
-"`chmod 0600 %(file)s` would be great."
+msgid "Unknown help topic: '%s'"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:409
+msgid "List of available subcommands:"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable encoding suffixes:"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable password schemes:"
 msgstr ""
-"Changer les droits de  permissions (%(perms)s) pour “%(file)s”\n"
-"`chmod 0600 %(file)s` serait mieux."
+
+#: VirtualMailManager/cli/subcommands.py:451
+msgid "Missing relocated address and destination."
+msgstr "Absence d'adresse relocalisée et de la destination."
+
+#: VirtualMailManager/cli/subcommands.py:460
+#: VirtualMailManager/cli/subcommands.py:467
+#, fuzzy
+msgid "Missing relocated address."
+msgstr "Absence d'adresse relocalisée"
+
+#: VirtualMailManager/cli/subcommands.py:490
+#: VirtualMailManager/cli/subcommands.py:503
+#: VirtualMailManager/cli/subcommands.py:516
+#: VirtualMailManager/cli/subcommands.py:568
+#: VirtualMailManager/cli/subcommands.py:603
+msgid "Missing e-mail address."
+msgstr "Absence de l'adresse e-mail."
+
+#: VirtualMailManager/cli/subcommands.py:497
+#, fuzzy, python-format
+msgid "Generated password: %s"
+msgstr "Entrer un nouveau mot de passe:"
 
-#: VirtualMailManager/VirtualMailManager.py:100
-#, python-format
-msgid ""
-"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
+#: VirtualMailManager/cli/subcommands.py:552
+msgid "alias addresses"
+msgstr "Adresses alias"
+
+#: VirtualMailManager/cli/subcommands.py:558
+#, fuzzy
+msgid "Missing e-mail address and user's name."
+msgstr "Absence de l'adresse e-mail et du nom des utilisateurs."
+
+#: VirtualMailManager/cli/subcommands.py:561
+#, fuzzy
+msgid "Missing user's name."
+msgstr "Absence du nom des utilisateurs."
+
+#: VirtualMailManager/cli/subcommands.py:579
+#, fuzzy
+msgid "Missing e-mail address and storage value."
+msgstr "Absence de l'adresse e-mail et du nom des utilisateurs."
+
+#: VirtualMailManager/cli/subcommands.py:617
+msgid "Missing e-mail address and transport."
+msgstr "Absence de l'adresse e-mail et du transport."
+
+#: VirtualMailManager/cli/subcommands.py:620
+msgid "Missing transport."
+msgstr "Absence du transport."
+
+#: VirtualMailManager/cli/subcommands.py:630
+msgid "usage: "
 msgstr ""
-"“%s” n'est pas un répertoire.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
 
-#: VirtualMailManager/VirtualMailManager.py:105
+#. 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]
+#: VirtualMailManager/cli/subcommands.py:640
 #, python-format
 msgid ""
-"“%(binary)s” doesn't exists.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+"usage: %s subcommand arguments\n"
+"  short long\n"
+"  subcommand                arguments\n"
 msgstr ""
-"“%(binary)s” n'existe pas.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "from"
+msgstr "de"
 
-#: VirtualMailManager/VirtualMailManager.py:109
-#, python-format
-msgid ""
-"“%(binary)s” is not executable.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#. 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
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "version"
+msgstr "version"
+
+#: VirtualMailManager/cli/subcommands.py:662
+msgid "on"
+msgstr "sur"
+
+#: VirtualMailManager/cli/subcommands.py:664
+msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
 msgstr ""
-"“%(binary)s” n'est pas executable.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+
+#: VirtualMailManager/cli/subcommands.py:672
+msgid "uid"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:166
-msgid "The domain name is too long."
-msgstr "Le nom du domaine est trop long."
+#: VirtualMailManager/cli/subcommands.py:673
+msgid "get the address of the user with the given UID"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:169
-#, python-format
-msgid "The domain name “%s” is invalid."
-msgstr "Le nom du domaine “%s” est invalide."
+#: VirtualMailManager/cli/subcommands.py:674
+#: VirtualMailManager/cli/subcommands.py:684
+msgid "address [password]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:675
+msgid "create a new e-mail user with the given address"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:209
-msgid "Enter new password: "
-msgstr "Entrer un nouveau mot de passe:"
+#: VirtualMailManager/cli/subcommands.py:677
+#: VirtualMailManager/cli/subcommands.py:704
+#: VirtualMailManager/cli/subcommands.py:744
+#: VirtualMailManager/cli/subcommands.py:746
+#, fuzzy
+msgid "address"
+msgstr "Adresses alias"
 
-#: VirtualMailManager/VirtualMailManager.py:210
-msgid "Retype new password: "
-msgstr "Re-écrire le mot de passe:"
+#: VirtualMailManager/cli/subcommands.py:678
+msgid "delete the specified user"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:212
-msgid "Sorry, passwords do not match"
-msgstr "Le mot de passe ne correspondent pas"
+#: VirtualMailManager/cli/subcommands.py:679
+msgid "address [details]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:680
+msgid "display information about the given address"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:216
-msgid "Sorry, empty passwords are not permitted"
-msgstr "Les mots de passe vides ne sont pas autorisées"
+#: VirtualMailManager/cli/subcommands.py:681
+msgid "address name"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:682
+msgid "set or update the real name for an address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:685
+msgid "update the password for the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:687
+msgid "address storage [messages]"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:265
-#: VirtualMailManager/VirtualMailManager.py:352
-#, python-format
-msgid "No such directory: %s"
-msgstr "Pas de répertoire:  %s"
+#: VirtualMailManager/cli/subcommands.py:688
+msgid "update the quota limit for the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:690
+msgid "address [service ...]"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:340
-msgid "Found \"..\" in home directory path."
-msgstr "\"..\" trouvé dans le chemins d'accès du répertoire racine de l'utilisateur (home)"
+#: VirtualMailManager/cli/subcommands.py:691
+msgid "enables the specified services and disables all not specified services"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:348
-msgid "Owner/group mismatch in home directory detected."
-msgstr "Défaut d'appariement détecté entre les droits propriétaire/groupe avec le répertoire de l'utilisateur (home)"
+#: VirtualMailManager/cli/subcommands.py:694
+#, fuzzy
+msgid "address transport"
+msgstr "Absence du transport."
 
-#: VirtualMailManager/VirtualMailManager.py:364
-msgid "FATAL: \"..\" in domain directory path detected."
-msgstr "FATAL: \"..\" dans le chemins d'accès du réperoire domaine."
+#: VirtualMailManager/cli/subcommands.py:695
+msgid "update the transport of the given address"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:370
-msgid "FATAL: group mismatch in domain directory detected"
-msgstr "Défaut d'appariement détecté dans le répertoire domaine"
+#: VirtualMailManager/cli/subcommands.py:697
+#, fuzzy
+msgid "address destination ..."
+msgstr "L'adresse et la destination sont identique. "
+
+#: VirtualMailManager/cli/subcommands.py:698
+msgid "create a new alias e-mail address with one or more destinations"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:457
-#, python-format
-msgid ""
-"Configurtion error: \"%s\"\n"
-"(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
+#: VirtualMailManager/cli/subcommands.py:701
+#, fuzzy
+msgid "address [destination]"
+msgstr "Absence de l'adresse alias et de la destination."
+
+#: VirtualMailManager/cli/subcommands.py:702
+msgid "delete the specified alias e-mail address or one of its destinations"
 msgstr ""
-"Erreur de configuration: \"%s\"\n"
-"(dans la section \"connfig\", option \"done\") voir aussi: vmm.cfg(5)\n"
+
+#: VirtualMailManager/cli/subcommands.py:705
+msgid "show the destination(s) of the specified alias"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:477
-#, python-format
-msgid "Invalid section: “%s”"
-msgstr "Section invalide:  “%s”"
+#: VirtualMailManager/cli/subcommands.py:708
+#: VirtualMailManager/cli/subcommands.py:717
+msgid "fqdn destination"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:709
+msgid "create a new alias for an existing domain"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:487
-#: VirtualMailManager/VirtualMailManager.py:497
-#: VirtualMailManager/VirtualMailManager.py:516
-#: VirtualMailManager/VirtualMailManager.py:624
-#: VirtualMailManager/VirtualMailManager.py:655
-#, python-format
-msgid "Invalid argument: “%s”"
-msgstr "Paramètre invalide:  “%s”"
+#: VirtualMailManager/cli/subcommands.py:711
+#: VirtualMailManager/cli/subcommands.py:714
+#: VirtualMailManager/cli/subcommands.py:723
+msgid "fqdn"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:712
+msgid "delete the specified alias domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:715
+#, fuzzy
+msgid "show the destination of the given alias domain"
+msgstr "Pas de destination pour l'alias de domaine relevé."
 
-#: VirtualMailManager/VirtualMailManager.py:520
-msgid ""
-"The keyword “detailed” is deprecated and will be removed in a future release.\n"
-"   Please use the keyword “full” to get full details."
+#: VirtualMailManager/cli/subcommands.py:718
+#, fuzzy
+msgid "assign the given alias domain to an other domain"
+msgstr "Absence du nom de l'alias de domaine et du nom cible de domaine"
+
+#: VirtualMailManager/cli/subcommands.py:720
+#, fuzzy
+msgid "fqdn [transport]"
+msgstr "Absence du transport."
+
+#: VirtualMailManager/cli/subcommands.py:721
+msgid "create a new domain"
 msgstr ""
-"Le mot-clé “detailed” est obselète et va être supprimé dans la futur version.\n"
-"   Utiliser, plutôt, le mot “full” pour avoir tous les détails."
+
+#: VirtualMailManager/cli/subcommands.py:724
+msgid "delete the given domain and all its alias domains"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:593
-#, python-format
-msgid "The pattern “%s” contains invalid characters."
-msgstr "Le modèle  “%s” contient des charactères invalides."
+#: VirtualMailManager/cli/subcommands.py:725
+msgid "fqdn [details]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:726
+msgid "display information about the given domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:728
+msgid "fqdn storage [messages]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:729
+msgid "update the quota limit of the specified domain"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:619
-#, python-format
-msgid "The destination account/alias “%s” doesn't exists yet."
-msgstr "La destination compte/alias “%s” n'existe pas."
+#: VirtualMailManager/cli/subcommands.py:731
+msgid "fqdn [service ...]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:732
+msgid "enables the specified services and disables all not specified services of the given domain"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:636
-#, python-format
-msgid ""
-"The account has been successfully deleted from the database.\n"
-"    But an error occurred while deleting the following directory:\n"
-"    “%(directory)s”\n"
-"    Reason: %(raeson)s"
+#: VirtualMailManager/cli/subcommands.py:735
+#, fuzzy
+msgid "fqdn transport"
+msgstr "Absence du transport."
+
+#: VirtualMailManager/cli/subcommands.py:736
+#, fuzzy
+msgid "update the transport of the specified domain"
+msgstr "Soit l'identifiant du transport ou le transport doit être spécifié. "
+
+#: VirtualMailManager/cli/subcommands.py:737
+msgid "[pattern]"
 msgstr ""
-"Le compte a été supprimé de la base de donnée avec succès.\n"
-"    Mais une erreur c'est produite lors de la suppression du répertoire suivant:\n"
-"    “%(directory)s”\n"
-"    Raison: %(raeson)s"
+
+#: VirtualMailManager/cli/subcommands.py:738
+msgid "list all domains / search domains by pattern"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:676
-msgid "Account doesn't exists"
-msgstr "Le compte n'existe pas."
+#: VirtualMailManager/cli/subcommands.py:741
+#, fuzzy
+msgid "address newaddress"
+msgstr "Adresses alias"
+
+#: VirtualMailManager/cli/subcommands.py:742
+msgid "create a new record for a relocated user"
+msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:692
-#: VirtualMailManager/VirtualMailManager.py:702
-msgid ""
-"The service name “managesieve” is deprecated and will be removed\n"
-"   in a future release.\n"
-"   Please use the service name “sieve” instead."
+#: VirtualMailManager/cli/subcommands.py:745
+msgid "delete the record of the relocated user"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:747
+msgid "print information about a relocated user"
 msgstr ""
-"Le nom de service “managesieve” est obselète et va être supprimé\n"
-"   dans la futur version.\n"
-"   Utiliser, plutôt, le nom de service “sieve”."
+
+#: VirtualMailManager/cli/subcommands.py:749
+msgid "option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:750
+msgid "show the actual value of the configuration option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:751
+msgid "option value"
+msgstr ""
 
-#: VirtualMailManager/ext/Postconf.py:44
-#, python-format
-msgid "The value “%s” looks not like a valid postfix configuration parameter name."
-msgstr "La valeur “%s” ne semble pas être un nom valide pour les paramètres de configuration postfix. "
+#: VirtualMailManager/cli/subcommands.py:752
+msgid "set a new value for the configuration option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:753
+msgid "[section]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:754
+msgid "start interactive configuration modus"
+msgstr ""
 
-#: vmm:34
+#: VirtualMailManager/cli/subcommands.py:756
+msgid "lists all usable password schemes and password encoding suffixes"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:758
+#, fuzzy
+msgid "[subcommand]"
+msgstr "Sous-commande inconue."
+
+#: VirtualMailManager/cli/subcommands.py:759
+msgid "show a help overview or help for the given subcommand"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:761
+msgid "show version and copyright information"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:809
 #, python-format
-msgid ""
-"Usage: %s SUBCOMMAND OBJECT ARGS*\n"
-"  short long\n"
-"  subcommand               object             args (* = optional)\n"
+msgid "[%(percent)s%%] %(used)s/%(limit)s"
 msgstr ""
-"Usage: %s SOUSCOMMANDE OBJET ARGS*\n"
-"  short long\n"
-"  souscommande               objet             args (* = facultatif)\n"
 
-#: vmm:73 vmm:84 vmm:494
-msgid "Error"
-msgstr "Erreur"
-
-#: vmm:111
+#. TP: used in e.g. 'Domain information' or 'Account information'
+#: VirtualMailManager/cli/subcommands.py:815
 msgid "information"
 msgstr "information"
 
-#: vmm:121
-msgid "Available"
-msgstr "Disponible"
+#. TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
+#: VirtualMailManager/cli/subcommands.py:828
+msgid "Existing"
+msgstr ""
 
-#: vmm:124 vmm:223 vmm:229
-msgid "alias domains"
-msgstr "Alias des domaines"
-
-#: vmm:134 vmm:145 vmm:169
+#: VirtualMailManager/cli/subcommands.py:841
+#: VirtualMailManager/cli/subcommands.py:883
 msgid "\tNone"
 msgstr "\tAucun"
 
-#: vmm:138
+#: VirtualMailManager/cli/subcommands.py:846
 msgid "Alias information"
 msgstr "Information sur alias"
 
-#: vmm:140
+#: VirtualMailManager/cli/subcommands.py:848
 #, python-format
 msgid "\tMail for %s will be redirected to:"
 msgstr "\tLes courriels pour %s vont être redirigé à:"
 
-#: vmm:149
+#: VirtualMailManager/cli/subcommands.py:855
 msgid "Relocated information"
 msgstr "Information sur la relocalisation"
 
-#: vmm:151
-#, python-format
-msgid "\tUser “%(addr)s” has moved to “%(dest)s”"
+#: VirtualMailManager/cli/subcommands.py:857
+#, fuzzy, python-format
+msgid "\tUser '%(addr)s' has moved to '%(dest)s'"
 msgstr "\tUtilisateur “%(addr)s” a été déplacée à “%(dest)s”"
 
-#: vmm:164
-msgid "Available domains"
-msgstr "Domaines disponible"
-
-#: vmm:166
+#: VirtualMailManager/cli/subcommands.py:872
 msgid "Matching domains"
 msgstr "Domaines concordant"
 
-#: vmm:180
+#: VirtualMailManager/cli/subcommands.py:874
+#, fuzzy
+msgid "Existing domains"
+msgstr "Domaines concordant"
+
+#: VirtualMailManager/cli/subcommands.py:889
 msgid "Alias domain information"
 msgstr "Information sur l'alias de domaine"
 
-#: vmm:186
+#: VirtualMailManager/cli/subcommands.py:894
 #, python-format
 msgid ""
 "\tThe alias domain %(alias)s belongs to:\n"
@@ -513,114 +822,470 @@
 "\tL'alias de domaine %(alias)s appartiennent à:\n"
 "\t    * %(domain)s"
 
-#: vmm:197 vmm:205 vmm:213
-msgid "Missing domain name."
-msgstr "Absence de nom de domaine."
+#: VirtualMailManager/common.py:63
+#, fuzzy, python-format
+msgid "No such file: '%s'"
+msgstr "Pas de répertoire:  %s"
+
+#: VirtualMailManager/common.py:66
+#, python-format
+msgid "File is not executable: '%s'"
+msgstr ""
+
+#: VirtualMailManager/common.py:82
+msgid "GiB"
+msgstr ""
+
+#: VirtualMailManager/common.py:82
+msgid "TiB"
+msgstr ""
+
+#: VirtualMailManager/common.py:83
+msgid "KiB"
+msgstr ""
+
+#: VirtualMailManager/common.py:83
+msgid "MiB"
+msgstr ""
+
+#. TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
+#: VirtualMailManager/common.py:87
+#, python-format
+msgid "%(size)s %(prefix)s"
+msgstr ""
+
+#: VirtualMailManager/config.py:89
+#, python-format
+msgid "Not a boolean: '%s'"
+msgstr ""
+
+#: VirtualMailManager/config.py:127
+#, python-format
+msgid "Bad format: '%s' - expected: section.option"
+msgstr ""
+
+#: VirtualMailManager/config.py:380
+#, fuzzy, python-format
+msgid "* Section: %s\n"
+msgstr "absence de la section: %s\n"
+
+#: VirtualMailManager/config.py:390 VirtualMailManager/config.py:398
+#, fuzzy, python-format
+msgid "Check of configuration file %s failed.\n"
+msgstr "Utilisation du fichier configuration: %s\n"
+
+#: VirtualMailManager/config.py:392
+msgid "Missing options, which have no default value.\n"
+msgstr ""
+
+#: VirtualMailManager/config.py:400 VirtualMailManager/config.py:402
+#, fuzzy
+msgid "Invalid configuration values.\n"
+msgstr "Utilisation du fichier configuration: %s\n"
+
+#: VirtualMailManager/config.py:441 VirtualMailManager/config.py:525
+#, python-format
+msgid "Not a valid Dovecot version: '%s'"
+msgstr ""
+
+#: VirtualMailManager/config.py:447 VirtualMailManager/config.py:482
+#, python-format
+msgid "Unsupported database module: '%s'"
+msgstr ""
+
+#: VirtualMailManager/config.py:452 VirtualMailManager/config.py:490
+#, python-format
+msgid "Unknown pgsql SSL mode: '%s'"
+msgstr ""
 
-#: vmm:215 vmm:219
-msgid "Domain"
-msgstr "Domaine"
+#: VirtualMailManager/config.py:459 VirtualMailManager/config.py:503
+#: VirtualMailManager/maillocation.py:70
+#, python-format
+msgid "Unsupported mailbox format: '%s'"
+msgstr ""
+
+#: VirtualMailManager/config.py:475 VirtualMailManager/handler.py:283
+#: VirtualMailManager/handler.py:357 VirtualMailManager/handler.py:362
+#: VirtualMailManager/handler.py:390
+#, python-format
+msgid "No such directory: %s"
+msgstr "Pas de répertoire:  %s"
+
+#: VirtualMailManager/config.py:514
+#, python-format
+msgid "Not a valid size value: '%s'"
+msgstr ""
+
+#: VirtualMailManager/domain.py:78
+#, fuzzy, python-format
+msgid "The domain '%s' is an alias domain."
+msgstr "Le domaine “%s” est un alias de domaine."
+
+#: VirtualMailManager/domain.py:108
+#, python-format
+msgid "There are %(account_count)u accounts, %(alias_count)u aliases and %(relocated_count)u relocated users."
+msgstr ""
+
+#: VirtualMailManager/domain.py:123
+#, fuzzy, python-format
+msgid "The domain '%s' already exists."
+msgstr "Le domaine “%s” existe déjà."
+
+#: VirtualMailManager/domain.py:437
+#, fuzzy
+msgid "The domain name is too long"
+msgstr "Le nom du domaine est trop long."
 
-#: vmm:221 vmm:230
-msgid "accounts"
+#: VirtualMailManager/domain.py:439
+#, fuzzy, python-format
+msgid "The domain name '%s' is invalid"
+msgstr "Le nom du domaine “%s” est invalide."
+
+#: VirtualMailManager/emailaddress.py:73
+#, fuzzy, python-format
+msgid "Missing the '@' sign in address: '%s'"
+msgstr "Absence du signe '@' dans l'adresse e-mail “%s”. "
+
+#: VirtualMailManager/emailaddress.py:76
+#, fuzzy, python-format
+msgid "Too many '@' signs in address: '%s'"
+msgstr "Absence du signe '@' dans l'adresse e-mail “%s”. "
+
+#: VirtualMailManager/emailaddress.py:79
+#, fuzzy, python-format
+msgid "Missing local-part in address: '%s'"
+msgstr "Absence d'adresse relocalisée"
+
+#: VirtualMailManager/emailaddress.py:82
+#, fuzzy, python-format
+msgid "Missing domain name in address: '%s'"
+msgstr "Absence d'un nom de domaine après “%s@”"
+
+#: VirtualMailManager/emailaddress.py:145
+#, fuzzy, python-format
+msgid "The local-part '%s' is too long."
+msgstr "La partie locale “%s” est trop longue."
+
+#: VirtualMailManager/emailaddress.py:150
+#, fuzzy, python-format
+msgid "The local-part '%(l_part)s' contains invalid characters: %(i_chars)s"
+msgstr "La partie locale “%(lpart)s” contient des caractères invalide: %(ichrs)s"
+
+#: VirtualMailManager/ext/postconf.py:84
+#, fuzzy, python-format
+msgid "The value '%s' does not look like a valid postfix configuration parameter name."
+msgstr "La valeur “%s” ne semble pas être un nom valide pour les paramètres de configuration postfix. "
+
+#: VirtualMailManager/handler.py:56
+#, fuzzy
+msgid "an account"
 msgstr "comptes"
 
-#: vmm:225 vmm:231
-msgid "aliases"
+#: VirtualMailManager/handler.py:57
+#, fuzzy
+msgid "an alias"
 msgstr "Alias"
 
-#: vmm:227 vmm:232
-msgid "relocated users"
+#: VirtualMailManager/handler.py:58
+#, fuzzy
+msgid "a relocated user"
 msgstr "Utilisateurs relocalisés"
 
-#: vmm:236
-msgid "Missing domain name and new transport."
-msgstr "Absence de nom de domaine et de nouveau transport."
+#: VirtualMailManager/handler.py:84
+msgid ""
+"You are not root.\n"
+"\tGood bye!\n"
+msgstr ""
+"Vous n'êtes pas super-utilisateur (root).\n"
+"\tAu revoir!\n"
 
-#: vmm:238
-msgid "Missing new transport."
-msgstr "Absence de nouveau transport."
+#: VirtualMailManager/handler.py:104
+#, python-format
+msgid "Could not find '%(cfg_file)s' in: '%(cfg_path)s'"
+msgstr ""
 
-#: vmm:247 vmm:262
-msgid "Missing alias domain name and target domain name."
-msgstr "Absence du nom de l'alias de domaine et du nom cible de domaine"
+#: VirtualMailManager/handler.py:115
+#, fuzzy, python-format
+msgid ""
+"wrong permissions for '%(file)s': %(perms)s\n"
+"`chmod 0600 %(file)s` would be great."
+msgstr ""
+"Changer les droits de  permissions (%(perms)s) pour “%(file)s”\n"
+"`chmod 0600 %(file)s` serait mieux."
 
-#: vmm:249 vmm:264
-msgid "Missing target domain name."
-msgstr "Asbence du nom cible de domaine"
+#: VirtualMailManager/handler.py:135
+#, fuzzy, python-format
+msgid ""
+"'%(path)s' is not a directory.\n"
+"(%(cfg_file)s: section 'misc', option 'base_directory')"
+msgstr ""
+"“%s” n'est pas un répertoire.\n"
+"(vmm.cfg: section \"domdir\", option \"base\")"
 
-#: vmm:255 vmm:270
-msgid "Missing alias domain name."
-msgstr "Absence du nom de l'alias de domaine."
+#: VirtualMailManager/handler.py:144
+#, python-format
+msgid ""
+"\n"
+"(%(cfg_file)s: section 'bin', option '%(option)s')"
+msgstr ""
 
-#: vmm:276 vmm:285 vmm:293 vmm:323 vmm:331 vmm:339
-msgid "Missing e-mail address."
-msgstr "Absence de l'adresse e-mail."
+#: VirtualMailManager/handler.py:158 VirtualMailManager/handler.py:165
+#, python-format
+msgid "Unable to import database module '%s'."
+msgstr ""
+
+#. TP: %(a_type)s will be one of: 'an account', 'an alias' or
+#. 'a relocated user'
+#: VirtualMailManager/handler.py:244
+#, fuzzy, python-format
+msgid "There is already %(a_type)s with the address '%(address)s'."
+msgstr "Il y a déjà un alias avec l'adresse “%s”."
 
-#: vmm:301
-msgid "alias addresses"
-msgstr "Adresses alias"
+#: VirtualMailManager/handler.py:297
+#, python-format
+msgid "'%s' is not a directory."
+msgstr ""
+
+#: VirtualMailManager/handler.py:300
+#, fuzzy, python-format
+msgid "The file/directory '%s' already exists."
+msgstr "Le transfére de l'utilisateur “%s” existe déjà."
 
-#: vmm:307
-msgid "Missing e-mail address and users name."
-msgstr "Absence de l'adresse e-mail et du nom des utilisateurs."
+#: VirtualMailManager/handler.py:329
+msgid "Skipped mailbox folders:"
+msgstr ""
 
-#: vmm:309
-msgid "Missing users name."
-msgstr "Absence du nom des utilisateurs."
+#: VirtualMailManager/handler.py:349
+#, python-format
+msgid "UID '%(uid)u' and/or GID '%(gid)u' are less than %(min_uid)u/%(min_gid)u."
+msgstr ""
+
+#: VirtualMailManager/handler.py:354 VirtualMailManager/handler.py:387
+#, fuzzy, python-format
+msgid "Found \"..\" in domain directory path: %s"
+msgstr "\"..\" trouvé dans le chemins d'accès du répertoire racine de l'utilisateur (home)"
 
-#: vmm:315
-msgid "Missing e-mail address and transport."
-msgstr "Absence de l'adresse e-mail et du transport."
+#: VirtualMailManager/handler.py:367
+#, fuzzy
+msgid "Detected owner/group mismatch in home directory."
+msgstr "Défaut d'appariement détecté entre les droits propriétaire/groupe avec le répertoire de l'utilisateur (home)"
+
+#: VirtualMailManager/handler.py:383
+#, python-format
+msgid "GID '%(gid)u' is less than '%(min_gid)u'."
+msgstr ""
 
-#: vmm:317
-msgid "Missing transport."
-msgstr "Absence du transport."
+#: VirtualMailManager/handler.py:394
+#, fuzzy, python-format
+msgid "Detected group mismatch in domain directory: %s"
+msgstr "Défaut d'appariement détecté dans le répertoire domaine"
 
-#: vmm:348
-msgid "Missing alias address and destination."
-msgstr "Absence de l'adresse alias et de la destination."
+#: VirtualMailManager/handler.py:470 VirtualMailManager/handler.py:748
+#, fuzzy, python-format
+msgid "Unknown service: '%s'"
+msgstr "Service “%s” inconnu."
 
-#: vmm:350 vmm:373
-msgid "Missing destination address."
+#: VirtualMailManager/handler.py:587
+#, fuzzy, python-format
+msgid "The pattern '%s' contains invalid characters."
+msgstr "Le modèle  “%s” contient des charactères invalides."
+
+#: VirtualMailManager/handler.py:614
+#, fuzzy
+msgid "Ignored destination addresses:"
 msgstr "Absence de l'adresse destination."
 
-#: vmm:356 vmm:362
-msgid "Missing alias address"
-msgstr "Absence de l'adresse alias"
+#: VirtualMailManager/handler.py:619 VirtualMailManager/handler.py:769
+#, fuzzy, python-format
+msgid "The destination account/alias '%s' does not exist."
+msgstr "La destination compte/alias “%s” n'existe pas."
+
+#: VirtualMailManager/handler.py:641
+#, fuzzy, python-format
+msgid ""
+"The account has been successfully deleted from the database.\n"
+"    But an error occurred while deleting the following directory:\n"
+"    '%(directory)s'\n"
+"    Reason: %(reason)s"
+msgstr ""
+"Le compte a été supprimé de la base de donnée avec succès.\n"
+"    Mais une erreur c'est produite lors de la suppression du répertoire suivant:\n"
+"    “%(directory)s”\n"
+"    Raison: %(raeson)s"
+
+#: VirtualMailManager/handler.py:712
+#, python-format
+msgid "Could not accept name: '%s'"
+msgstr ""
 
-#: vmm:371
-msgid "Missing relocated address and destination."
-msgstr "Absence d'adresse relocalisée et de la destination."
+#: VirtualMailManager/handler.py:735
+#, python-format
+msgid "Could not accept transport: '%s'"
+msgstr ""
+
+#: VirtualMailManager/handler.py:779 VirtualMailManager/relocated.py:98
+#: VirtualMailManager/relocated.py:105
+#, fuzzy, python-format
+msgid "The relocated user '%s' does not exist."
+msgstr "L'emplacement de l'utilisateur “%s” n'existe pas."
+
+#: VirtualMailManager/mailbox.py:260
+#, python-format
+msgid "Failed to create mailboxes: %r\n"
+msgstr ""
+
+#: VirtualMailManager/maillocation.py:74
+msgid "Empty directory name"
+msgstr ""
+
+#: VirtualMailManager/maillocation.py:76
+#, fuzzy, python-format
+msgid "Directory name is too long: '%s'"
+msgstr "Le nom du domaine est trop long."
 
-#: vmm:379 vmm:387
-msgid "Missing relocated address"
-msgstr "Absence d'adresse relocalisée"
+#: VirtualMailManager/password.py:388
+#, python-format
+msgid "Unsupported password scheme: '%s'"
+msgstr ""
+
+#: VirtualMailManager/password.py:391
+#, python-format
+msgid "The password scheme '%(scheme)s' requires Dovecot >= v%(version)s."
+msgstr ""
+
+#: VirtualMailManager/password.py:397
+msgid "Encoding suffixes for password schemes require Dovecot >= v1.1.alpha1."
+msgstr ""
+
+#: VirtualMailManager/password.py:400
+#, python-format
+msgid "Unsupported password encoding: '%s'"
+msgstr ""
+
+#: VirtualMailManager/relocated.py:71
+msgid "Address and destination are identical."
+msgstr "L'adresse et la destination sont identique. "
 
-#: vmm:393
-msgid "Missing userid"
-msgstr "Absence d'identiant utilisateur"
+#: VirtualMailManager/relocated.py:75
+#, fuzzy, python-format
+msgid "The relocated user '%s' already exists."
+msgstr "Le transfére de l'utilisateur “%s” existe déjà."
+
+#~ msgid "There is already a relocated user with the address “%s”."
+#~ msgstr "Il y a déjà un utilisateur relocalisé avec cette adresse “%s”."
+
+#~ msgid "There is already an account with address “%s”."
+#~ msgstr "Il y a déjà un compte avec l'adresse  “%s”."
 
-#: vmm:406
-msgid "Warnings:"
-msgstr "Avertissements:"
+#~ msgid "No destination address for alias denoted."
+#~ msgstr "Pas de destination à l'adresse de l'alias relevé."
+
+#~ msgid "The alias “%(a)s” with destination “%(d)s” already exists."
+#~ msgstr "L'alias “%(a)s” avec la destination “%(d)s” existe déjà."
+
+#~ msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
+#~ msgstr "L'alias “%(a)s” avec la destination “%(d)s”  n'existe pas."
+
+#~ msgid "missing options in section %s:\n"
+#~ msgstr "absence des options dans la section %s:\n"
+
+#~ msgid "There are accounts and aliases."
+#~ msgstr "Il y a des comptes et des alias."
 
-#: vmm:412
-msgid "from"
-msgstr "de"
+#~ msgid "There are accounts."
+#~ msgstr "Il y a des comptes."
+
+#~ msgid "There are aliases."
+#~ msgstr "Il y a des alias."
+
+#~ msgid "“%s” looks not like an e-mail address."
+#~ msgstr "“%s” ne ressemble pas à une adresse e-mail."
+
+#~ msgid "No localpart specified."
+#~ msgstr "Pas de partie locale spécifié."
+
+#~ msgid "Either mid or maillocation must be specified."
+#~ msgstr "Soit l'identifiant de l'emplacement e-mail ou l'emplacement e-mail doit être spécifié. "
+
+#~ msgid "mid must be an int/long."
+#~ msgstr "L'identiant de l'emplcement e-mail doit être un entier long."
 
-#: vmm:412
-msgid "version"
-msgstr "version"
+#~ msgid ""
+#~ "Invalid folder name “%s”, it may consist only of\n"
+#~ "1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
+#~ msgstr ""
+#~ "Le nom du fichier “%s” est invalid, il peut comporté\n"
+#~ "entre 1 à 20 caractères (A-Z, a-z, 0-9 est _)."
+
+#~ msgid "Unknown mid specified."
+#~ msgstr "Identifiant de l'emplacement e-mail specifié est inconnu."
+
+#~ msgid "No destination address for relocated user denoted."
+#~ msgstr "Pas d'adresse destination pour l'utilisateur relocalisé."
+
+#~ msgid "tid must be an int/long."
+#~ msgstr "L'identifiant du transport doit être un entier long."
+
+#~ msgid "Unknown tid specified."
+#~ msgstr "L'identifiant du transport spécifié est inconnu."
+
+#~ msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
+#~ msgstr "Pas de “vmm.cfg” trouvé dans:  /root:/usr/local/etc:/etc"
+
+#~ msgid ""
+#~ "“%(binary)s” doesn't exists.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#~ msgstr ""
+#~ "“%(binary)s” n'existe pas.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
 
-#: vmm:414
-msgid "on"
-msgstr "sur"
+#~ msgid ""
+#~ "“%(binary)s” is not executable.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#~ msgstr ""
+#~ "“%(binary)s” n'est pas executable.\n"
+#~ "(vmm.cfg: section \"bin\", option \"%(option)s\")"
+
+#~ msgid "FATAL: \"..\" in domain directory path detected."
+#~ msgstr "FATAL: \"..\" dans le chemins d'accès du réperoire domaine."
+
+#~ msgid ""
+#~ "Configurtion error: \"%s\"\n"
+#~ "(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
+#~ msgstr ""
+#~ "Erreur de configuration: \"%s\"\n"
+#~ "(dans la section \"connfig\", option \"done\") voir aussi: vmm.cfg(5)\n"
+
+#~ msgid ""
+#~ "The keyword “detailed” is deprecated and will be removed in a future release.\n"
+#~ "   Please use the keyword “full” to get full details."
+#~ msgstr ""
+#~ "Le mot-clé “detailed” est obselète et va être supprimé dans la futur version.\n"
+#~ "   Utiliser, plutôt, le mot “full” pour avoir tous les détails."
 
-#: vmm:488
-msgid "Unknown subcommand"
-msgstr "Sous-commande inconue."
+#~ msgid "Account doesn't exists"
+#~ msgstr "Le compte n'existe pas."
+
+#~ msgid ""
+#~ "The service name “managesieve” is deprecated and will be removed\n"
+#~ "   in a future release.\n"
+#~ "   Please use the service name “sieve” instead."
+#~ msgstr ""
+#~ "Le nom de service “managesieve” est obselète et va être supprimé\n"
+#~ "   dans la futur version.\n"
+#~ "   Utiliser, plutôt, le nom de service “sieve”."
 
-#: vmm:491
-msgid "Ouch"
-msgstr "Aie"
+#~ msgid ""
+#~ "Usage: %s SUBCOMMAND OBJECT ARGS*\n"
+#~ "  short long\n"
+#~ "  subcommand               object             args (* = optional)\n"
+#~ msgstr ""
+#~ "Usage: %s SOUSCOMMANDE OBJET ARGS*\n"
+#~ "  short long\n"
+#~ "  souscommande               objet             args (* = facultatif)\n"
+
+#~ msgid "Available"
+#~ msgstr "Disponible"
+
+#~ msgid "Available domains"
+#~ msgstr "Domaines disponible"
--- a/po/vmm.pot	Mon Nov 07 03:22:15 2011 +0000
+++ b/po/vmm.pot	Thu Jun 28 19:26:50 2012 +0000
@@ -6,634 +6,1120 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: vmm 0.5.2\n"
-"Report-Msgid-Bugs-To: neverseen@users.sourceforge.net\n"
-"POT-Creation-Date: 2009-10-20 19:19+0200\n"
+"Project-Id-Version: vmm 0.6.0\n"
+"Report-Msgid-Bugs-To: user+vmm/tp@localhost.localdomain.org\n"
+"POT-Creation-Date: 2011-11-07 05:20+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#. TP: Hm, what quotation marks should be used?
+#. 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
-#: VirtualMailManager/Account.py:37 VirtualMailManager/Relocated.py:42
+#: VirtualMailManager/account.py:58 VirtualMailManager/alias.py:35
+#: VirtualMailManager/domain.py:120 VirtualMailManager/relocated.py:38
 #, python-format
-msgid "There is already an alias with the address “%s”."
+msgid "The domain '%s' does not exist."
 msgstr ""
 
-#: VirtualMailManager/Account.py:42 VirtualMailManager/Alias.py:41
+#: VirtualMailManager/account.py:106
 #, python-format
-msgid "There is already a relocated user with the address “%s”."
+msgid "The mailbox format '%(mbfmt)s' requires Dovecot >= v%(version)s."
 msgstr ""
 
-#: VirtualMailManager/Account.py:62 VirtualMailManager/Alias.py:57
-#: VirtualMailManager/Domain.py:161 VirtualMailManager/Domain.py:187
-#: VirtualMailManager/Domain.py:218 VirtualMailManager/Relocated.py:58
+#: VirtualMailManager/account.py:113 VirtualMailManager/account.py:305
 #, python-format
-msgid "The domain “%s” doesn't exist."
+msgid "Invalid transport '%(transport)s' for mailbox format '%(mbfmt)s'."
+msgstr ""
+
+#: VirtualMailManager/account.py:153 VirtualMailManager/cli/handler.py:93
+#: VirtualMailManager/handler.py:628 VirtualMailManager/handler.py:679
+#: VirtualMailManager/handler.py:705 VirtualMailManager/handler.py:716
+#: VirtualMailManager/handler.py:727 VirtualMailManager/handler.py:739
+#: VirtualMailManager/handler.py:753
+#, python-format
+msgid "The account '%s' does not exist."
 msgstr ""
 
-#: VirtualMailManager/Account.py:81
+#: VirtualMailManager/account.py:204 VirtualMailManager/account.py:214
+#: VirtualMailManager/cli/handler.py:77 VirtualMailManager/handler.py:596
 #, python-format
-msgid "Unknown service “%s”."
+msgid "The account '%s' already exists."
 msgstr ""
 
-#: VirtualMailManager/Account.py:84 VirtualMailManager/Account.py:157
-#: VirtualMailManager/Account.py:188 VirtualMailManager/Account.py:223
+#: VirtualMailManager/account.py:207 VirtualMailManager/handler.py:701
 #, python-format
-msgid "The account “%s” doesn't exist."
+msgid "Could not accept password: '%s'"
 msgstr ""
 
-#: VirtualMailManager/Account.py:152
+#: VirtualMailManager/account.py:217
 #, python-format
-msgid "The account “%s” already exists."
+msgid "No password set for account: '%s'"
 msgstr ""
 
-#. TP: A service (pop3/imap/…) is enabled/usable for a user
-#: VirtualMailManager/Account.py:197
-msgid "enabled"
+#: VirtualMailManager/account.py:245
+#, python-format
+msgid "Unknown field: '%s'"
 msgstr ""
 
-#. TP: A service (pop3/imap) isn't enabled/usable for a user
-#: VirtualMailManager/Account.py:200
+#: VirtualMailManager/account.py:267 VirtualMailManager/domain.py:292
+msgid "PostgreSQL-based dictionary quota requires Dovecot >= v1.1.2."
+msgstr ""
+
+#. TP: A service (e.g. pop3 or imap) may be enabled/usable or
+#. disabled/unusable for a user.
+#: VirtualMailManager/account.py:332
 msgid "disabled"
 msgstr ""
 
-#: VirtualMailManager/Account.py:244
-#, python-format
-msgid "There are %(count)d aliases with the destination address “%(address)s”."
+#: VirtualMailManager/account.py:332
+msgid "enabled"
 msgstr ""
 
-#: VirtualMailManager/Account.py:252
-msgid "uid must be an int/long."
+#: VirtualMailManager/account.py:343
+#, python-format
+msgid "Could not fetch information for account: '%s'"
 msgstr ""
 
-#: VirtualMailManager/Account.py:254
-msgid "uid must be greater than 0."
+#: VirtualMailManager/account.py:387
+#, python-format
+msgid "There are %(count)d aliases with the destination address '%(address)s'."
 msgstr ""
 
-#: VirtualMailManager/Account.py:262
-#, python-format
-msgid "There is no account with the UID “%d”."
+#: VirtualMailManager/account.py:416
+msgid "UID must be an int/long."
 msgstr ""
 
-#: VirtualMailManager/Alias.py:28 VirtualMailManager/Relocated.py:28
-msgid "Address and destination are identical."
+#: VirtualMailManager/account.py:418
+msgid "UID must be greater than 0."
+msgstr ""
+
+#: VirtualMailManager/account.py:427
+#, python-format
+msgid "There is no account with the UID: '%d'"
 msgstr ""
 
-#: VirtualMailManager/Alias.py:37 VirtualMailManager/Relocated.py:37
+#: VirtualMailManager/alias.py:60
 #, python-format
-msgid "There is already an account with address “%s”."
+msgid ""
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"Currently this alias expands into %(count)i/%(limit)i recipients.\n"
+"%(count_new)i additional destination(s) will render this alias unusable.\n"
+"Hint: Increase Postfix' virtual_alias_expansion_limit"
 msgstr ""
 
-#: VirtualMailManager/Alias.py:67
+#: VirtualMailManager/alias.py:67
 #, python-format
 msgid ""
-"Can't add new destination to alias “%(address)s”.\n"
-"Currently this alias expands into %(count)i recipients.\n"
-"One more destination will render this alias unusable.\n"
-"Hint: Increase Postfix' virtual_alias_expansion_limit\n"
+"Cannot add %(count_new)i new destination(s) to alias '%(address)s'.\n"
+"This alias already exceeds its expansion limit (%(count)i/%(limit)i).\n"
+"So its unusable, all messages addressed to this alias will be bounced.\n"
+"Hint: Delete some destination addresses."
 msgstr ""
 
-#: VirtualMailManager/Alias.py:76
-msgid "No destination address specified for alias."
+#: VirtualMailManager/alias.py:142 VirtualMailManager/alias.py:154
+#: VirtualMailManager/alias.py:161 VirtualMailManager/handler.py:657
+#, python-format
+msgid "The alias '%s' does not exist."
 msgstr ""
 
-#: VirtualMailManager/Alias.py:87
+#: VirtualMailManager/alias.py:145
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” already exists."
+msgid "The address '%(addr)s' is not a destination of the alias '%(alias)s'."
 msgstr ""
 
-#: VirtualMailManager/Alias.py:100 VirtualMailManager/Alias.py:117
+#: VirtualMailManager/aliasdomain.py:50
 #, python-format
-msgid "The alias “%s” doesn't exist."
+msgid "The domain '%s' is a primary domain."
 msgstr ""
 
-#: VirtualMailManager/Alias.py:119
+#: VirtualMailManager/aliasdomain.py:69
 #, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” doesn't exist."
+msgid "The alias domain '%s' already exists."
+msgstr ""
+
+#: VirtualMailManager/aliasdomain.py:72 VirtualMailManager/aliasdomain.py:106
+msgid "No destination domain set for the alias domain."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:30
+#: VirtualMailManager/aliasdomain.py:75 VirtualMailManager/aliasdomain.py:109
 #, python-format
-msgid "The domain “%s” is a primary domain."
+msgid "The target domain '%s' does not exist."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:35
+#: VirtualMailManager/aliasdomain.py:88 VirtualMailManager/aliasdomain.py:112
+#: VirtualMailManager/aliasdomain.py:133
 #, python-format
-msgid "The alias domain “%s” already exists."
+msgid "The alias domain '%s' does not exist."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:38 VirtualMailManager/AliasDomain.py:68
-msgid "No destination domain specified for alias domain."
+#: VirtualMailManager/aliasdomain.py:98
+#, python-format
+msgid "There is no primary domain for the alias domain '%s'."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:41 VirtualMailManager/AliasDomain.py:71
+#: VirtualMailManager/aliasdomain.py:115
 #, python-format
-msgid "The target domain “%s” doesn't exist."
+msgid ""
+"The alias domain '%(alias)s' is already assigned to the domain '%(domain)s'."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:60
-#, python-format
-msgid "There is no primary domain for the alias domain “%s”."
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:78
+msgid "Enter new password: "
+msgstr ""
+
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/cli/__init__.py:80
+msgid "Retype new password: "
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:63 VirtualMailManager/AliasDomain.py:74
-#: VirtualMailManager/AliasDomain.py:97
-#, python-format
-msgid "The alias domain “%s” doesn't exist."
+#: VirtualMailManager/cli/__init__.py:85 VirtualMailManager/cli/config.py:53
+msgid "Too many failures - try again later."
+msgstr ""
+
+#: VirtualMailManager/cli/__init__.py:91
+msgid "Sorry, passwords do not match."
 msgstr ""
 
-#: VirtualMailManager/AliasDomain.py:77
-#, python-format
-msgid ""
-"The alias domain “%(alias)s” is already assigned to the domain “%(domain)s”."
+#: VirtualMailManager/cli/__init__.py:95
+msgid "Sorry, empty passwords are not permitted."
 msgstr ""
 
-#: VirtualMailManager/Config.py:90 VirtualMailManager/Config.py:125
+#: VirtualMailManager/cli/config.py:32
+#, python-format
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
+msgstr ""
+
+#: VirtualMailManager/cli/config.py:36
 #, python-format
 msgid "Using configuration file: %s\n"
 msgstr ""
 
-#: VirtualMailManager/Config.py:94
+#: VirtualMailManager/cli/config.py:38
 #, python-format
-msgid "missing section: %s\n"
+msgid "* Configuration section: '%s'"
 msgstr ""
 
-#: VirtualMailManager/Config.py:96
+#: VirtualMailManager/cli/config.py:50
 #, python-format
-msgid "missing options in section %s:\n"
+msgid "Warning: %s"
+msgstr ""
+
+#: VirtualMailManager/cli/handler.py:66
+#, python-format
+msgid "Invalid section: '%s'"
 msgstr ""
 
-#: VirtualMailManager/Config.py:128
+#: VirtualMailManager/cli/main.py:32 VirtualMailManager/cli/main.py:65
+#: VirtualMailManager/cli/main.py:68 VirtualMailManager/cli/subcommands.py:629
+#: VirtualMailManager/cli/subcommands.py:649
 #, python-format
-msgid "* Config section: “%s”"
+msgid "Error: %s"
 msgstr ""
 
-#: VirtualMailManager/Config.py:131
-#, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
+#: VirtualMailManager/cli/main.py:41
+msgid "You must specify a subcommand at least."
 msgstr ""
 
-#: VirtualMailManager/Domain.py:37
+#: VirtualMailManager/cli/main.py:53
 #, python-format
-msgid "The domain “%s” is an alias domain."
-msgstr ""
-
-#: VirtualMailManager/Domain.py:122
-msgid "There are accounts and aliases."
+msgid "Unknown subcommand: '%s'"
 msgstr ""
 
-#: VirtualMailManager/Domain.py:125
-msgid "There are accounts."
-msgstr ""
-
-#: VirtualMailManager/Domain.py:128
-msgid "There are aliases."
+#. TP: We have to cry, because root has killed/interrupted vmm
+#. with Ctrl+C or Ctrl+D.
+#: VirtualMailManager/cli/main.py:62
+msgid "Ouch!"
 msgstr ""
 
-#: VirtualMailManager/Domain.py:143
+#: VirtualMailManager/cli/main.py:71
 #, python-format
-msgid "The domain “%s” already exists."
+msgid "Error: Unknown section: '%s'"
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:42
+#: VirtualMailManager/cli/main.py:74
 #, python-format
-msgid "Missing '@' sign in e-mail address “%s”."
+msgid "Error: No option '%(option)s' in section: '%(section)s'"
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:45
-#, python-format
-msgid "“%s” doesn't look like an e-mail address."
+#: VirtualMailManager/cli/main.py:77
+msgid "Warnings:"
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:50
+#: VirtualMailManager/cli/subcommands.py:78
 #, python-format
-msgid "Missing domain name after “%s@”."
+msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:62
-msgid "No local-part specified."
+#: VirtualMailManager/cli/subcommands.py:92
+msgid "Missing alias address and destination."
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:65
-#, python-format
-msgid "The local-part “%s” is too long"
+#: VirtualMailManager/cli/subcommands.py:95
+#: VirtualMailManager/cli/subcommands.py:453
+msgid "Missing destination address."
 msgstr ""
 
-#: VirtualMailManager/EmailAddress.py:72
-#, python-format
-msgid "The local-part “%(lpart)s” contains invalid characters: %(ichrs)s"
+#: VirtualMailManager/cli/subcommands.py:102
+#: VirtualMailManager/cli/subcommands.py:112
+msgid "Missing alias address."
 msgstr ""
 
-#: VirtualMailManager/MailLocation.py:28
-msgid "Either mid or maillocation must be specified."
+#: VirtualMailManager/cli/subcommands.py:134
+#: VirtualMailManager/cli/subcommands.py:168
+msgid "Missing alias domain name and destination domain name."
 msgstr ""
 
-#: VirtualMailManager/MailLocation.py:34
-msgid "mid must be an int/long."
+#: VirtualMailManager/cli/subcommands.py:137
+#: VirtualMailManager/cli/subcommands.py:171
+msgid "Missing destination domain name."
 msgstr ""
 
-#: VirtualMailManager/MailLocation.py:42
-#, python-format
-msgid ""
-"Invalid folder name “%s”, it may consist only of\n"
-"1 - 20 single byte characters (A-Z, a-z, 0-9 and _)."
+#: VirtualMailManager/cli/subcommands.py:145
+#: VirtualMailManager/cli/subcommands.py:152
+msgid "Missing alias domain name."
 msgstr ""
 
-#: VirtualMailManager/MailLocation.py:55
-msgid "Unknown mid specified."
+#: VirtualMailManager/cli/subcommands.py:179
+msgid "Missing option name."
 msgstr ""
 
-#: VirtualMailManager/Relocated.py:64
-msgid "No destination address specified for relocated user."
+#: VirtualMailManager/cli/subcommands.py:195
+msgid "Missing option and new value."
 msgstr ""
 
-#: VirtualMailManager/Relocated.py:74
-#, python-format
-msgid "The relocated user “%s” already exists."
+#: VirtualMailManager/cli/subcommands.py:197
+msgid "Missing new configuration value."
 msgstr ""
 
-#: VirtualMailManager/Relocated.py:88 VirtualMailManager/Relocated.py:101
-#, python-format
-msgid "The relocated user “%s” doesn't exist."
+#: VirtualMailManager/cli/subcommands.py:213
+#: VirtualMailManager/cli/subcommands.py:229
+#: VirtualMailManager/cli/subcommands.py:242
+#: VirtualMailManager/cli/subcommands.py:331
+msgid "Missing domain name."
 msgstr ""
 
-#: VirtualMailManager/Transport.py:27
-msgid "Either tid or transport must be specified."
-msgstr ""
-
-#: VirtualMailManager/Transport.py:33
-msgid "tid must be an int/long."
+#: VirtualMailManager/cli/subcommands.py:219
+#, python-format
+msgid "Creating account for postmaster@%s"
 msgstr ""
 
-#: VirtualMailManager/Transport.py:61
-msgid "Unknown tid specified."
-msgstr ""
-
-#: VirtualMailManager/VirtualMailManager.py:47
-msgid ""
-"You are not root.\n"
-"\tGood bye!\n"
+#: VirtualMailManager/cli/subcommands.py:235
+#: VirtualMailManager/cli/subcommands.py:249
+#: VirtualMailManager/cli/subcommands.py:322
+#: VirtualMailManager/cli/subcommands.py:343
+#: VirtualMailManager/cli/subcommands.py:372
+#: VirtualMailManager/cli/subcommands.py:509
+#: VirtualMailManager/cli/subcommands.py:522 VirtualMailManager/handler.py:453
+#: VirtualMailManager/handler.py:466 VirtualMailManager/handler.py:481
+#: VirtualMailManager/handler.py:510 VirtualMailManager/handler.py:674
+#, python-format
+msgid "Invalid argument: '%s'"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:67
-msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
+#: VirtualMailManager/cli/subcommands.py:267
+#: VirtualMailManager/cli/subcommands.py:273
+msgid "Domain"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:78
-#, python-format
-msgid ""
-"fix permissions (%(perms)s) for “%(file)s”\n"
-"`chmod 0600 %(file)s` would be great."
+#: VirtualMailManager/cli/subcommands.py:275
+#: VirtualMailManager/cli/subcommands.py:284
+msgid "accounts"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:93
-#, python-format
-msgid ""
-"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
+#: VirtualMailManager/cli/subcommands.py:277
+#: VirtualMailManager/cli/subcommands.py:283
+#: VirtualMailManager/cli/subcommands.py:831
+msgid "alias domains"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:98
-#, python-format
-msgid ""
-"“%(binary)s” doesn't exist.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#: VirtualMailManager/cli/subcommands.py:279
+#: VirtualMailManager/cli/subcommands.py:285
+msgid "aliases"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:102
-#, python-format
-msgid ""
-"“%(binary)s” is not executable.\n"
-"(vmm.cfg: section \"bin\", option \"%(option)s\")"
+#: VirtualMailManager/cli/subcommands.py:281
+#: VirtualMailManager/cli/subcommands.py:286
+msgid "relocated users"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:292
+msgid "Missing domain name and storage value."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:150
-msgid "The domain name is too long."
+#: VirtualMailManager/cli/subcommands.py:295
+#: VirtualMailManager/cli/subcommands.py:582
+msgid "Missing storage value."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:153
+#: VirtualMailManager/cli/subcommands.py:301
+#: VirtualMailManager/cli/subcommands.py:586
 #, python-format
-msgid "The domain name “%s” is invalid."
+msgid "Invalid storage value: '%s'"
 msgstr ""
 
-#. TP: Please preserve the trailing space.
-#: VirtualMailManager/VirtualMailManager.py:192
-msgid "Enter new password: "
+#: VirtualMailManager/cli/subcommands.py:311
+#, python-format
+msgid "Neither a valid number of messages nor the keyword 'force': '%s'"
 msgstr ""
 
-#. TP: Please preserve the trailing space.
-#: VirtualMailManager/VirtualMailManager.py:194
-msgid "Retype new password: "
+#: VirtualMailManager/cli/subcommands.py:319
+#: VirtualMailManager/cli/subcommands.py:595
+#, python-format
+msgid "Not a valid number of messages: '%s'"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:200
-msgid "Sorry, passwords do not match"
+#: VirtualMailManager/cli/subcommands.py:354
+#: VirtualMailManager/cli/subcommands.py:609
+#, python-format
+msgid "Invalid service arguments: %s"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:203
-msgid "Sorry, empty passwords are not permitted"
+#: VirtualMailManager/cli/subcommands.py:363
+msgid "Missing domain name and new transport."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:251
-#: VirtualMailManager/VirtualMailManager.py:338
-#, python-format
-msgid "No such directory: %s"
+#: VirtualMailManager/cli/subcommands.py:366
+msgid "Missing new transport."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:326
-msgid "Found \"..\" in home directory path."
+#: VirtualMailManager/cli/subcommands.py:380
+msgid "Missing UID."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:334
-msgid "Detected owner/group mismatch in home directory."
-msgstr ""
-
-#: VirtualMailManager/VirtualMailManager.py:349
-msgid "Found \"..\" in domain directory path."
+#: VirtualMailManager/cli/subcommands.py:381
+#: VirtualMailManager/cli/subcommands.py:545
+#: VirtualMailManager/cli/subcommands.py:551
+msgid "Account"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:355
-msgid "Detected group mismatch in domain directory."
+#: VirtualMailManager/cli/subcommands.py:396
+#, python-format
+msgid "Unknown help topic: '%s'"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:439
-#, python-format
-msgid ""
-"Configuration error: \"%s\"\n"
-"(in section \"config\", option \"done\") see also: vmm.cfg(5)\n"
+#: VirtualMailManager/cli/subcommands.py:409
+msgid "List of available subcommands:"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:459
-#, python-format
-msgid "Invalid section: “%s”"
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable encoding suffixes:"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:430
+msgid "Usable password schemes:"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:469
-#: VirtualMailManager/VirtualMailManager.py:479
-#: VirtualMailManager/VirtualMailManager.py:498
-#: VirtualMailManager/VirtualMailManager.py:606
-#: VirtualMailManager/VirtualMailManager.py:637
-#, python-format
-msgid "Invalid argument: “%s”"
+#: VirtualMailManager/cli/subcommands.py:451
+msgid "Missing relocated address and destination."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:502
-msgid ""
-"The keyword “detailed” is deprecated and will be removed in a future "
-"release.\n"
-"   Please use the keyword “full” to get full details."
+#: VirtualMailManager/cli/subcommands.py:460
+#: VirtualMailManager/cli/subcommands.py:467
+msgid "Missing relocated address."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:575
-#, python-format
-msgid "The pattern “%s” contains invalid characters."
+#: VirtualMailManager/cli/subcommands.py:490
+#: VirtualMailManager/cli/subcommands.py:503
+#: VirtualMailManager/cli/subcommands.py:516
+#: VirtualMailManager/cli/subcommands.py:568
+#: VirtualMailManager/cli/subcommands.py:603
+msgid "Missing e-mail address."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:601
+#: VirtualMailManager/cli/subcommands.py:497
 #, python-format
-msgid "The destination account/alias “%s” doesn't exist."
+msgid "Generated password: %s"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:552
+msgid "alias addresses"
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:618
-#, python-format
-msgid ""
-"The account has been successfully deleted from the database.\n"
-"    But an error occurred while deleting the following directory:\n"
-"    “%(directory)s”\n"
-"    Reason: %(reason)s"
+#: VirtualMailManager/cli/subcommands.py:558
+msgid "Missing e-mail address and user's name."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:658
-msgid "Account doesn't exist"
+#: VirtualMailManager/cli/subcommands.py:561
+msgid "Missing user's name."
 msgstr ""
 
-#: VirtualMailManager/VirtualMailManager.py:674
-#: VirtualMailManager/VirtualMailManager.py:684
-msgid ""
-"The service name “managesieve” is deprecated and will be removed\n"
-"   in a future release.\n"
-"   Please use the service name “sieve” instead."
+#: VirtualMailManager/cli/subcommands.py:579
+msgid "Missing e-mail address and storage value."
 msgstr ""
 
-#: VirtualMailManager/ext/Postconf.py:41
-#, python-format
-msgid ""
-"The value “%s” doesn't look like a valid postfix configuration parameter "
-"name."
+#: VirtualMailManager/cli/subcommands.py:617
+msgid "Missing e-mail address and transport."
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:620
+msgid "Missing transport."
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:630
+msgid "usage: "
 msgstr ""
 
 #. TP: Please adjust translated words like the original text.
 #. (It's a table header.) Extract from usage text:
-#. Usage: vmm SUBCOMMAND OBJECT ARGS*
+#. usage: vmm subcommand arguments
 #. short long
-#. subcommand               object             args (* = optional)
+#. subcommand                arguments
 #.
-#. da    domainadd          domain.tld         transport*
-#. di    domaininfo         domain.tld         details*
-#: vmm:26
+#. da    domainadd           fqdn [transport]
+#. dd    domaindelete        fqdn [force]
+#: VirtualMailManager/cli/subcommands.py:640
 #, python-format
 msgid ""
-"Usage: %s SUBCOMMAND OBJECT ARGS*\n"
+"usage: %s subcommand arguments\n"
 "  short long\n"
-"  subcommand               object             args (* = optional)\n"
+"  subcommand                arguments\n"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "from"
+msgstr ""
+
+#. 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
+#: VirtualMailManager/cli/subcommands.py:659
+msgid "version"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:662
+msgid "on"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:664
+msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:672
+msgid "uid"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:673
+msgid "get the address of the user with the given UID"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:674
+#: VirtualMailManager/cli/subcommands.py:684
+msgid "address [password]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:675
+msgid "create a new e-mail user with the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:677
+#: VirtualMailManager/cli/subcommands.py:704
+#: VirtualMailManager/cli/subcommands.py:744
+#: VirtualMailManager/cli/subcommands.py:746
+msgid "address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:678
+msgid "delete the specified user"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:679
+msgid "address [details]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:680
+msgid "display information about the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:681
+msgid "address name"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:682
+msgid "set or update the real name for an address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:685
+msgid "update the password for the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:687
+msgid "address storage [messages]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:688
+msgid "update the quota limit for the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:690
+msgid "address [service ...]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:691
+msgid "enables the specified services and disables all not specified services"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:694
+msgid "address transport"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:695
+msgid "update the transport of the given address"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:697
+msgid "address destination ..."
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:698
+msgid "create a new alias e-mail address with one or more destinations"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:701
+msgid "address [destination]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:702
+msgid "delete the specified alias e-mail address or one of its destinations"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:705
+msgid "show the destination(s) of the specified alias"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:708
+#: VirtualMailManager/cli/subcommands.py:717
+msgid "fqdn destination"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:709
+msgid "create a new alias for an existing domain"
 msgstr ""
 
-#: vmm:65 vmm:76
-#, python-format
-msgid "Error: %s\n"
+#: VirtualMailManager/cli/subcommands.py:711
+#: VirtualMailManager/cli/subcommands.py:714
+#: VirtualMailManager/cli/subcommands.py:723
+msgid "fqdn"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:712
+msgid "delete the specified alias domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:715
+msgid "show the destination of the given alias domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:718
+msgid "assign the given alias domain to an other domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:720
+msgid "fqdn [transport]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:721
+msgid "create a new domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:724
+msgid "delete the given domain and all its alias domains"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:725
+msgid "fqdn [details]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:726
+msgid "display information about the given domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:728
+msgid "fqdn storage [messages]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:729
+msgid "update the quota limit of the specified domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:731
+msgid "fqdn [service ...]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:732
+msgid ""
+"enables the specified services and disables all not specified services of "
+"the given domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:735
+msgid "fqdn transport"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:736
+msgid "update the transport of the specified domain"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:737
+msgid "[pattern]"
 msgstr ""
 
-#. TP: e.g. 'Domain information' or 'Account information'
-#: vmm:104
+#: VirtualMailManager/cli/subcommands.py:738
+msgid "list all domains / search domains by pattern"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:741
+msgid "address newaddress"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:742
+msgid "create a new record for a relocated user"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:745
+msgid "delete the record of the relocated user"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:747
+msgid "print information about a relocated user"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:749
+msgid "option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:750
+msgid "show the actual value of the configuration option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:751
+msgid "option value"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:752
+msgid "set a new value for the configuration option"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:753
+msgid "[section]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:754
+msgid "start interactive configuration modus"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:756
+msgid "lists all usable password schemes and password encoding suffixes"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:758
+msgid "[subcommand]"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:759
+msgid "show a help overview or help for the given subcommand"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:761
+msgid "show version and copyright information"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:809
+#, python-format
+msgid "[%(percent)s%%] %(used)s/%(limit)s"
+msgstr ""
+
+#. TP: used in e.g. 'Domain information' or 'Account information'
+#: VirtualMailManager/cli/subcommands.py:815
 msgid "information"
 msgstr ""
 
-#. TP: e.g. 'Available alias addresses' or 'Available accounts'
-#: vmm:115
-msgid "Available"
+#. TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
+#: VirtualMailManager/cli/subcommands.py:828
+msgid "Existing"
 msgstr ""
 
-#: vmm:118 vmm:218 vmm:224
-msgid "alias domains"
-msgstr ""
-
-#: vmm:128 vmm:139 vmm:163
+#: VirtualMailManager/cli/subcommands.py:841
+#: VirtualMailManager/cli/subcommands.py:883
 msgid "\tNone"
 msgstr ""
 
-#: vmm:132
+#: VirtualMailManager/cli/subcommands.py:846
 msgid "Alias information"
 msgstr ""
 
-#: vmm:134
+#: VirtualMailManager/cli/subcommands.py:848
 #, python-format
 msgid "\tMail for %s will be redirected to:"
 msgstr ""
 
-#: vmm:143
+#: VirtualMailManager/cli/subcommands.py:855
 msgid "Relocated information"
 msgstr ""
 
-#: vmm:145
+#: VirtualMailManager/cli/subcommands.py:857
 #, python-format
-msgid "\tUser “%(addr)s” has moved to “%(dest)s”"
+msgid "\tUser '%(addr)s' has moved to '%(dest)s'"
 msgstr ""
 
-#: vmm:158
-msgid "Available domains"
-msgstr ""
-
-#: vmm:160
+#: VirtualMailManager/cli/subcommands.py:872
 msgid "Matching domains"
 msgstr ""
 
-#: vmm:174
+#: VirtualMailManager/cli/subcommands.py:874
+msgid "Existing domains"
+msgstr ""
+
+#: VirtualMailManager/cli/subcommands.py:889
 msgid "Alias domain information"
 msgstr ""
 
-#: vmm:180
+#: VirtualMailManager/cli/subcommands.py:894
 #, python-format
 msgid ""
 "\tThe alias domain %(alias)s belongs to:\n"
 "\t    * %(domain)s"
 msgstr ""
 
-#: vmm:191 vmm:199 vmm:207
-msgid "Missing domain name."
+#: VirtualMailManager/common.py:63
+#, python-format
+msgid "No such file: '%s'"
+msgstr ""
+
+#: VirtualMailManager/common.py:66
+#, python-format
+msgid "File is not executable: '%s'"
+msgstr ""
+
+#: VirtualMailManager/common.py:82
+msgid "GiB"
+msgstr ""
+
+#: VirtualMailManager/common.py:82
+msgid "TiB"
 msgstr ""
 
-#: vmm:210 vmm:214
-msgid "Domain"
+#: VirtualMailManager/common.py:83
+msgid "KiB"
+msgstr ""
+
+#: VirtualMailManager/common.py:83
+msgid "MiB"
 msgstr ""
 
-#: vmm:216 vmm:225
-msgid "accounts"
+#. TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
+#: VirtualMailManager/common.py:87
+#, python-format
+msgid "%(size)s %(prefix)s"
+msgstr ""
+
+#: VirtualMailManager/config.py:89
+#, python-format
+msgid "Not a boolean: '%s'"
 msgstr ""
 
-#: vmm:220 vmm:226
-msgid "aliases"
+#: VirtualMailManager/config.py:127
+#, python-format
+msgid "Bad format: '%s' - expected: section.option"
+msgstr ""
+
+#: VirtualMailManager/config.py:380
+#, python-format
+msgid "* Section: %s\n"
+msgstr ""
+
+#: VirtualMailManager/config.py:390 VirtualMailManager/config.py:398
+#, python-format
+msgid "Check of configuration file %s failed.\n"
+msgstr ""
+
+#: VirtualMailManager/config.py:392
+msgid "Missing options, which have no default value.\n"
 msgstr ""
 
-#: vmm:222 vmm:227
-msgid "relocated users"
+#: VirtualMailManager/config.py:400 VirtualMailManager/config.py:402
+msgid "Invalid configuration values.\n"
+msgstr ""
+
+#: VirtualMailManager/config.py:441 VirtualMailManager/config.py:525
+#, python-format
+msgid "Not a valid Dovecot version: '%s'"
 msgstr ""
 
-#: vmm:238
-msgid "Missing domain name and new transport."
+#: VirtualMailManager/config.py:447 VirtualMailManager/config.py:482
+#, python-format
+msgid "Unsupported database module: '%s'"
 msgstr ""
 
-#: vmm:240
-msgid "Missing new transport."
+#: VirtualMailManager/config.py:452 VirtualMailManager/config.py:490
+#, python-format
+msgid "Unknown pgsql SSL mode: '%s'"
+msgstr ""
+
+#: VirtualMailManager/config.py:459 VirtualMailManager/config.py:503
+#: VirtualMailManager/maillocation.py:70
+#, python-format
+msgid "Unsupported mailbox format: '%s'"
 msgstr ""
 
-#: vmm:249 vmm:272
-msgid "Missing alias domain name and target domain name."
+#: VirtualMailManager/config.py:475 VirtualMailManager/handler.py:283
+#: VirtualMailManager/handler.py:357 VirtualMailManager/handler.py:362
+#: VirtualMailManager/handler.py:390
+#, python-format
+msgid "No such directory: %s"
+msgstr ""
+
+#: VirtualMailManager/config.py:514
+#, python-format
+msgid "Not a valid size value: '%s'"
+msgstr ""
+
+#: VirtualMailManager/domain.py:78
+#, python-format
+msgid "The domain '%s' is an alias domain."
 msgstr ""
 
-#: vmm:251 vmm:274
-msgid "Missing target domain name."
+#: VirtualMailManager/domain.py:108
+#, python-format
+msgid ""
+"There are %(account_count)u accounts, %(alias_count)u aliases and "
+"%(relocated_count)u relocated users."
+msgstr ""
+
+#: VirtualMailManager/domain.py:123
+#, python-format
+msgid "The domain '%s' already exists."
 msgstr ""
 
-#: vmm:257 vmm:280
-msgid "Missing alias domain name."
+#: VirtualMailManager/domain.py:437
+msgid "The domain name is too long"
 msgstr ""
 
-#: vmm:286 vmm:295 vmm:303 vmm:345 vmm:353 vmm:361
-msgid "Missing e-mail address."
+#: VirtualMailManager/domain.py:439
+#, python-format
+msgid "The domain name '%s' is invalid"
+msgstr ""
+
+#: VirtualMailManager/emailaddress.py:73
+#, python-format
+msgid "Missing the '@' sign in address: '%s'"
 msgstr ""
 
-#: vmm:312
-msgid "alias addresses"
+#: VirtualMailManager/emailaddress.py:76
+#, python-format
+msgid "Too many '@' signs in address: '%s'"
 msgstr ""
 
-#: vmm:329
-msgid "Missing e-mail address and user’s name."
+#: VirtualMailManager/emailaddress.py:79
+#, python-format
+msgid "Missing local-part in address: '%s'"
+msgstr ""
+
+#: VirtualMailManager/emailaddress.py:82
+#, python-format
+msgid "Missing domain name in address: '%s'"
+msgstr ""
+
+#: VirtualMailManager/emailaddress.py:145
+#, python-format
+msgid "The local-part '%s' is too long."
 msgstr ""
 
-#: vmm:331
-msgid "Missing user’s name."
+#: VirtualMailManager/emailaddress.py:150
+#, python-format
+msgid "The local-part '%(l_part)s' contains invalid characters: %(i_chars)s"
 msgstr ""
 
-#: vmm:337
-msgid "Missing e-mail address and transport."
+#: VirtualMailManager/ext/postconf.py:84
+#, python-format
+msgid ""
+"The value '%s' does not look like a valid postfix configuration parameter "
+"name."
 msgstr ""
 
-#: vmm:339
-msgid "Missing transport."
+#: VirtualMailManager/handler.py:56
+msgid "an account"
+msgstr ""
+
+#: VirtualMailManager/handler.py:57
+msgid "an alias"
+msgstr ""
+
+#: VirtualMailManager/handler.py:58
+msgid "a relocated user"
 msgstr ""
 
-#: vmm:370
-msgid "Missing alias address and destination."
+#: VirtualMailManager/handler.py:84
+msgid ""
+"You are not root.\n"
+"\tGood bye!\n"
+msgstr ""
+
+#: VirtualMailManager/handler.py:104
+#, python-format
+msgid "Could not find '%(cfg_file)s' in: '%(cfg_path)s'"
+msgstr ""
+
+#: VirtualMailManager/handler.py:115
+#, python-format
+msgid ""
+"wrong permissions for '%(file)s': %(perms)s\n"
+"`chmod 0600 %(file)s` would be great."
 msgstr ""
 
-#: vmm:372 vmm:407
-msgid "Missing destination address."
+#: VirtualMailManager/handler.py:135
+#, python-format
+msgid ""
+"'%(path)s' is not a directory.\n"
+"(%(cfg_file)s: section 'misc', option 'base_directory')"
 msgstr ""
 
-#: vmm:378 vmm:396
-msgid "Missing alias address"
+#: VirtualMailManager/handler.py:144
+#, python-format
+msgid ""
+"\n"
+"(%(cfg_file)s: section 'bin', option '%(option)s')"
+msgstr ""
+
+#: VirtualMailManager/handler.py:158 VirtualMailManager/handler.py:165
+#, python-format
+msgid "Unable to import database module '%s'."
 msgstr ""
 
-#: vmm:405
-msgid "Missing relocated address and destination."
+#. TP: %(a_type)s will be one of: 'an account', 'an alias' or
+#. 'a relocated user'
+#: VirtualMailManager/handler.py:244
+#, python-format
+msgid "There is already %(a_type)s with the address '%(address)s'."
 msgstr ""
 
-#: vmm:413 vmm:431
-msgid "Missing relocated address"
+#: VirtualMailManager/handler.py:297
+#, python-format
+msgid "'%s' is not a directory."
+msgstr ""
+
+#: VirtualMailManager/handler.py:300
+#, python-format
+msgid "The file/directory '%s' already exists."
+msgstr ""
+
+#: VirtualMailManager/handler.py:329
+msgid "Skipped mailbox folders:"
 msgstr ""
 
-#: vmm:437
-msgid "Missing userid"
+#: VirtualMailManager/handler.py:349
+#, python-format
+msgid ""
+"UID '%(uid)u' and/or GID '%(gid)u' are less than %(min_uid)u/%(min_gid)u."
+msgstr ""
+
+#: VirtualMailManager/handler.py:354 VirtualMailManager/handler.py:387
+#, python-format
+msgid "Found \"..\" in domain directory path: %s"
 msgstr ""
 
-#: vmm:450
-msgid "Warnings:"
+#: VirtualMailManager/handler.py:367
+msgid "Detected owner/group mismatch in home directory."
 msgstr ""
 
-#: vmm:460
-msgid "from"
+#: VirtualMailManager/handler.py:383
+#, python-format
+msgid "GID '%(gid)u' is less than '%(min_gid)u'."
+msgstr ""
+
+#: VirtualMailManager/handler.py:394
+#, python-format
+msgid "Detected group mismatch in domain directory: %s"
 msgstr ""
 
-#. TP: The words 'from', 'version' and 'on' are used in the version
-#. information:
-#. vmm, version 0.5.2 (from 09/09/09)
-#. Python 2.5.4 on FreeBSD
-#: vmm:460
-msgid "version"
+#: VirtualMailManager/handler.py:470 VirtualMailManager/handler.py:748
+#, python-format
+msgid "Unknown service: '%s'"
+msgstr ""
+
+#: VirtualMailManager/handler.py:587
+#, python-format
+msgid "The pattern '%s' contains invalid characters."
+msgstr ""
+
+#: VirtualMailManager/handler.py:614
+msgid "Ignored destination addresses:"
 msgstr ""
 
-#: vmm:463
-msgid "on"
+#: VirtualMailManager/handler.py:619 VirtualMailManager/handler.py:769
+#, python-format
+msgid "The destination account/alias '%s' does not exist."
 msgstr ""
 
-#: vmm:464
-msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
+#: VirtualMailManager/handler.py:641
+#, python-format
+msgid ""
+"The account has been successfully deleted from the database.\n"
+"    But an error occurred while deleting the following directory:\n"
+"    '%(directory)s'\n"
+"    Reason: %(reason)s"
+msgstr ""
+
+#: VirtualMailManager/handler.py:712
+#, python-format
+msgid "Could not accept name: '%s'"
+msgstr ""
+
+#: VirtualMailManager/handler.py:735
+#, python-format
+msgid "Could not accept transport: '%s'"
 msgstr ""
 
-#: vmm:472
+#: VirtualMailManager/handler.py:779 VirtualMailManager/relocated.py:98
+#: VirtualMailManager/relocated.py:105
 #, python-format
-msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
+msgid "The relocated user '%s' does not exist."
 msgstr ""
 
-#: vmm:535
+#: VirtualMailManager/mailbox.py:260
 #, python-format
-msgid "Unknown subcommand: “%s”"
+msgid "Failed to create mailboxes: %r\n"
+msgstr ""
+
+#: VirtualMailManager/maillocation.py:74
+msgid "Empty directory name"
+msgstr ""
+
+#: VirtualMailManager/maillocation.py:76
+#, python-format
+msgid "Directory name is too long: '%s'"
 msgstr ""
 
-#. TP: We have to cry, because root has killed/interrupted vmm
-#. with Ctrl+C or Ctrl+D.
-#: vmm:540
-msgid ""
-"\n"
-"Ouch!\n"
+#: VirtualMailManager/password.py:388
+#, python-format
+msgid "Unsupported password scheme: '%s'"
+msgstr ""
+
+#: VirtualMailManager/password.py:391
+#, python-format
+msgid "The password scheme '%(scheme)s' requires Dovecot >= v%(version)s."
+msgstr ""
+
+#: VirtualMailManager/password.py:397
+msgid "Encoding suffixes for password schemes require Dovecot >= v1.1.alpha1."
 msgstr ""
 
-#: vmm:543
+#: VirtualMailManager/password.py:400
 #, python-format
-msgid "Error: %s"
+msgid "Unsupported password encoding: '%s'"
+msgstr ""
+
+#: VirtualMailManager/relocated.py:71
+msgid "Address and destination are identical."
 msgstr ""
+
+#: VirtualMailManager/relocated.py:75
+#, python-format
+msgid "The relocated user '%s' already exists."
+msgstr ""
--- a/postfix/pgsql-relocated_maps.cf	Mon Nov 07 03:22:15 2011 +0000
+++ b/postfix/pgsql-relocated_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -11,4 +11,4 @@
 dbname = mailsys
 
 # The SQL query template used to search the database
-query = SELECT destination FROM postfix_relocated WHERE address='%s'
+query = SELECT destination FROM postfix_relocated_map('%u', '%d')
--- a/postfix/pgsql-smtpd_sender_login_maps.cf	Mon Nov 07 03:22:15 2011 +0000
+++ b/postfix/pgsql-smtpd_sender_login_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -15,4 +15,4 @@
 # 	* line 26: function postfix_smtpd_sender_login_map + comment above
 #
 # The SQL query template used to search the database
-#query = SELECT login FROM postfix_smtpd_sender_login_map('%u', '%d')
+query = SELECT login FROM postfix_smtpd_sender_login_map('%u', '%d')
--- a/postfix/pgsql-transport.cf	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# All parameters are described in pgsql_table(5) / PGSQL PARAMETERS
-#
-# The hosts that Postfix will try to connect to and query from.
-hosts = localhost
-
-# The user name and password to log into the pgsql server.
-user = postfix
-password = some_password
-
-# The database name on the servers.
-dbname = mailsys
-
-# The SQL query template used to search the database
-query = SELECT transport FROM postfix_transport WHERE address='%s'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/postfix/pgsql-transport_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,14 @@
+# All parameters are described in pgsql_table(5) / PGSQL PARAMETERS
+#
+# The hosts that Postfix will try to connect to and query from.
+hosts = localhost
+
+# The user name and password to log into the pgsql server.
+user = postfix
+password = some_password
+
+# The database name on the servers.
+dbname = mailsys
+
+# The SQL query template used to search the database
+query = SELECT transport FROM postfix_transport_map('%u', '%d')
--- a/postfix/pgsql-virtual_alias_maps.cf	Mon Nov 07 03:22:15 2011 +0000
+++ b/postfix/pgsql-virtual_alias_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -11,4 +11,4 @@
 dbname = mailsys
 
 # The SQL query template used to search the database
-query = SELECT destination FROM postfix_alias WHERE address='%s'
+query = SELECT destination FROM postfix_virtual_alias_map('%u', '%d')
--- a/postfix/pgsql-virtual_mailbox_maps.cf	Mon Nov 07 03:22:15 2011 +0000
+++ b/postfix/pgsql-virtual_mailbox_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -11,4 +11,4 @@
 dbname = mailsys
 
 # The SQL query template used to search the database
-query = SELECT maildir FROM postfix_maildir WHERE address='%s'
+query = SELECT maildir FROM postfix_virtual_mailbox_map('%u', '%d')
--- a/postfix/pgsql-virtual_uid_maps.cf	Mon Nov 07 03:22:15 2011 +0000
+++ b/postfix/pgsql-virtual_uid_maps.cf	Thu Jun 28 19:26:50 2012 +0000
@@ -11,4 +11,4 @@
 dbname = mailsys
 
 # The SQL query template used to search the database
-query = SELECT uid FROM postfix_uid WHERE address='%s'
+query = SELECT uid FROM postfix_virtual_uid_map('%u', '%d')
--- a/setup.py	Mon Nov 07 03:22:15 2011 +0000
+++ b/setup.py	Thu Jun 28 19:26:50 2012 +0000
@@ -1,55 +1,67 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Copyright 2007 - 2010, Pascal Volk
+# Copyright 2007 - 2012, Pascal Volk
 # See COPYING for distribution information.
 
 import os
 from distutils.core import setup
+from distutils.dist import DistributionMetadata
 
 VERSION = '0.5.2'
 
+descr = 'Tool to manage mail domains/accounts/aliases for Dovecot and Postfix'
 long_description = """
 vmm, a virtual mail manager, is a command line tool for
 administrators/postmasters to manage (alias-)domains, accounts,
 aliases and relocated users.
 It is designed for Dovecot and Postfix with a PostgreSQL backend.
 """
+packages = [
+    'VirtualMailManager',
+    'VirtualMailManager.cli',
+    'VirtualMailManager.ext',
+    'VirtualMailManager.pycompat',
+]
+# http://pypi.python.org/pypi?%3Aaction=list_classifiers
+classifiers = ['Development Status :: 5 - Production/Stable',
+               'Environment :: Console',
+               'Intended Audience :: System Administrators',
+               'License :: OSI Approved :: BSD License',
+               'Natural Language :: Dutch',
+               'Natural Language :: English',
+               'Natural Language :: French',
+               'Natural Language :: German',
+               'Operating System :: POSIX',
+               'Operating System :: POSIX :: BSD',
+               'Operating System :: POSIX :: Linux',
+               'Operating System :: POSIX :: Other',
+               'Programming Language :: Python',
+               'Programming Language :: Python :: 2',
+               'Topic :: Communications :: Email',
+               'Topic :: System :: Systems Administration',
+               'Topic :: Utilities']
+
+# sucessfuly tested on:
+platforms = ['freebsd7', 'linux2', 'openbsd4']
 
 # remove existing MANIFEST
 if os.path.exists('MANIFEST'):
     os.remove('MANIFEST')
 
+setup_args = {'name': 'VirtualMailManager',
+              'version': VERSION,
+              'description': descr,
+              'long_description': long_description,
+              'packages': packages,
+              'author': 'Pascal Volk',
+              'author_email': 'user+vmm@localhost.localdomain.org',
+              'license': 'BSD License',
+              'url': 'http://vmm.localdomain.org/',
+              'download_url':'http://sf.net/projects/vmm/files/',
+              'platforms': platforms,
+              'classifiers': classifiers}
 
-setup(name='VirtualMailManager',
-      version=VERSION,
-      description='Tool to manage mail domains/accounts/aliases for Dovecot and Postfix',
-      long_description=long_description,
-      packages=['VirtualMailManager', 'VirtualMailManager.ext',
-          'VirtualMailManager.constants'],
-      author='Pascal Volk',
-      author_email='neverseen@users.sourceforge.net',
-      license='BSD License',
-      url='http://vmm.localdomain.org/',
-      download_url='http://sf.net/projects/vmm/files/',
-      platforms=['freebsd7', 'linux2', 'openbsd4'],
-      classifiers=[
-          'Development Status :: 4 - Beta',
-          'Development Status :: 5 - Production/Stable',
-          'Environment :: Console',
-          'Intended Audience :: System Administrators',
-          'License :: OSI Approved :: BSD License',
-          'Natural Language :: Dutch',
-          'Natural Language :: English',
-          'Natural Language :: French',
-          'Natural Language :: German',
-          'Operating System :: POSIX',
-          'Operating System :: POSIX :: BSD',
-          'Operating System :: POSIX :: Linux',
-          'Operating System :: POSIX :: Other',
-          'Programming Language :: Python',
-          'Topic :: Communications :: Email',
-          'Topic :: System :: Systems Administration',
-          'Topic :: Utilities'
-      ],
-      requires=['pyPgSQL']
-      )
+if 'requires' in DistributionMetadata._METHOD_BASENAMES:
+    setup_args['requires'] = ['psycopg2 (>=2.0)', 'pyPgSQL (>=2.5.1)']
+
+setup(**setup_args)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/update_config.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+
+import os
+os.sys.path.remove(os.sys.path[0])
+from time import time
+from ConfigParser import ConfigParser
+from shutil import copy2
+try:
+    from VirtualMailManager.constants.VERSION import VERSION
+except ImportError:
+    os.sys.stderr.write('error: no pre 0.6.0 version information found\n')
+    raise SystemExit(2)
+
+# we have to remove the old CamelCase files
+import VirtualMailManager
+vmm_inst_dir = os.path.dirname(VirtualMailManager.__file__)
+tmp_info = open('/tmp/vmm_inst_dir', 'w')
+tmp_info.write(vmm_inst_dir)
+tmp_info.close()
+
+try:
+    import psycopg2
+except ImportError:
+    has_psycopg2 = False
+else:
+    has_psycopg2 = True
+
+def get_config_file():
+    f = None
+    for d in ('/root', '/usr/local/etc', '/etc'):
+        tmp = os.path.join(d, 'vmm.cfg')
+        if os.path.isfile(tmp):
+            f = tmp
+            break
+    if f:
+        return f
+    else:
+        os.sys.stderr.write('error: vmm.cfg not found\n')
+        raise SystemExit(2)
+
+def update(cp):
+    if VERSION == '0.5.2':
+        upd_052(cp)
+    elif VERSION == '0.6.0':
+        os.sys.stdout.write('info: nothing to do for version %s\n' % VERSION)
+        return
+    else:
+        os.sys.stderr.write(
+            'error: the version %s is not supported by this script\n' % VERSION)
+        raise SystemExit(3)
+
+def get_cfg_parser(cf):
+    fh = open(cf, 'r')
+    cp = ConfigParser()
+    cp.readfp(fh)
+    fh.close()
+    return cp
+
+def update_cfg_file(cp, cf):
+    copy2(cf, cf+'.bak.'+str(time()))
+    fh = open(cf, 'w')
+    cp.write(fh)
+    fh.close()
+
+def add_sections(cp, sections):
+    for section in sections:
+        if not cp.has_section(section):
+            cp.add_section(section)
+
+def move_option(cp, src, dst):
+    ds, do = dst.split('.')
+    if not cp.has_option(ds, do):
+        ss, so = src.split('.')
+        cp.set(ds, do, cp.get(ss, so))
+        cp.remove_option(ss, so)
+        sect_opt.append((dst, 'R'))
+
+def add_option(cp, dst, val):
+    ds, do = dst.split('.')
+    if not cp.has_option(ds, do):
+        cp.set(ds, do, val)
+        sect_opt.append((dst, 'N'))
+
+
+def set_dovecot_version(cp):
+    if len(os.sys.argv) > 1:
+        dovecot_version = os.sys.argv[1].strip()
+        if not dovecot_version:
+            dovecot_version = '1.2.11'
+    else:
+        dovecot_version = '1.2.11'
+    cp.set('misc', 'dovecot_version', dovecot_version)
+    sect_opt.append(('misc.dovecot_version', 'M'))
+
+
+def get_option(cp, src):
+    ss, so = src.split('.')
+    return cp.get(ss, so)
+
+def upd_052(cp):
+    global had_config
+    global had_gid_mail
+
+    had_config = cp.remove_section('config')
+    had_gid_mail = cp.remove_option('misc', 'gid_mail')
+    add_sections(cp, ('domain', 'account', 'mailbox'))
+    if cp.has_section('domdir'):
+        for src, dst in (('domdir.mode',   'domain.directory_mode'),
+                         ('domdir.delete', 'domain.delete_directory'),
+                         ('domdir.base',   'misc.base_directory')):
+            move_option(cp, src, dst)
+        cp.remove_section('domdir')
+    if cp.has_section('services'):
+        for service in cp.options('services'):
+            move_option(cp, 'services.%s' % service, 'domain.%s' % service)
+        cp.remove_section('services')
+    for src, dst in (('maildir.mode',      'account.directory_mode'),
+                     ('maildir.diskusage', 'account.disk_usage'),
+                     ('maildir.delete',    'account.delete_directory'),
+                     ('maildir.folders',   'mailbox.folders'),
+                     ('maildir.name',      'mailbox.root'),
+                     ('misc.forcedel',     'domain.force_deletion'),
+                     ('misc.transport',    'domain.transport'),
+                     ('misc.passwdscheme', 'misc.password_scheme'),
+                     ('misc.dovecotvers',  'misc.dovecot_version')):
+        move_option(cp, src, dst)
+    cp.remove_section('maildir')
+    if not has_psycopg2:
+        add_option(cp, 'database.module', 'pyPgSQL')
+    set_dovecot_version(cp)
+
+
+# def main():
+if __name__ == '__main__':
+    sect_opt = []
+    had_config = False
+    had_gid_mail = False
+    cf = get_config_file()
+    cp = get_cfg_parser(cf)
+    update(cp)
+    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:'
+        for s_o, st in sect_opt:
+            print '%s   %s = %s' % (st, s_o, get_option(cp, s_o))
+        if had_config:
+            print '\nRemoved section "config" with option "done" (obsolte)'
+        if had_gid_mail:
+            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)'
+        if had_gid_mail:
+            print '\nRemoved option "gid_mail" from section "misc" (obsolte)\n'
--- a/update_config_0.4.x-0.5.py	Mon Nov 07 03:22:15 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2008 - 2010, Pascal Volk
-# See COPYING for distribution information.
-
-import os
-os.sys.path.remove(os.sys.path[0])
-from time import time
-from ConfigParser import ConfigParser
-from shutil import copy2
-from VirtualMailManager.constants.VERSION import VERSION
-
-
-def get_config_file():
-    f = None
-    for d in ('/root', '/usr/local/etc', '/etc'):
-        tmp = os.path.join(d, 'vmm.cfg')
-        if os.path.isfile(tmp):
-            f = tmp
-            break
-    if f:
-        return f
-    else:
-        os.sys.stderr.write('error: vmm.cfg not found\n')
-        os.sys.exit(2)
-
-def update(cp):
-    if VERSION == '0.4':
-        upd_040(cp)
-    elif VERSION == '0.5':
-        upd_050(cp)
-    elif VERSION == '0.5.1':
-        upd_051(cp)
-    elif VERSION == '0.5.2':
-        os.sys.stdout.write('info: nothing to do for version %s\n' % VERSION)
-        os.sys.exit(0)
-    else:
-        os.sys.stderr.write(
-            'error: the version %s is not supported by this script\n' % VERSION)
-        os.sys.exit(3)
-
-def get_cfg_parser(cf):
-    fh = file(cf, 'r')
-    cp = ConfigParser()
-    cp.readfp(fh)
-    fh.close()
-    return cp
-
-def update_cfg_file(cp, cf):
-    copy2(cf, cf+'.bak.'+str(time()))
-    fh = file(cf, 'w')
-    cp.write(fh)
-    fh.close()
-
-def upd_040(cp):
-    if not cp.has_option('maildir', 'name') or not cp.has_option('maildir',
-        'folders') or cp.has_option('maildir', 'folder'):
-        if not cp.has_option('maildir', 'name'):
-            if cp.has_option('maildir', 'folder'):
-                cp.set('maildir', 'name', cp.get('maildir', 'folder'))
-                cp.remove_option('maildir', 'folder')
-                sect_opt.append(('maildir', 'name'))
-            else:
-                cp.set('maildir', 'name', 'Maildir')
-                sect_opt.append(('maildir', 'name'))
-        if not cp.has_option('maildir', 'folders'):
-            cp.set('maildir', 'folders', 'Drafts:Sent:Templates:Trash')
-            sect_opt.append(('maildir', 'folders'))
-        if cp.has_option('maildir', 'folder'):
-            cp.remove_option('maildir', 'folder')
-    upd_050(cp)
-
-def upd_050(cp):
-    if not cp.has_option('bin', 'postconf'):
-        try:
-            postconf = os.sys.argv[1].strip()
-            if len(postconf):
-                cp.set('bin', 'postconf', postconf)
-                sect_opt.append(('bin', 'postconf'))
-            else: # possible?
-                cp.set('bin', 'postconf', '/usr/sbin/postconf')
-                sect_opt.append(('bin', 'postconf'))
-        except IndexError:
-            cp.set('bin', 'postconf', '/usr/sbin/postconf')
-            sect_opt.append(('bin', 'postconf'))
-    upd_051(cp)
-
-def upd_051(cp):
-    if not cp.has_option('misc', 'dovecotvers') or cp.has_option('services',
-            'managesieve'):
-        if not cp.has_option('misc', 'dovecotvers'):
-            cp.set('misc', 'dovecotvers', os.sys.argv[2].strip())
-            sect_opt.append(('misc', 'dovecotvers'))
-        if cp.has_option('services', 'managesieve'):
-            cp.set('services','sieve',cp.getboolean('services', 'managesieve'))
-            cp.remove_option('services', 'managesieve')
-            sect_opt.append(('services', 'sieve'))
-
-# def main():
-if __name__ == '__main__':
-    sect_opt = []
-    cf = get_config_file()
-    cp = get_cfg_parser(cf)
-    update(cp)
-    if len(sect_opt): 
-        update_cfg_file(cp, cf)
-        print 'Please have a look at your configuration: %s' %cf
-        print 'and verify the value from:'
-        for s_o in sect_opt:
-            print '  [%s] %s' % s_o
-        print
-
-
--- a/upgrade.sh	Mon Nov 07 03:22:15 2011 +0000
+++ b/upgrade.sh	Thu Jun 28 19:26:50 2012 +0000
@@ -7,10 +7,7 @@
 PATH=/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
 PREFIX=/usr/local
 
-PF_CONFDIR=$(postconf -h config_directory)
-PF_GID=$(id -g $(postconf -h mail_owner))
-POSTCONF=$(which postconf)
-DOVECOT_VERS=$(dovecot --version | awk -F . '{print $1 $2}')
+DOVECOT_VERS=$(dovecot --version | awk '{print $1}')
 LOCALE_DIR=${PREFIX}/share/locale
 DOC_DIR=${PREFIX}/share/doc/vmm
 if [ ${PREFIX} = "/usr" ]; then
@@ -18,10 +15,9 @@
 else
     MANDIR=${PREFIX}/man
 fi
-DOCS="ChangeLog COPYING NEWS INSTALL README"
+DOCS="ChangeLog Configure.Dovecot_2 COPYING INSTALL NEWS README"
 
 INSTALL_OPTS="-g 0 -o 0 -p"
-INSTALL_OPTS_CF="-b -m 0640 -g ${PF_GID} -o 0 -p"
 
 if [ $(id -u) -ne 0 ]; then
     echo "Run this script as root."
@@ -29,7 +25,7 @@
 fi
 
 # update config file before installing the new files.
-./update_config_0.4.x-0.5.py ${POSTCONF} ${DOVECOT_VERS:-10}
+./update_config.py ${DOVECOT_VERS:-'1.2.11'}
 rv=$?
 if [ $rv -eq 2 ]; then
 	echo "please run the install.sh script"
@@ -38,12 +34,18 @@
     echo "please read the upgrade instructions at http://vmm.localdomain.org/"
     exit 1
 elif [ $rv -ne 0 ]; then
-    echo "Sorry, something went wrong. Please file a bug:"
-    echo "https://sourceforge.net/tracker/?group_id=213727"
+    echo "Sorry, something went wrong. Please file a bug at:"
+    echo "https://bitbucket.org/pvo/vmm/issues"
     exit 1
 fi
 
-python setup.py -q install --prefix ${PREFIX}
+# remove old CamelCase files
+if [ -f /tmp/vmm_inst_dir ] ; then
+    rm -rf `cat /tmp/vmm_inst_dir`
+    rm -f /tmp/vmm_inst_dir
+fi
+
+python setup.py -q install --force --prefix ${PREFIX}
 python setup.py clean --all >/dev/null
 
 install -m 0700 ${INSTALL_OPTS} vmm ${PREFIX}/sbin
@@ -58,14 +60,6 @@
 done
 cd - >/dev/null
 
-# remove misplaced manual pages
-if [ -f /usr/local/share/man/man1/vmm.1 ]; then
-    rm -f /usr/local/share/man/man1/vmm.1
-fi
-if [ -f /usr/local/share/man/man5/vmm.cfg.5 ]; then
-    rm -f /usr/local/share/man/man5/vmm.cfg.5
-fi
-
 # install manual pages
 cd man
 [ -d ${MANDIR}/man1 ] || mkdir -m 0755 -p ${MANDIR}/man1
@@ -74,7 +68,7 @@
 [ -d ${MANDIR}/man5 ] || mkdir -m 0755 -p ${MANDIR}/man5
 install -m 0644 ${INSTALL_OPTS} man5/vmm.cfg.5 ${MANDIR}/man5
 
-for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\? \! -name .svn)
+for l in $(find . -maxdepth 1 -mindepth 1 -type d \! -name man\?)
 do
     for s in man1 man5; do
         [ -d ${MANDIR}/${l}/${s} ] || mkdir -m 0755 -p ${MANDIR}/${l}/${s}
--- a/vmm	Mon Nov 07 03:22:15 2011 +0000
+++ b/vmm	Thu Jun 28 19:26:50 2012 +0000
@@ -1,545 +1,18 @@
 #!/usr/bin/env python
 # -*- coding: UTF-8 -*-
-# Copyright 2007 - 2010, Pascal Volk
+# Copyright 2007 - 2012, Pascal Volk
 # See COPYING for distribution information.
 
 """This is the vmm main script."""
 
-import gettext
-from time import strftime, strptime
-
-from VirtualMailManager import *
-from VirtualMailManager.VirtualMailManager import VirtualMailManager
-import VirtualMailManager.Exceptions as VMME
-import VirtualMailManager.constants.EXIT as EXIT
-
-
-def usage(excode=0, errMsg=None):
-    # TP: Please adjust translated words like the original text.
-    # (It's a table header.) Extract from usage text:
-    # Usage: vmm SUBCOMMAND OBJECT ARGS*
-    #  short long
-    #  subcommand               object             args (* = optional)
-    #
-    #  da    domainadd          domain.tld         transport*
-    #  di    domaininfo         domain.tld         details*
-    u_head = _(u"""\
-Usage: %s SUBCOMMAND OBJECT ARGS*
-  short long
-  subcommand               object             args (* = optional)\n""")\
-          % __prog__
-
-    u_body = u"""\
-  da    domainadd          domain.tld         transport*
-  di    domaininfo         domain.tld         details*
-  dt    domaintransport    domain.tld         transport force*
-  dd    domaindelete       domain.tld         delalias*|deluser*|delall*
-  ada   aliasdomainadd     aliasdomain.tld    domain.tld
-  adi   aliasdomaininfo    aliasdomain.tld
-  ads   aliasdomainswitch  aliasdomain.tld    domain.tld
-  add   aliasdomaindelete  aliasdomain.tld
-  ua    useradd            user@domain.tld    password*
-  ui    userinfo           user@domain.tld    details*
-  un    username           user@domain.tld    "user’s name"
-  up    userpassword       user@domain.tld    password*
-  ut    usertransport      user@domain.tld    transport
-  u0    userdisable        user@domain.tld    service*
-  u1    userenable         user@domain.tld    service*
-  ud    userdelete         user@domain.tld    delalias*
-  aa    aliasadd           alias@domain.tld   user@domain.tld
-  ai    aliasinfo          alias@domain.tld
-  ad    aliasdelete        alias@domain.tld   user@domain.tld*
-  ra    relocatedadd       exaddr@domain.tld  user@domain.tld
-  ri    relocatedinfo      exaddr@domain.tld
-  rf    relocateddelete    exaddr@domain.tld
-  gu    getuser            userid
-  ld    listdomains                           pattern*
-  cf    configure                             section*
-  h     help
-  v     version
-"""
-    if excode > 0:
-        if errMsg is None:
-            w_err(excode, u_head, u_body)
-        else:
-            w_err(excode, u_head, u_body, _(u'Error: %s\n') % errMsg)
-    else:
-        w_std(u_head, u_body)
-        os.sys.exit(excode)
-
-def get_vmm():
-    try:
-        vmm = VirtualMailManager()
-        return vmm
-    except (VMME.VMMException, VMME.VMMNotRootException, VMME.VMMPermException,
-            VMME.VMMConfigException), e:
-        w_err(e.code(), _(u'Error: %s\n') % e.msg())
-
-def _getOrder():
-    order = ()
-    if vmm.cfgGetInt('misc', 'dovecotvers') > 11:
-        sieve_name = u'sieve'
-    else:
-        sieve_name = u'managesieve'
-    if argv[1] in (u'di', u'domaininfo'):
-        order = ((u'domainname', 0), (u'gid', 1), (u'transport', 0),
-                (u'domaindir', 0), (u'aliasdomains', 0), (u'accounts', 0),
-                (u'aliases', 0), (u'relocated', 0))
-    elif argv[1] in (u'ui', u'userinfo'):
-        if argc == 4 and argv[3] != u'aliases'\
-        or vmm.cfgGetBoolean('maildir', 'diskusage'):
-            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
-                    (u'transport', 0), (u'maildir', 0), (u'disk usage', 0),
-                    (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve_name, 1))
-        else:
-            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
-                    (u'transport', 0), (u'maildir', 0), (u'smtp', 1),
-                    (u'pop3', 1), (u'imap', 1), (sieve_name, 1))
-    elif argv[1] in (u'gu', u'getuser'):
-        order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
-    return order
-
-def _printInfo(info, title):
-    # TP: e.g. 'Domain information' or 'Account information'
-    msg = u'%s %s' % (title, _(u'information'))
-    w_std (u'%s\n%s' % (msg, u'-'*len(msg)))
-    for k,u in _getOrder():
-        if u:
-            w_std(u'\t%s: %s' % (k.upper().ljust(15, u'.'), info[k]))
-        else:
-            w_std(u'\t%s: %s' % (k.title().ljust(15, u'.'), info[k]))
-    print
-
-def _printList(alist, title):
-    # TP: e.g. 'Available alias addresses' or 'Available accounts'
-    msg = u'%s %s' % (_(u'Available'), title)
-    w_std(u'%s\n%s' % (msg, u'-'*len(msg)))
-    if len(alist) > 0:
-        if title != _(u'alias domains'):
-            for val in alist:
-                w_std(u'\t%s' % val)
-        else:
-            for dom in alist:
-                if not dom.startswith('xn--'):
-                    w_std(u'\t%s' % dom)
-                else:
-                    w_std(u'\t%s (%s)' % (dom, vmm.ace2idna(dom)))
-    else:
-        w_std(_(u'\tNone'))
-    print
-
-def _printAliases(alias, targets):
-    msg = _(u'Alias information')
-    w_std(u'%s\n%s' % (msg, u'-'*len(msg)))
-    w_std(_(u'\tMail for %s will be redirected to:') % alias)
-    if len(targets) > 0:
-        for target in targets:
-            w_std(u'\t     * %s' % target)
-    else:
-        w_std(_(u'\tNone'))
-    print
-
-def _printRelocated(addr_dest):
-    msg = _(u'Relocated information')
-    w_std(u'%s\n%s' % (msg, u'-'*len(msg)))
-    w_std(_(u'\tUser “%(addr)s” has moved to “%(dest)s”') % addr_dest)
-    print
-
-def _formatDom(domain, main=True):
-    if domain.startswith('xn--'):
-        domain = u'%s (%s)' % (domain, vmm.ace2idna(domain))
-    if main:
-        return u'\t[+] %s' %  domain
-    else:
-        return u'\t[-]     %s' % domain
-
-def _printDomList(dids, domains):
-    if argc < 3:
-        msg = _('Available domains')
-    else:
-        msg = _('Matching domains')
-    w_std('%s\n%s' % (msg, '-'*len(msg)))
-    if not len(domains):
-        w_std(_('\tNone'))
-    else:
-        for id in dids:
-            if domains[id][0] is not None:
-                w_std(_formatDom(domains[id][0]))
-            if len(domains[id]) > 1:
-                for alias in domains[id][1:]:
-                    w_std(_formatDom(alias, main=False))
-    print
-
-def _printAliasDomInfo(info):
-    msg = _('Alias domain information')
-    for k in ['alias', 'domain']:
-        if info[k].startswith('xn--'):
-            info[k] = "%s (%s)" % (info[k], vmm.ace2idna(info[k]))
-    w_std('%s\n%s' % (msg, '-'*len(msg)))
-    w_std(
-        _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s')%info)
-    print
-
-def configure():
-    if need_setup or len(argv) < 3:
-        vmm.configure()
-    else:
-        vmm.configure(argv[2])
-
-def domain_add():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing domain name.'))
-    elif argc < 4:
-        vmm.domainAdd(argv[2].lower())
-    else:
-        vmm.domainAdd(argv[2].lower(), argv[3])
-
-def domain_delete():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing domain name.'))
-    elif argc < 4:
-        vmm.domainDelete(argv[2].lower())
-    else:
-        vmm.domainDelete(argv[2].lower(), argv[3])
-
-def domain_info():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing domain name.'))
-    try:
-        if argc < 4:
-            _printInfo(vmm.domainInfo(argv[2].lower()), _(u'Domain'))
-        else:
-            details = argv[3].lower()
-            infos = vmm.domainInfo(argv[2].lower(), details)
-            _printInfo(infos[0], _(u'Domain'))
-            if details == u'accounts':
-                _printList(infos[1], _(u'accounts'))
-            elif details == u'aliasdomains':
-                _printList(infos[1], _(u'alias domains'))
-            elif details == u'aliases':
-                _printList(infos[1], _(u'aliases'))
-            elif details == u'relocated':
-                _printList(infos[1], _(u'relocated users'))
-            else:
-                _printList(infos[1], _(u'alias domains'))
-                _printList(infos[2], _(u'accounts'))
-                _printList(infos[3], _(u'aliases'))
-                _printList(infos[4], _(u'relocated users'))
-    except VMME.VMMDomainException, e:
-        if e.code() is ERR.DOMAIN_ALIAS_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'aliasdomaininfo',
-                'object': argv[2].lower()})
-            alias_domain_info()
-        else:
-            raise e
-
-def domain_transport():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing domain name and new transport.'))
-    if argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing new transport.'))
-    elif argc < 5:
-        vmm.domainTransport(argv[2].lower(), argv[3])
-    else:
-        vmm.domainTransport(argv[2].lower(), argv[3], argv[4])
-
-def alias_domain_add():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS,
-                _(u'Missing alias domain name and target domain name.'))
-    elif argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing target domain name.'))
-    else:
-        vmm.aliasDomainAdd(argv[2].lower(), argv[3].lower())
-
-def alias_domain_info():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing alias domain name.'))
-    try:
-        _printAliasDomInfo(vmm.aliasDomainInfo(argv[2].lower()))
-    except VMME.VMMAliasDomainException, e:
-        if e.code() is ERR.ALIASDOMAIN_ISDOMAIN:
-            w_std(plan_a_b % {'subcommand': u'domaininfo',
-                'object': argv[2].lower()})
-            argv[1] = u'di' # necessary manipulation to get the order
-            domain_info()
-        else:
-            raise e
+import sys
 
-def alias_domain_switch():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS,
-                _(u'Missing alias domain name and target domain name.'))
-    elif argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing target domain name.'))
-    else:
-        vmm.aliasDomainSwitch(argv[2].lower(), argv[3].lower())
-
-def alias_domain_delete():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing alias domain name.'))
-    else:
-        vmm.aliasDomainDelete(argv[2].lower())
-
-def user_add():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    elif argc < 4:
-        password = None
-    else:
-        password = argv[3]
-    vmm.userAdd(argv[2].lower(), password)
-
-def user_delete():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    elif argc < 4:
-        vmm.userDelete(argv[2].lower())
-    else:
-        vmm.userDelete(argv[2].lower(), argv[3].lower())
-
-def user_info():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    try:
-        if argc < 4:
-            _printInfo(vmm.userInfo(argv[2].lower()), u'Account')
-        else:
-            arg3 = argv[3].lower()
-            info = vmm.userInfo(argv[2].lower(), arg3)
-            if arg3 in ['aliases', 'full']:
-                _printInfo(info[0], u'Account')
-                _printList(info[1], _(u'alias addresses'))
-            else:
-                _printInfo(info, u'Account')
-    except VMME.VMMAccountException, e:
-        if e.code() is ERR.ALIAS_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'aliasinfo',
-                'object': argv[2].lower()})
-            alias_info()
-        elif e.code() is ERR.RELOCATED_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'relocatedinfo',
-                'object': argv[2].lower()})
-            relocated_info()
-        else:
-            raise e
-
-def user_name():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address and user’s name.'))
-    if argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing user’s name.'))
-    else:
-        vmm.userName(argv[2].lower(), argv[3])
-
-def user_transport():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address and transport.'))
-    if argc <4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing transport.'))
-    else:
-        vmm.userTransport(argv[2].lower(), argv[3])
-
-def user_enable():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    elif argc < 4:
-        vmm.userEnable(argv[2].lower())
-    else:
-        vmm.userEnable(argv[2].lower(), argv[3].lower())
-
-def user_disable():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    elif argc < 4:
-        vmm.userDisable(argv[2].lower())
-    else:
-        vmm.userDisable(argv[2].lower(), argv[3].lower())
-
-def user_password():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.'))
-    elif argc < 4:
-        password = None
-    else:
-        password = argv[3]
-    vmm.userPassword(argv[2].lower(), password)
-
-def alias_add():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing alias address and destination.'))
-    elif argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing destination address.'))
-    else:
-        vmm.aliasAdd(argv[2].lower(), argv[3])
-
-def alias_info():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing alias address'))
-    try:
-        _printAliases(argv[2].lower(), vmm.aliasInfo(argv[2].lower()))
-    except VMME.VMMAliasException, e:
-        if e.code() is ERR.ACCOUNT_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'userinfo',
-                'object': argv[2].lower()})
-            argv[1] = u'ui' # necessary manipulation to get the order
-            user_info()
-        elif e.code() is ERR.RELOCATED_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'relocatedinfo',
-                'object': argv[2].lower()})
-            relocated_info()
-        else:
-            raise e
-
-def alias_delete():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing alias address'))
-    elif argc < 4:
-        vmm.aliasDelete(argv[2].lower())
-    else:
-        vmm.aliasDelete(argv[2].lower(), argv[3].lower())
-
-def relocated_add():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS,
-                _(u'Missing relocated address and destination.'))
-    elif argc < 4:
-        usage(EXIT.MISSING_ARGS, _(u'Missing destination address.'))
-    else:
-        vmm.relocatedAdd(argv[2].lower(), argv[3])
-
-def relocated_info():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing relocated address'))
-    relocated = argv[2].lower()
-    try:
-        _printRelocated({'addr': relocated,
-            'dest': vmm.relocatedInfo(relocated)})
-    except VMME.VMMRelocatedException, e:
-        if e.code() is ERR.ACCOUNT_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'userinfo', 'object': relocated})
-            argv[1] = u'ui' # necessary manipulation to get the order
-            user_info()
-        elif e.code() is ERR.ALIAS_EXISTS:
-            w_std(plan_a_b % {'subcommand': u'aliasinfo', 'object': relocated})
-            alias_info()
-        else:
-            raise e
-
-def relocated_delete():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing relocated address'))
-    else:
-        vmm.relocatedDelete(argv[2].lower())
-
-def user_byID():
-    if argc < 3:
-        usage(EXIT.MISSING_ARGS, _(u'Missing userid'))
-    else:
-        _printInfo(vmm.userByID(argv[2]), u'Account')
-
-def domain_list():
-    if argc < 3:
-        order, doms = vmm.domainList()
-    else:
-        order, doms = vmm.domainList(argv[2].lower())
-    _printDomList(order, doms)
-
-def show_warnings():
-    if vmm.hasWarnings():
-        w_std(_(u'Warnings:'))
-        for warning in vmm.getWarnings():
-            w_std( " * %s" % warning)
-
-def show_version():
-    w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s %s' % (__prog__,
-    # TP: The words 'from', 'version' and 'on' are used in the version
-    # information:
-    # 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], __prog__,
-        _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
-
-#def main():
 if __name__ == '__main__':
-    __prog__ = os.path.basename(os.sys.argv[0])
-    gettext.install(__prog__, '/usr/local/share/locale', unicode=1)
-    argv = [unicode(arg, ENCODING) for arg in os.sys.argv]
-    argc = len(os.sys.argv)
-    plan_a_b =_(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
-
-    if argc < 2:
-        usage(EXIT.MISSING_ARGS)
-
-    vmm = get_vmm()
-    try:
-        need_setup = not vmm.setupIsDone()
-        if   argv[1] in (u'cf', u'configure') or need_setup:
-            configure()
-        elif argv[1] in (u'da', u'domainadd'):
-            domain_add()
-        elif argv[1] in (u'di', u'domaininfo'):
-            domain_info()
-        elif argv[1] in (u'dt', u'domaintransport'):
-            domain_transport()
-        elif argv[1] in (u'dd', u'domaindelete'):
-            domain_delete()
-        elif argv[1] in (u'ada', u'aliasdomainadd'):
-            alias_domain_add()
-        elif argv[1] in (u'adi', u'aliasdomaininfo'):
-            alias_domain_info()
-        elif argv[1] in (u'ads', u'aliasdomainswitch'):
-            alias_domain_switch()
-        elif argv[1] in (u'add', u'aliasdomaindelete'):
-            alias_domain_delete()
-        elif argv[1] in (u'ua', u'useradd'):
-            user_add()
-        elif argv[1] in (u'ui', u'userinfo'):
-            user_info()
-        elif argv[1] in (u'un', u'username'):
-            user_name()
-        elif argv[1] in (u'up', u'userpassword'):
-            user_password()
-        elif argv[1] in (u'ut', u'usertransport'):
-            user_transport()
-        elif argv[1] in (u'u0', u'userdisable'):
-            user_disable()
-        elif argv[1] in (u'u1', u'userenable'):
-            user_enable()
-        elif argv[1] in (u'ud', u'userdelete'):
-            user_delete()
-        elif argv[1] in (u'aa', u'aliasadd'):
-            alias_add()
-        elif argv[1] in (u'ai', u'aliasinfo'):
-            alias_info()
-        elif argv[1] in (u'ad', u'aliasdelete'):
-            alias_delete()
-        elif argv[1] in (u'ra', u'relocatedadd'):
-            relocated_add()
-        elif argv[1] in (u'ri', u'relocatedinfo'):
-            relocated_info()
-        elif argv[1] in (u'rd', u'relocateddelete'):
-            relocated_delete()
-        elif argv[1] in (u'gu', u'getuser'):
-            user_byID()
-        elif argv[1] in (u'ld', u'listdomains'):
-            domain_list()
-        elif argv[1] in (u'h', u'help'):
-            usage()
-        elif argv[1] in (u'v', u'version'):
-            show_version()
-        else:
-            usage(EXIT.UNKNOWN_COMMAND, _(u'Unknown subcommand: “%s”')% argv[1])
-        show_warnings()
-    except (EOFError, KeyboardInterrupt):
-        # TP: We have to cry, because root has killed/interrupted vmm
-        # with Ctrl+C or Ctrl+D.
-        w_err(EXIT.USER_INTERRUPT, _(u'\nOuch!\n'))
-    except (VMME.VMMConfigException, VMME.VMMException), e:
-        if e.code() != ERR.DATABASE_ERROR:
-            w_err(e.code(), _(u'Error: %s') % e.msg())
-        else:
-            w_err(e.code(), unicode(e.msg(), ENCODING, 'replace'))
+    # replace the script's cwd (/usr/local/sbin) with our module dir
+    # (the location of the VirtualMailManager directory) - if it is
+    # not in sys.path
+    #sys.path[0] = '/usr/local/lib/vmm'
+    # 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))
--- a/vmm.cfg	Mon Nov 07 03:22:15 2011 +0000
+++ b/vmm.cfg	Thu Jun 28 19:26:50 2012 +0000
@@ -6,56 +6,123 @@
 # Database settings
 #
 [database]
+; The Python PostgreSQL database adapter module to be used (String)
+; Supported modules are:
+;    * psycopg2
+;    * pyPgSQL
+module = psycopg2
 ; Hostname or IP address of the database server (String)
-host = 127.0.0.1
+host = localhost
+; The TCP port, on which the database server is listening for connections (Int)
+port = 5432
+; SSL mode for the database connection (String)
+; Possible values are:
+;    * disabled
+;    * allow
+;    * prefer (default)
+;    * require
+;    * verify-ca (PostgreSQL >= 8.4)
+;    * verify-full (PostgreSQL >= 8.4)
+sslmode = prefer
 ; Database user name (String)
 user = dbuser
 ; Database password (String)
 pass = dbpassword
-; database name (String)
+; Database name (String)
 name = mailsys
 
 #
-# Mail directories
+# mailbox settings
 #
-[maildir]
-; Default name of the Maildir folder (String)
-name = Maildir
-; A colon separated list of folder names, that should be created (String)
-; e.g.: folders = Drafts:Sent:Templates:Trash
+[mailbox]
+; The mailbox format to be used for user's mailboxes. (String)
+; Depending on the used Dovecot version there are up to 3 supported formats:
+;    * maildir - since Dovecot v1.0.0
+;    * mdbox   - since Dovecot v2.0.beta5
+;    * sdbox   - since Dovecot v2.0.rc3
+format = maildir
+; A colon separated list of mailbox names, that should be created (String)
+; e.g.: folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
 folders = Drafts:Sent:Templates:Trash
-; Permissions for maildirs (Int)
-; octal 0700 -> decimal 448
-mode = 448
-; Display disk usage in account info by default? (Boolean)
-diskusage = false
-; Delete maildir recursive when deleting an account? (Boolean)
-delete = false
+; Name of the mailbox root directory in a user's home. (String)
+; Usually used names (format: name):
+;    * maildir: Maildir
+;    * mdbox:   mdbox
+;    * sdbox:   sdbox
+root = Maildir
+; Set to true if the mailboxes from the folders option should be listed in
+; the subscriptions file. (Boolean)
+subscribe = true
 
 #
-# Services per user
+# Domain settings
 #
-[services]
-; allow smtp by default? (Boolean)
+[domain]
+; Should vmm create the postmaster account when a new domain is created?
+; (Boolean)
+auto_postmaster = true
+; Delete domain directory recursive when deleting a domain? (Boolean)
+delete_directory = false
+; Permissions for domain directories (Int)
+; octal 0770 -> decimal 504
+directory_mode = 504
+; Force deletion of accounts and aliases when deleting a domain (Boolean)
+force_deletion = false
+;
+; The service settings will be evaluated and applied when a domain is
+; created. The service settings of the domain will be applied when you
+; create a new account.
+; Use the subcommand domainservices to modify a domain's service settings.
+; Or userservices in order to update the service setting of an account.
+; Allow smtp by default? (Boolean)
 smtp = true
-; allow pop3 by default? (Boolean)
+; Allow pop3 by default? (Boolean)
 pop3 = true
-; allow imap by default? (Boolean)
+; Allow imap by default? (Boolean)
 imap = true
-; allow managesieve by default? (Boolean)
+; Allow managesieve by default? (Boolean)
 sieve = true
+;
+; The quota_* settings will be evaluated and applied when a domain is
+; created. The domain's quota_* settings will be applied when an account
+; is added to a domain.
+; Use the subcommand domainquota to modify a domain's quota limits.
+; Or userquota in order to update an account's quota limits.
+; Quota limit in bytes. 0 means unlimited (String)
+; The value can have one of the suffixes:
+;    * b: bytes
+;    * k: kilobytes
+;    * M: megabytes
+;    * G: gigabytes
+; 1024 is the same as 1024b or 1k
+quota_bytes = 0
+; Quota limit in number of messages. 0 means unlimited (Int)
+quota_messages = 0
+;
+; The transport setting will be evaluated and applied when a domain is
+; created. The domain's transport setting will be applied when an account
+; is added to a domain.
+; Use the subcommand domaintransport to modify the transport of a domain.
+; Or usertransport in order to update an account's transport setting.
+; default transport for domains and accounts (String)
+transport = dovecot:
 
 #
-# domain directory settings
+# Account settings
 #
-[domdir]
-; The base directory for all domains/accounts (String)
-base = /srv/mail
-; Permissions for domain directories (Int)
-; octal 0770 -> decimal 504
-mode = 504
-; Delete domain directory recursive when deleting a domain? (Boolean)
-delete = false
+[account]
+; Delete the user's home directory recursive when deleting an account? (Boolean)
+delete_directory = false
+; Permissions for the user's home directory and mail directories (Int)
+; octal 0700 -> decimal 448
+directory_mode = 448
+; Display disk usage in account info by default? (Boolean)
+disk_usage = false
+; Should vmm generate a random password when no password was given for the
+; useradd subcommand? (Boolean)
+random_password = false
+; How many characters to include in the generated passwords? (Int)
+password_length = 8
 
 #
 # external binaries
@@ -72,21 +139,17 @@
 # misc settings
 #
 [misc]
+; The base directory for all domains/accounts (String)
+base_directory = /srv/mail
+; Number of encryption rounds for the password_scheme BLF-CRYPT (Int)
+crypt_blowfish_rounds = 5
+; Number of encryption rounds for the password_scheme SHA256-CRYPT (Int)
+crypt_sha256_rounds = 5000
+; Number of encryption rounds for the password_scheme SHA512-CRYPT (Int)
+crypt_sha512_rounds = 5000
+; the version number from `dovecot --version` (String)
+; e.g. 1.1.18; 1.2.11; 2.0.beta4
+dovecot_version = 1.2.11
 ; Password scheme to use (see also: dovecotpw -l) (String)
-passwdscheme = PLAIN
-; numeric group ID of group mail (mail_privileged_group from dovecot.conf) (Int)
-gid_mail = 8
-; force deletion of accounts and aliases (Boolean)
-forcedel = false
-; default transport for domains and accounts
-transport = dovecot:
-; the concatenated major and minor version number from `dovecot --version` (Int)
-; e.g. 1.0.15 -> 10; 1.1.18 -> 11; 1.2.3 -> 12
-dovecotvers = 11
+password_scheme = CRAM-MD5
 
-#
-# Configuration state
-#
-[config]
-; finally set this to true (Boolean)
-done = false