--- a/.hgignore Tue Apr 20 02:59:08 2010 +0000
+++ b/.hgignore Tue Apr 20 03:04:16 2010 +0000
@@ -5,3 +5,4 @@
*.py?
.*.swp
.swp
+doc/build
--- a/INSTALL Tue Apr 20 02:59:08 2010 +0000
+++ b/INSTALL Tue Apr 20 03:04:16 2010 +0000
@@ -9,13 +9,18 @@
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:
@@ -23,24 +28,25 @@
DB User:
createuser -d -E -e -P $USERNAME
-* Create Database and db users for Postfix and Dovecot
+* Create Database and db users for vmm, Postfix and Dovecot
connecting to PostgreSQL:
psql template1
- # create database
- CREATE DATABASE mailsys ENCODING 'UTF8';
+ # create users, group and the database
+ CREATE USER vmm ENCRYPTED PASSWORD 'DB PASSWORD for vmm';
+ CREATE USER dovecot ENCRYPTED password 'DB PASSWORD for Dovecot';
+ CREATE USER postfix ENCRYPTED password 'DB PASSWORD for Postfix';
+ CREATE ROLE mailsys WITH USER postfix, dovecot, vmm;
+ CREATE DATABASE mailsys WITH OWNER vmm ENCODING 'UTF8';
+ \q
+
# connect to the new database
- \c mailsys
+ psql mailsys vmm -W -h 127.0.0.1
# 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 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,
--- a/TODO Tue Apr 20 02:59:08 2010 +0000
+++ b/TODO Tue Apr 20 03:04:16 2010 +0000
@@ -1,3 +1,8 @@
+Config
+ cfs - configset sect.opt val
+ cfg - configget sect.opt
+
+ ds - domainservices: smtp pop imap sieve???
- Aliases
- avoid looping aliases
@@ -8,3 +13,9 @@
+ aliases
+ destinations/alias
+ alias domains
+
+Database:
+ public.users.passwd: increase to "character varying(150)"
+ why? `doveadm pw -s SSHA512.hex -p 1`
+ public.users.digestmd5: add "character varying(48)"
+ Outlook will love it. (`doveadm pw -s DIGEST-MD5.hex -p 1 -u 0`)
--- a/VirtualMailManager/Account.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Account.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,178 +2,295 @@
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's Account class to manage e-mail accounts."""
+"""
+ VirtualMailManager.Account
+
+ 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
+from VirtualMailManager.Domain import Domain
+from VirtualMailManager.EmailAddress import EmailAddress
+from VirtualMailManager.Transport import Transport
+from VirtualMailManager.constants.ERROR import \
+ ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
+ INVALID_AGUMENT, NO_SUCH_ACCOUNT, NO_SUCH_DOMAIN, \
+ UNKNOWN_MAILLOCATION_NAME, UNKNOWN_SERVICE
+from VirtualMailManager.errors import AccountError as AErr
+from VirtualMailManager.maillocation import MailLocation, known_format
+from VirtualMailManager.pycompat import all
+
+
+_ = lambda msg: msg
+
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):
+ __slots__ = ('_addr', '_domain', '_mid', '_new', '_passwd', '_tid', '_uid',
+ '_dbh')
+
+ 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` : basestring
+ 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._base = None
- if isinstance(address, EmailAddress):
- self._addr = address
- else:
- raise TypeError("Argument 'address' is not an EmailAddress")
+ self._domain = Domain(self._dbh, self._addr.domainname)
+ if not self._domain.gid:
+ raise AErr(_(u"The domain '%s' doesn't exist.") %
+ self._addr.domainname, NO_SUCH_DOMAIN)
self._uid = 0
- self._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)
+ self._passwd = None
+ self._new = True
+ self._load()
- def _exists(self):
+ def _load(self):
+ """Load 'uid', 'mid' and 'tid' from the database and set _new to
+ `False` - if the user could be found. """
dbc = self._dbh.cursor()
- dbc.execute("SELECT uid, mid, tid FROM users \
-WHERE gid=%s AND local_part=%s",
- self._gid, self._addr._localpart)
+ dbc.execute(
+ "SELECT uid, mid, tid FROM users WHERE gid=%s AND local_part=%s",
+ self._domain.gid, self._addr.localpart)
result = dbc.fetchone()
dbc.close()
- if result is not None:
+ if result:
self._uid, self._mid, self._tid = result
- return True
- else:
- return False
+ self._new = 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):
+ 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):
- self._setID()
- self._mid = MailLocation(self._dbh, maillocation=maillocation).getID()
+ """Check and set different attributes - before we store the
+ information in the database."""
+ if not known_format(maillocation):
+ raise AErr(_(u'Unknown mail_location mailbox format: %r') %
+ maillocation, UNKNOWN_MAILLOCATION_NAME)
+ self._mid = MailLocation(format=maillocation).mid
+ if not self._tid:
+ self._tid = self._domain.transport.tid
+ self._set_uid()
- 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)
+ def _switch_state(self, state, dcvers, service):
+ """Switch the state of the Account's services on or off. See
+ Account.enable()/Account.disable() for more information."""
+ self._chk_state()
+ if service not in (None, 'all', 'imap', 'pop3', 'sieve', 'smtp'):
+ raise AErr(_(u"Unknown service: '%s'.") % service, UNKNOWN_SERVICE)
if 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)
+ self._uid)
elif service == 'sieve':
sql = 'UPDATE users SET %s = %s WHERE uid = %d' % (sieve_col,
- state, self._uid)
+ 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}
+ %(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):
+ def _count_aliases(self):
+ """Count all alias addresses where the destination address is the
+ address of the Account."""
dbc = self._dbh.cursor()
- q = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
- %self._addr
- dbc.execute(q)
+ sql = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\
+ % self._addr
+ dbc.execute(sql)
a_count = dbc.fetchone()[0]
dbc.close()
return a_count
- def setPassword(self, password):
- self._passwd = password
+ def _chk_state(self):
+ """Raise an AccountError if the Account is new - not yet saved in the
+ database."""
+ if self._new:
+ raise AErr(_(u"The account '%s' doesn't exist.") % self._addr,
+ NO_SUCH_ACCOUNT)
+
+ @property
+ def address(self):
+ """The Account's EmailAddress instance."""
+ return self._addr
- def getUID(self):
+ @property
+ def domain_directory(self):
+ """The directory of the domain the Account belongs to."""
+ return self._domain.directory
+
+ @property
+ def gid(self):
+ """The Account's group ID."""
+ return self._domain.gid
+
+ @property
+ def home(self):
+ """The Account's home directory."""
+ return '%s/%s' % (self._domain.directory, self._uid)
+
+ @property
+ def uid(self):
+ """The Account's unique ID."""
return self._uid
- def getGID(self):
- return self._gid
+ 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 hashed password for the new Account."""
+ self._passwd = password
- def getDir(self, directory):
- if directory == 'domain':
- return '%s' % self._base
- elif directory == 'home':
- return '%s/%i' % (self._base, self._uid)
+ def set_transport(self, transport):
+ """Set the transport for the new Account.
+
+ If you want to update the transport of an existing Account use
+ Account.modify().
+
+ Argument:
+
+ `transport` : basestring
+ The string representation of the transport, e.g.: 'dovecot:'
+ """
+ self._tid = Transport(self._dbh, transport=transport).tid
def enable(self, dcvers, service=None):
- self._switchState(True, dcvers, service)
+ """Enable a/all service/s for the Account.
+
+ Possible values for the *service* are: 'imap', 'pop3', 'sieve' and
+ 'smtp'. When all services should be enabled, use 'all' or the
+ default value `None`.
+
+ Arguments:
+
+ `dcvers` : int
+ The concatenated major and minor version number from
+ `dovecot --version`.
+ `service` : basestring
+ The name of a service ('imap', 'pop3', 'smtp', 'sieve'), 'all'
+ or `None`.
+ """
+ self._switch_state(True, dcvers, service)
def disable(self, dcvers, service=None):
- self._switchState(False, dcvers, service)
+ """Disable a/all service/s for the Account.
+
+ For more information see: Account.enable()."""
+ self._switch_state(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()
+ """Save the new Account in the database.
+
+ Arguments:
+
+ `maillocation` : basestring
+ The mailbox format of the mail_location: 'maildir', 'mbox',
+ 'dbox' or 'mdbox'.
+ `dcvers` : int
+ The concatenated major and minor version number from
+ `dovecot --version`.
+ `smtp, pop3, imap, sieve` : bool
+ Indicates if the user of the Account should be able to use this
+ services.
+ """
+ if not self._new:
+ raise AErr(_(u"The account '%s' already exists.") % self._addr,
+ ACCOUNT_EXISTS)
+ if not self._passwd:
+ raise AErr(_(u"No password set for '%s'.") % self._addr,
+ ACCOUNT_MISSING_PASSWORD)
+ assert all(isinstance(service, bool) for service in (smtp, pop3, imap,
+ sieve))
+ if dcvers > 11:
+ sieve_col = 'sieve'
else:
- raise AccE(_(u'The account “%s” already exists.') % self._addr,
- ERR.ACCOUNT_EXISTS)
+ 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._domain.gid, self._mid, self._tid, smtp, pop3, imap, sieve)
+ dbc = self._dbh.cursor()
+ dbc.execute(sql)
+ self._dbh.commit()
+ dbc.close()
+ self._new = False
+
+ def modify(self, field, value):
+ """Update the Account's *field* to the new *value*.
+
+ Possible values for *filed* are: 'name', 'password' and
+ 'transport'. *value* is the *field*'s new value.
- 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
+ Arguments:
+
+ `field` : basestring
+ The attribute name: 'name', 'password' or 'transport'
+ `value` : basestring
+ The new value of the attribute. The password is expected as a
+ hashed password string.
+ """
+ if field not in ('name', 'password', 'transport'):
+ raise AErr(_(u"Unknown field: '%s'") % field, INVALID_AGUMENT)
+ self._chk_state()
dbc = self._dbh.cursor()
- if what == 'password':
+ if field == '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()
+ value, self._uid)
+ elif field == 'transport':
+ self._tid = Transport(self._dbh, transport=value).tid
dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
- self._tid, self._uid)
+ self._tid, self._uid)
else:
dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
- value, self._uid)
+ value, self._uid)
if dbc.rowcount > 0:
self._dbh.commit()
dbc.close()
- def getInfo(self, dcvers):
+ def get_info(self, dcvers):
+ """Returns a dict with some information about the Account.
+
+ The keys of the dict are: 'address', 'gid', 'home', 'imap'
+ 'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport' and
+ 'uid'.
+
+ Argument:
+
+ `dcvers` : int
+ The concatenated major and minor version number from
+ `dovecot --version`.
+ """
+ self._chk_state()
if dcvers > 11:
sieve_col = 'sieve'
else:
@@ -184,29 +301,35 @@
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]
+ if info:
+ keys = ('name', 'uid', 'gid', 'mid', '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
+ if info[service]:
+ # TP: A service (pop3/imap) is enabled/usable for a user
info[service] = _('enabled')
else:
# TP: A service (pop3/imap) isn't enabled/usable for a user
info[service] = _('disabled')
info['address'] = self._addr
- info['maildir'] = '%s/%s/%s' % (self._base, info['uid'],
- MailLocation(self._dbh,
- mid=info['maildir']).getMailLocation())
- info['transport'] = Transport(self._dbh,
- tid=info['transport']).getTransport()
+ info['home'] = '%s/%s' % (self._domain.directory, info['uid'])
+ info['mail_location'] = MailLocation(mid=info['mid']).mail_location
+ if info['transport'] == self._domain.transport.tid:
+ info['transport'] = self._domain.transport.transport
+ else:
+ info['transport'] = Transport(self._dbh,
+ tid=info['transport']).transport
+ del info['mid']
return info
+ # nearly impossible‽
+ raise AErr(_(u"Couldn't fetch information for account: '%s'") \
+ % self._addr, NO_SUCH_ACCOUNT)
- def getAliases(self):
+ 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\
@@ -214,54 +337,71 @@
addresses = dbc.fetchall()
dbc.close()
aliases = []
- if len(addresses) > 0:
+ if addresses:
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)
+ """Delete the Account from the database.
+
+ Argument:
+
+ `delalias` : basestring
+ if the values of delalias is 'delalias', all aliases, which
+ points to the Account, will be also deleted."""
+ self._chk_state()
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()
+ str(self._addr))
+ self._dbh.commit()
+ else: # check first for aliases
+ a_count = self._count_aliases()
if a_count == 0:
dbc.execute('DELETE FROM users WHERE uid = %s', self._uid)
- if dbc.rowcount > 0:
- self._dbh.commit()
+ 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)
+ raise AErr(_(u"There are %(count)d aliases with the \
+destination address '%(address)s'.") % \
+ {'count': a_count, 'address': self._addr},
+ ALIAS_PRESENT)
dbc.close()
-def getAccountByID(uid, dbh):
+
+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 AccE(_(u'uid must be an int/long.'), ERR.INVALID_AGUMENT)
+ raise AErr(_(u'UID must be an int/long.'), INVALID_AGUMENT)
if uid < 1:
- raise AccE(_(u'uid must be greater than 0.'), ERR.INVALID_AGUMENT)
+ raise AErr(_(u'UID must be greater than 0.'), 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))
+ if not info:
+ raise AErr(_(u"There is no account with the UID '%d'.") % uid,
+ NO_SUCH_ACCOUNT)
+ info = dict(zip(('address', 'uid', 'gid'), info))
return info
+
+del _
--- a/VirtualMailManager/Alias.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Alias.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,121 +2,162 @@
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's Alias class to manage e-mail aliases."""
+"""
+ VirtualMailManager.Alias
+
+ 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
+from VirtualMailManager.Domain import get_gid
+from VirtualMailManager.EmailAddress import EmailAddress
+from VirtualMailManager.errors import AliasError as AErr
+from VirtualMailManager.pycompat import all
+from VirtualMailManager.constants.ERROR import \
+ ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
+
+
+_ = lambda msg: msg
+
class Alias(object):
"""Class to manage e-mail aliases."""
- __slots__ = ('_addr', '_dest', '_gid', '_isNew', '_dbh')
- def __init__(self, dbh, address, destination=None):
- 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)
+ __slots__ = ('_addr', '_dests', '_gid', '_dbh')
+
+ def __init__(self, dbh, address):
+ assert isinstance(address, EmailAddress)
+ self._addr = address
self._dbh = dbh
- self._gid = 0
- self._isNew = False
- self._setAddr()
- if not self._dest is None:
- self._exists()
- if VMM.VirtualMailManager.accountExists(self._dbh, self._addr):
- raise VMMAE(_(u"There is already an account with address “%s”.") %\
- self._addr, ERR.ACCOUNT_EXISTS)
- if VMM.VirtualMailManager.relocatedExists(self._dbh, self._addr):
- raise VMMAE(
- _(u"There is already a relocated user with the address “%s”.") %\
- self._addr, ERR.RELOCATED_EXISTS)
+ self._gid = get_gid(self._dbh, self._addr.domainname)
+ if not self._gid:
+ raise AErr(_(u"The domain '%s' doesn't exist.") %
+ self._addr.domainname, NO_SUCH_DOMAIN)
+ self._dests = []
+
+ self.__load_dests()
+
+ def __load_dests(self):
+ """Loads all known destination addresses into the _dests list."""
+ dbc = self._dbh.cursor()
+ dbc.execute(
+ 'SELECT destination FROM alias WHERE gid=%s AND address=%s',
+ self._gid, self._addr.localpart)
+ dests = iter(dbc.fetchall())
+ if dbc.rowcount > 0:
+ self._dests.extend(EmailAddress(dest[0]) for dest in dests)
+ dbc.close()
- 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 __check_expansion(self, count_new, limit):
+ """Checks the current expansion limit of the alias."""
+ dcount = len(self._dests)
+ failed = False
+ if dcount == limit or dcount + count_new > limit:
+ failed = True
+ errmsg = _(
+u"""Can't add %(count_new)i new destination(s) to alias '%(address)s'.
+Currently this alias expands into %(count)i/%(limit)i recipients.
+%(count_new)i additional destination(s) will render this alias unusable.
+Hint: Increase Postfix' virtual_alias_expansion_limit""")
+ elif dcount > limit:
+ failed = True
+ errmsg = _(
+u"""Can't add %(count_new)i new destination(s) to alias '%(address)s'.
+This alias already exceeds its expansion limit (%(count)i/%(limit)i).
+So its unusable, all messages addressed to this alias will be bounced.
+Hint: Delete some destination addresses.""")
+ if failed:
+ raise AErr(errmsg % {'address': self._addr, 'count': dcount,
+ 'limit': limit, 'count_new': count_new},
+ ALIAS_EXCEEDS_EXPANSION_LIMIT)
+
+ def __delete(self, destination=None):
+ """Deletes a destination from the alias, if ``destination`` is
+ not ``None``. If ``destination`` is None, the alias with all
+ its destination addresses will be deleted.
- 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]
+ 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()
- 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 __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, expansion_limit, warnings=None):
+ """Adds the `EmailAddress`es from *destinations* list to the
+ destinations of the alias.
- 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)
+ 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), expansion_limit)
+ dbc = self._dbh.cursor()
+ dbc.executemany("INSERT INTO alias VALUES (%d, '%s', %%s)" %
+ (self._gid, self._addr.localpart),
+ (str(destination) for destination in destinations))
+ self._dbh.commit()
+ dbc.close()
+ self._dests.extend(destinations)
+ return destinations
- def 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 del_destination(self, destination):
+ """Deletes the specified ``destination`` address from the alias."""
+ assert isinstance(destination, EmailAddress)
+ if not self._dests:
+ raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+ NO_SUCH_ALIAS)
+ if not destination in self._dests:
+ raise AErr(_(u"The address '%(d)s' isn't a destination of \
+the alias '%(a)s'.") %
+ {'a': self._addr, 'd': destination},
+ NO_SUCH_ALIAS)
+ self.__delete(destination)
+ self._dests.remove(destination)
+
+ def get_destinations(self):
+ """Returns an iterator for all destinations of the alias."""
+ if not self._dests:
+ raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+ NO_SUCH_ALIAS)
+ return iter(self._dests)
def delete(self):
- 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)
+ """Deletes the alias with all its destinations."""
+ if not self._dests:
+ raise AErr(_(u"The alias '%s' doesn't exist.") % self._addr,
+ NO_SUCH_ALIAS)
+ self.__delete()
+ del self._dests[:]
+
+del _
--- a/VirtualMailManager/AliasDomain.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/AliasDomain.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,98 +2,147 @@
# Copyright (c) 2008 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's AliasDomain class to manage alias domains."""
+"""
+ VirtualMailManager.AliasDomain
+
+ Virtual Mail Manager's AliasDomain class to manage alias domains.
+"""
-from __main__ import ERR
-from Exceptions import VMMAliasDomainException as VADE
-import VirtualMailManager as VMM
+from VirtualMailManager.Domain import Domain, check_domainname
+from VirtualMailManager.constants.ERROR 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, targetDomain=None):
+ __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 = VMM.VirtualMailManager.chkDomainname(domainname)
- self.__gid = 0
- self._domain = targetDomain
- self._exists()
+ self._name = check_domainname(domainname)
+ self._gid = 0
+ self._domain = None
+ self._load()
- def _exists(self):
+ 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)
- alias = dbc.fetchone()
+ dbc.execute(
+ 'SELECT gid, is_primary FROM domain_name WHERE domainname = %s',
+ self._name)
+ result = 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)
+ 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):
- 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)
+ """Stores information about the new AliasDomain in the database."""
+ if self._gid > 0:
+ raise ADErr(_(u"The alias domain '%s' already exists.") %
+ self._name, ALIASDOMAIN_EXISTS)
+ if not self._domain:
+ raise ADErr(_(u'No destination domain set for the alias domain.'),
+ ALIASDOMAIN_NO_DOMDEST)
+ if self._domain.gid < 1:
+ raise ADErr(_(u"The target domain '%s' doesn't exist.") %
+ self._domain.name, NO_SUCH_DOMAIN)
dbc = self._dbh.cursor()
- dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary)\
- VALUES (%s, %s, FALSE)', self.__name, self._domain._id)
+ dbc.execute('INSERT INTO domain_name VALUES (%s, %s, FALSE)',
+ self._name, self._domain.gid)
self._dbh.commit()
dbc.close()
+ self._gid = self._domain.gid
def info(self):
- 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)
+ """Returns a dict (keys: "alias" and "domain") with the names of the
+ AliasDomain and its primary domain."""
+ if self._gid < 1:
+ raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+ self._name, NO_SUCH_ALIASDOMAIN)
+ dbc = self._dbh.cursor()
+ dbc.execute(
+ 'SELECT domainname FROM domain_name WHERE gid = %s AND is_primary',
+ self._gid)
+ domain = dbc.fetchone()
+ dbc.close()
+ if domain:
+ return {'alias': self._name, 'domain': domain[0]}
+ else: # an almost unlikely case, isn't it?
+ raise ADErr(
+ _(u"There is no primary domain for the alias domain '%s'.")\
+ % self._name, NO_SUCH_DOMAIN)
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)
+ """Switch the destination of the AliasDomain to the new destination,
+ set with the method `set_destination()`.
+ """
+ if not self._domain:
+ raise ADErr(_(u'No destination domain set for the alias domain.'),
+ ALIASDOMAIN_NO_DOMDEST)
+ if self._domain.gid < 1:
+ raise ADErr(_(u"The target domain '%s' doesn't exist.") %
+ self._domain.name, NO_SUCH_DOMAIN)
+ if self._gid < 1:
+ raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+ self._name, NO_SUCH_ALIASDOMAIN)
+ if self._gid == self._domain.gid:
+ raise ADErr(_(u"The alias domain '%(alias)s' is already assigned\
+ 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._id, self.__gid, self.__name)
+ self._domain.gid, self._gid, self._name)
self._dbh.commit()
dbc.close()
+ self._gid = self._domain.gid
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)
+ """Delete the AliasDomain's record form the database.
+ Raises an AliasDomainError if the AliasDomain doesn't exist.
+ """
+ if self._gid < 1:
+ raise ADErr(_(u"The alias domain '%s' doesn't exist.") %
+ self._name, NO_SUCH_ALIASDOMAIN)
+ dbc = self._dbh.cursor()
+ dbc.execute(
+ 'DELETE FROM domain_name WHERE domainname = %s AND NOT is_primary',
+ self._name)
+ if dbc.rowcount > 0:
+ self._dbh.commit()
+ self._gid = 0
+ dbc.close()
+
+
+del _
--- a/VirtualMailManager/Config.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Config.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,186 +2,425 @@
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Configuration class for read, modify and write the
-configuration from Virtual Mail Manager.
+"""
+ VirtualMailManager.Config
+ VMM's configuration module for simplified configuration access.
"""
-from shutil import copy2
-from ConfigParser import ConfigParser, MissingSectionHeaderError, ParsingError
-from cStringIO import StringIO
+
+from ConfigParser import \
+ Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
+ ParsingError, RawConfigParser
+from cStringIO import StringIO# TODO: move interactive stff to cli
+
+from VirtualMailManager import exec_ok, get_unicode, is_dir
+from VirtualMailManager.constants.ERROR import CONF_ERROR
+from VirtualMailManager.errors import ConfigError
+
+
+_ = lambda msg: msg
+
+
+class BadOptionError(Error):
+ """Raised when a option isn't in the format 'section.option'."""
+ pass
+
+
+class ConfigValueError(Error):
+ """Raised when creating or validating of new values fails."""
+ pass
+
+
+class NoDefaultError(Error):
+ """Raised when the requested option has no default value."""
+
+ def __init__(self, section, option):
+ Error.__init__(self, 'Option %r in section %r has no default value' %
+ (option, section))
+
+
+class LazyConfig(RawConfigParser):
+ """The **lazy** derivate of the `RawConfigParser`.
+
+ There are two additional getters:
+
+ `pget()`
+ The polymorphic getter, which returns a option's value with the
+ appropriate type.
+ `dget()`
+ Like `LazyConfig.pget()`, but returns the option's default, from
+ `LazyConfig._cfg['sectionname']['optionname'].default`, if the
+ option is not configured in a ini-like configuration file.
+
+ `set()` differs from `RawConfigParser`'s `set()` method. `set()`
+ takes the `section` and `option` arguments combined to a single
+ string in the form "section.option".
+
+ """
+
+ def __init__(self):
+ RawConfigParser.__init__(self)
+ self._modified = False
+ # sample _cfg dict. Create your own in your derived class.
+ self._cfg = {
+ 'sectionname': {
+ 'optionname': LazyConfigOption(int, 1, self.getint),
+ }
+ }
+
+ def bool_new(self, value):
+ """Converts the string `value` into a `bool` and returns it.
+
+ | '1', 'on', 'yes' and 'true' will become `True`
+ | '0', 'off', 'no' and 'false' will become `False`
+
+ Throws a `ConfigValueError` for all other values, except bools.
+ """
+ if isinstance(value, bool):
+ return value
+ if value.lower() in self._boolean_states:
+ return self._boolean_states[value.lower()]
+ else:
+ raise ConfigValueError(_(u"Not a boolean: '%s'") %
+ get_unicode(value))
+
+ def getboolean(self, section, option):
+ """Returns the boolean value of the option, in the given
+ section.
+
+ For a boolean True, the value must be set to '1', 'on', 'yes',
+ 'true' or True. For a boolean False, the value must set to '0',
+ 'off', 'no', 'false' or False.
+ If the option has another value assigned this method will raise
+ a ValueError.
+
+ """
+ # if the setting was modified it may be still a boolean value lets see
+ tmp = self.get(section, option)
+ if isinstance(tmp, bool):
+ return tmp
+ if not tmp.lower() in self._boolean_states:
+ raise ValueError('Not a boolean: %s' % tmp)
+ return self._boolean_states[tmp.lower()]
+
+ def _get_section_option(self, section_option):
+ """splits ``section_option`` (section.option) in two parts and
+ returns them as list ``[section, option]``, if:
+
+ * it likes the format of ``section_option``
+ * the ``section`` is known
+ * the ``option`` is known
+
+ Else one of the following exceptions will be thrown:
+
+ * `BadOptionError`
+ * `NoSectionError`
+ * `NoOptionError`
+
+ """
+ sect_opt = section_option.lower().split('.')
+ # TODO: cache it
+ if len(sect_opt) != 2:# do we need a regexp to check the format?
+ raise BadOptionError(
+ _(u"Bad format: '%s' - expected: 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``.
-from __main__ import ENCODING, ERR, w_std
-from Exceptions import VMMConfigException
+ """
+ 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-msg=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.
-class Config(ConfigParser):
- """This class is for reading and modifying vmm's configuration file."""
+ """
+ # pylint: disable-msg=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 -- path to the configuration file
+
+ `filename` : str
+ 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
+ LazyConfig.__init__(self)
+ self._cfg_filename = filename
+ self._cfg_file = None
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']
- ]
+
+ LCO = LazyConfigOption
+ bool_t = self.bool_new
+ self._cfg = {
+ 'account': {
+ 'delete_directory': LCO(bool_t, False, self.getboolean),
+ 'directory_mode': LCO(int, 448, self.getint),
+ 'disk_usage': LCO(bool_t, False, self.getboolean),
+ 'password_length': LCO(int, 8, self.getint),
+ 'random_password': LCO(bool_t, False, self.getboolean),
+ 'imap': LCO(bool_t, True, self.getboolean),
+ 'pop3': LCO(bool_t, True, self.getboolean),
+ 'sieve': LCO(bool_t, True, self.getboolean),
+ 'smtp': LCO(bool_t, True, self.getboolean),
+ },
+ 'bin': {
+ 'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
+ exec_ok),
+ 'du': LCO(str, '/usr/bin/du', self.get, exec_ok),
+ 'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok),
+ },
+ 'database': {
+ 'host': LCO(str, 'localhost', self.get),
+ 'name': LCO(str, 'mailsys', self.get),
+ 'pass': LCO(str, None, self.get),
+ 'user': LCO(str, None, self.get),
+ },
+ 'domain': {
+ 'auto_postmaster': LCO(bool_t, True, self.getboolean),
+ 'delete_directory': LCO(bool_t, False, self.getboolean),
+ 'directory_mode': LCO(int, 504, self.getint),
+ 'force_deletion': LCO(bool_t, False, self.getboolean),
+ },
+ 'mailbox': {
+ 'folders': LCO(str, 'Drafts:Sent:Templates:Trash', self.get),
+ 'format': LCO(str, 'maildir', self.get),
+ },
+ 'misc': {
+ 'base_directory': LCO(str, '/srv/mail', self.get, is_dir),
+ 'dovecot_version': LCO(int, 12, self.getint),
+ 'gid_mail': LCO(int, 8, self.getint),
+ 'password_scheme': LCO(str, 'CRAM-MD5', self.get,
+ self.known_scheme),
+ 'transport': LCO(str, 'dovecot:', self.get),
+ },
+ }
def load(self):
"""Loads the configuration, read only.
- Raises a VMMConfigException if the configuration syntax is invalid.
+ Raises a ConfigError 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()
+ self._cfg_file = open(self._cfg_filename, 'r')
+ self.readfp(self._cfg_file)
+ except (MissingSectionHeaderError, ParsingError), err:
+ raise ConfigError(str(err), CONF_ERROR)
+ finally:
+ if self._cfg_file and not self._cfg_file.closed:
+ self._cfg_file.close()
def check(self):
"""Performs a configuration check.
- Raises a VMMConfigException if the check fails.
+ Raises a ConfigError if the check fails.
+
"""
- if not self.__chkSections():
+ # TODO: There are only two settings w/o defaults.
+ # So there is no need for cStringIO
+ if not self.__chk_cfg():
errmsg = StringIO()
- errmsg.write(_("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)
+ errmsg.write(_(u'Missing options, which have no default value.\n'))
+ errmsg.write(_(u'Using configuration file: %s\n') %
+ self._cfg_filename)
+ for section, options in self.__missing.iteritems():
+ errmsg.write(_(u'* Section: %s\n') % section)
+ for option in options:
+ errmsg.write((u' %s\n') % option)
+ raise ConfigError(errmsg.getvalue(), CONF_ERROR)
- def getsections(self):
- """Return a list with all configurable sections."""
- return self.__VMMsections[:-1]
+ def known_scheme(self, scheme):
+ """Converts `scheme` to upper case and checks if is known by
+ Dovecot (listed in VirtualMailManager.SCHEMES).
- def get(self, section, option, raw=False, vars=None):
- return unicode(ConfigParser.get(self, section, option, raw, vars),
- ENCODING, 'replace')
+ Throws a `ConfigValueError` if the scheme is not listed in
+ VirtualMailManager.SCHEMES.
- 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()
+ scheme = scheme.upper()
+ # TODO: VMM.SCHEMES
+
+ def unicode(self, section, option):
+ """Returns the value of the `option` from `section`, converted
+ to Unicode.
+
+ """
+ return get_unicode(self.get(section, option))
+
+ def __chk_cfg(self):
+ """Checks all section's options for settings w/o a default
+ value.
- 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()
+ Returns `True` if everything is fine, else `False`.
- 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
+ for section in self._cfg.iterkeys():
+ missing = []
+ for option, value in self._cfg[section].iteritems():
+ if (value.default is None and
+ not RawConfigParser.has_option(self, section, option)):
+ missing.append(option)
+ errors = True
+ if missing:
+ self.__missing[section] = missing
return not errors
- def __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
+del _
--- a/VirtualMailManager/Domain.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Domain.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,231 +2,272 @@
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's Domain class to manage e-mail domains."""
+"""
+ VirtualMailManager.Domain
+ Virtual Mail Manager's Domain class to manage e-mail domains.
+"""
+
+import os
+import re
+from encodings.idna import ToASCII, ToUnicode
from random import choice
-from __main__ import ERR
-from Exceptions import VMMDomainException as VMMDE
-import VirtualMailManager as VMM
-from Transport import Transport
+from VirtualMailManager.constants.ERROR import \
+ ACCOUNT_AND_ALIAS_PRESENT, ACCOUNT_PRESENT, ALIAS_PRESENT, \
+ DOMAIN_ALIAS_EXISTS, DOMAIN_EXISTS, DOMAIN_INVALID, DOMAIN_TOO_LONG, \
+ NO_SUCH_DOMAIN
+from VirtualMailManager.errors import DomainError as DomErr
+from VirtualMailManager.Transport import Transport
+
MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
+RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$")
+_ = lambda msg: msg
+
class Domain(object):
"""Class to manage e-mail domains."""
- __slots__ = ('_basedir','_domaindir','_id','_name','_transport','_dbh')
- def __init__(self, dbh, domainname, basedir=None, transport=None):
+ __slots__ = ('_directory', '_gid', '_name', '_transport', '_dbh', '_new')
+
+ def __init__(self, dbh, domainname):
"""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)
+ 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._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)
+ self._gid = 0
+ self._transport = None
+ self._directory = None
+ self._new = True
+ self._load()
- def _exists(self):
- """Checks if the domain already exists.
+ def _load(self):
+ """Load information from the database and checks if the domain name
+ is the primary one.
- If the domain exists _id will be set and returns True, otherwise False
- will be returned.
+ Raises a DomainError if Domain._name isn't the primary name of the
+ domain.
"""
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)
+ dbc.execute('SELECT dd.gid, tid, domaindir, is_primary FROM \
+ domain_data dd, domain_name dn WHERE domainname = %s AND dn.gid = dd.gid',
+ self._name)
result = dbc.fetchone()
dbc.close()
- if result is not None:
- self._id, self._domaindir = result[0], result[2]
+ if result:
+ if not result[3]:
+ raise DomErr(_(u"The domain '%s' is an alias domain.") %
+ self._name, DOMAIN_ALIAS_EXISTS)
+ self._gid, self._directory = result[0], result[2]
self._transport = Transport(self._dbh, tid=result[1])
- return True
- else:
- return False
+ self._new = 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."""
+ 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._id = dbc.fetchone()[0]
+ self._gid = 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)
+ Argument:
+
+ `what` : basestring
+ "alias" or "users"
"""
- if what not in ['alias', 'users']:
- return False
+ assert what in ('alias', 'users')
dbc = self._dbh.cursor()
if what == 'users':
- dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._id)
+ dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._gid)
else:
- dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._id)
+ dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._gid)
count = dbc.fetchone()
dbc.close()
- if count[0] > 0:
- return True
- else:
- return False
+ return count[0] > 0
- def _chkDelete(self, delUser, delAlias):
+ def _chk_delete(self, deluser, delalias):
"""Checks dependencies for deletion.
- Keyword arguments:
- delUser -- ignore available accounts (bool)
- delAlias -- ignore available aliases (bool)
+ Arguments:
+ deluser -- ignore available accounts (bool)
+ delalias -- ignore available aliases (bool)
"""
- if not delUser:
- hasUser = self._has('users')
+ if not deluser:
+ hasuser = self._has('users')
else:
- hasUser = False
- if not delAlias:
- hasAlias = self._has('alias')
+ 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)
+ hasalias = False
+ if hasuser and hasalias:
+ raise DomErr(_(u'There are accounts and aliases.'),
+ ACCOUNT_AND_ALIAS_PRESENT)
+ elif hasuser:
+ raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT)
+ elif hasalias:
+ raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT)
+
+ def _chk_state(self):
+ """Throws a DomainError if the Domain is new - not saved in the
+ database."""
+ if self._new:
+ raise DomErr(_(u"The domain '%s' doesn't exist.") % self._name,
+ NO_SUCH_DOMAIN)
+
+ @property
+ def gid(self):
+ """The GID of the Domain."""
+ return self._gid
+
+ @property
+ def name(self):
+ """The Domain's name."""
+ return self._name
+
+ @property
+ def directory(self):
+ """The Domain's directory."""
+ return self._directory
+
+ def set_directory(self, basedir):
+ """Set the path value of the Domain's directory, inside *basedir*.
+
+ Argument:
+
+ `basedir` : basestring
+ The base directory of all domains
+ """
+ assert self._new and self._directory is None
+ self._set_gid()
+ self._directory = os.path.join(basedir, choice(MAILDIR_CHARS),
+ str(self._gid))
+
+ @property
+ def transport(self):
+ """The Domain's transport."""
+ return self._transport
+
+ def set_transport(self, transport):
+ """Set the transport for the new Domain.
+
+ Argument:
+
+ `transport` : VirtualMailManager.Transport
+ The transport of the new Domain
+ """
+ assert self._new and isinstance(transport, Transport)
+ self._transport = transport
def save(self):
"""Stores the new domain in the database."""
- if 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)
+ if not self._new:
+ raise DomErr(_(u"The domain '%s' already exists.") % self._name,
+ DOMAIN_EXISTS)
+ assert self._directory is not None and self._transport is not None
+ dbc = self._dbh.cursor()
+ dbc.execute("INSERT INTO domain_data VALUES (%s, %s, %s)", self._gid,
+ self._transport.tid, self._directory)
+ dbc.execute("INSERT INTO domain_name VALUES (%s, %s, %s)", self._name,
+ self._gid, True)
+ self._dbh.commit()
+ dbc.close()
+ self._new = False
- def delete(self, delUser=False, delAlias=False):
+ 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)
+ Arguments:
+
+ `deluser` : bool
+ force deletion of all available accounts, default `False`
+ `delalias` : bool
+ force deletion of all available aliases, default `False`
"""
- 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)
+ self._chk_state()
+ self._chk_delete(deluser, delalias)
+ dbc = self._dbh.cursor()
+ for tbl in ('alias', 'users', 'relocated', 'domain_name',
+ 'domain_data'):
+ dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._gid))
+ self._dbh.commit()
+ dbc.close()
+ self._gid = 0
+ self._directory = self._transport = None
+ self._new = True
+
+ def update_transport(self, transport, force=False):
+ """Sets a new transport for the Domain.
- def updateTransport(self, transport, force=False):
- """Sets a new transport for the domain.
+ If *force* is `True` the new *transport* will be assigned to all
+ existing accounts. Otherwise the *transport* will be only used for
+ accounts created from now on.
- Keyword arguments:
- transport -- the new transport (str)
- force -- True/False force new transport for all accounts (bool)
+ Arguments:
+
+ `transport` : VirtualMailManager.Transport
+ the new transport
+ `force` : bool
+ enforce new transport setting for all accounts, default `False`
"""
- 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)
+ self._chk_state()
+ assert isinstance(transport, Transport)
+ if transport == self._transport:
+ return
+ dbc = self._dbh.cursor()
+ dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s",
+ transport.tid, self._gid)
+ if dbc.rowcount > 0:
+ self._dbh.commit()
+ if force:
+ dbc.execute("UPDATE users SET tid = %s WHERE gid = %s",
+ transport.tid, self._gid)
if dbc.rowcount > 0:
self._dbh.commit()
- 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
+ dbc.close()
+ self._transport = transport
- 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):
+ def get_info(self):
"""Returns a dictionary with information about the domain."""
- sql = """\
-SELECT gid, domainname, transport, domaindir, aliasdomains, accounts,
- aliases, relocated
+ self._chk_state()
+ sql = """SELECT gid, domainname, transport, domaindir, aliasdomains,
+ accounts, aliases, relocated
FROM vmm_domain_info
- WHERE gid = %i""" % self._id
+ WHERE gid = %i""" % self._gid
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))
+ 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."""
+ 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._id)
+ local_part", self._gid)
users = dbc.fetchall()
dbc.close()
accounts = []
@@ -236,56 +277,125 @@
accounts = [addr((account[0], _dom)) for account in users]
return accounts
- def getAliases(self):
- """Returns a list with all aliases from the domain."""
+ 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._id)
+ ORDER BY address", self._gid)
addresses = dbc.fetchall()
dbc.close()
aliases = []
- if len(addresses) > 0:
+ if addresses:
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."""
+ 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._id)
+ ORDER BY address", self._gid)
addresses = dbc.fetchall()
dbc.close()
relocated = []
- if len(addresses) > 0:
+ if addresses:
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."""
+ 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._id)
+ AND NOT is_primary ORDER BY domainname", self._gid)
anames = dbc.fetchall()
dbc.close()
aliasdomains = []
- if len(anames) > 0:
+ if anames:
aliasdomains = [aname[0] for aname in anames]
return aliasdomains
+
+def ace2idna(domainname):
+ """Converts the domain name `domainname` from ACE according to IDNA."""
+ return u'.'.join([ToUnicode(lbl) for lbl in domainname.split('.') if lbl])
+
+
+def check_domainname(domainname):
+ """Returns the validated domain name `domainname`.
+
+ It also converts the name of the domain from IDN to ASCII, if
+ necessary.
+
+ 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 = idn2ascii(domainname)
+ 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 idn2ascii(domainname):
+ """Converts the idn domain name `domainname` into punycode."""
+ return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl])
+
+
def search(dbh, pattern=None, like=False):
- if pattern is not None and like is False:
- pattern = VMM.VirtualMailManager.chkDomainname(pattern)
+ """'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 is None:
- pass
- elif like:
- sql += " WHERE domainname LIKE '%s'" % pattern
- else:
- sql += " WHERE domainname = '%s'" % pattern
+ 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)
@@ -311,3 +421,5 @@
domains[gid] = [None, domain]
return gids, domains
+
+del _
--- a/VirtualMailManager/EmailAddress.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/EmailAddress.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,75 +2,104 @@
# Copyright (c) 2008 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's EmailAddress class to handle e-mail addresses."""
+"""
+ VirtualMailManager.EmailAddress
+
+ Virtual Mail Manager's EmailAddress class to handle e-mail addresses.
+"""
+import re
-from __main__ import re, ERR
-from Exceptions import VMMEmailAddressException as VMMEAE
-import VirtualMailManager as VMM
+from VirtualMailManager.Domain import check_domainname
+from VirtualMailManager.constants.ERROR import \
+ DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG
+from VirtualMailManager.errors import EmailAddressError as EAErr
-RE_LOCALPART = """[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]"""
+
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+_ = lambda msg: msg
+
class EmailAddress(object):
+ """Simple class for validated e-mail addresses."""
__slots__ = ('_localpart', '_domainname')
+
def __init__(self, address):
+ """Creates a new instance from the string/unicode ``address``."""
+ assert isinstance(address, basestring)
self._localpart = None
self._domainname = None
- self.__chkAddress(address)
+ 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 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 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 __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
+ return '%s@%s' % (self._localpart, self._domainname)
- def __chkLocalpart(self, localpart):
- """Validates the local-part of an e-mail address.
+ def _chk_address(self, address):
+ """Checks if the string ``address`` could be used for an e-mail
+ address. If so, it will assign the corresponding values to the
+ attributes `_localpart` and `_domainname`."""
+ parts = address.split('@')
+ p_len = len(parts)
+ if p_len < 2:
+ raise EAErr(_(u"Missing the '@' sign in address %r") % address,
+ INVALID_ADDRESS)
+ elif p_len > 2:
+ raise EAErr(_(u"Too many '@' signs in address %r") % address,
+ INVALID_ADDRESS)
+ if not parts[0]:
+ raise EAErr(_(u'Missing local-part in address %r') % address,
+ LOCALPART_INVALID)
+ if not parts[1]:
+ raise EAErr(_(u'Missing domain name in address %r') % address,
+ DOMAIN_NO_NAME)
+ self._localpart = check_localpart(parts[0])
+ self._domainname = check_domainname(parts[1])
- 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
+
+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 \
+characters: %(i_chars)s") %
+ {'l_part': localpart, 'i_chars': i_chars},
+ LOCALPART_INVALID)
+ return localpart
+
+
+del _
--- a/VirtualMailManager/Exceptions.py Tue Apr 20 02:59:08 2010 +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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/Handler.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,724 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+ VirtualMailManager.Handler
+
+ A wrapper class. It wraps round all other classes and does some
+ dependencies checks.
+
+ Additionally it communicates with the PostgreSQL database, creates
+ or deletes directories of domains or users.
+"""
+
+import os
+import re
+
+from shutil import rmtree
+from subprocess import Popen, PIPE
+
+from pyPgSQL import PgSQL # python-pgsql - http://pypgsql.sourceforge.net
+
+import VirtualMailManager.constants.ERROR as ERR
+from VirtualMailManager import ENCODING, exec_ok, set_configuration
+from VirtualMailManager.Account import Account
+from VirtualMailManager.Alias import Alias
+from VirtualMailManager.AliasDomain import AliasDomain
+from VirtualMailManager.Config import Config as Cfg
+from VirtualMailManager.Domain import Domain, ace2idna, get_gid
+from VirtualMailManager.EmailAddress import EmailAddress
+from VirtualMailManager.errors import VMMError, AliasError, DomainError, \
+ RelocatedError
+from VirtualMailManager.Relocated import Relocated
+from VirtualMailManager.Transport import Transport
+from VirtualMailManager.ext.Postconf import Postconf
+
+
+SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
+RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$"""
+TYPE_ACCOUNT = 0x1
+TYPE_ALIAS = 0x2
+TYPE_RELOCATED = 0x4
+
+
+class Handler(object):
+ """Wrapper class to simplify the access on all the stuff from
+ VirtualMailManager"""
+ __slots__ = ('_Cfg', '_cfgFileName', '_dbh', '_scheme', '__warnings',
+ '_postconf')
+
+ 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._cfgFileName = ''
+ self.__warnings = []
+ self._Cfg = None
+ self._dbh = None
+
+ if os.geteuid():
+ raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
+ ERR.CONF_NOPERM)
+ if self.__chkCfgFile():
+ self._Cfg = Cfg(self._cfgFileName)
+ self._Cfg.load()
+ if not skip_some_checks:
+ self._Cfg.check()
+ self._chkenv()
+ # will be moved to the new password module and Alias
+ #self._scheme = self._Cfg.dget('misc.password_scheme')
+ #self._postconf = Postconf(self._Cfg.dget('bin.postconf'))
+ set_configuration(self._Cfg)
+
+ 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 VMMError(
+ _(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 PermissionError(_(
+ 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):
+ """"""
+ 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, self._Cfg.dget('misc.gid_mail'))
+ os.umask(old_umask)
+ elif not os.path.isdir(basedir):
+ raise VMMError(_(u'“%s” is not a directory.\n\
+(vmm.cfg: section "misc", option "base_directory")') %
+ basedir, ERR.NO_SUCH_DIRECTORY)
+ for opt, val in self._Cfg.items('bin'):
+ try:
+ exec_ok(val)
+ except VMMError, e:
+ if e.code is ERR.NO_SUCH_BINARY:
+ raise VMMError(_(u'“%(binary)s” doesn\'t exist.\n\
+(vmm.cfg: section "bin", option "%(option)s")') %
+ {'binary': val, 'option': opt},
+ ERR.NO_SUCH_BINARY)
+ elif e.code is ERR.NOT_EXECUTABLE:
+ raise VMMError(_(u'“%(binary)s” is not executable.\
+\n(vmm.cfg: section "bin", option "%(option)s")') %
+ {'binary': val, 'option': opt},
+ ERR.NOT_EXECUTABLE)
+ else:
+ raise
+
+ def __dbConnect(self):
+ """Creates a pyPgSQL.PgSQL.connection instance."""
+ if self._dbh is None or (isinstance(self._dbh, PgSQL.Connection) and
+ not self._dbh._isOpen):
+ try:
+ self._dbh = PgSQL.connect(
+ database=self._Cfg.dget('database.name'),
+ user=self._Cfg.pget('database.user'),
+ host=self._Cfg.dget('database.host'),
+ password=self._Cfg.pget('database.pass'),
+ client_encoding='utf8', unicode_results=True)
+ dbc = self._dbh.cursor()
+ dbc.execute("SET NAMES 'UTF8'")
+ dbc.close()
+ except PgSQL.libpq.DatabaseError, e:
+ raise VMMError(str(e), 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.uid > 0:
+ 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 __getAccount(self, address, password=None):
+ address = EmailAddress(address)
+ if not password is None:
+ password = self.__pwhash(password)
+ self.__dbConnect()
+ return Account(self._dbh, address, password)
+
+ def __getAlias(self, address):
+ address = EmailAddress(address)
+ self.__dbConnect()
+ return Alias(self._dbh, address)
+
+ def __getRelocated(self, address):
+ address = EmailAddress(address)
+ self.__dbConnect()
+ return Relocated(self._dbh, address)
+
+ def __getDomain(self, domainname):
+ self.__dbConnect()
+ return Domain(self._dbh, domainname)
+
+ 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.dget('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.dget('account.directory_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):
+ #TODO: clenaup!
+ os.umask(0006)
+ oldpwd = os.getcwd()
+ basedir = self._Cfg.dget('misc.base_directory')
+ domdirdirs = domdir.replace(basedir + '/', '').split('/')
+
+ os.chdir(basedir)
+ if not os.path.isdir(domdirdirs[0]):
+ self.__makedir(domdirdirs[0], 489, 0,
+ self._Cfg.dget('misc.gid_mail'))
+ os.chdir(domdirdirs[0])
+ os.umask(0007)
+ self.__makedir(domdirdirs[1], self._Cfg.dget('domain.directory_mode'),
+ 0, gid)
+ os.chdir(oldpwd)
+
+ def __subscribe(self, folderlist, uid, gid):
+ """Creates a subscriptions file with the mailboxes from `folderlist`"""
+ fname = os.path.join(self._Cfg.dget('maildir.name'), 'subscriptions')
+ sf = open(fname, 'w')
+ sf.write('\n'.join(folderlist))
+ sf.write('\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.dget('maildir.name')
+ folders = [maildir]
+ append = folders.append
+ for folder in self._Cfg.dget('maildir.folders').split(':'):
+ folder = folder.strip()
+ if len(folder) and not folder.count('..'):
+ if re.match(RE_MBOX_NAMES, folder):
+ append('%s/.%s' % (maildir, folder))
+ else:
+ self.__warnings.append(_('Skipped mailbox folder: %r') %
+ folder)
+ else:
+ self.__warnings.append(_('Skipped mailbox folder: %r') %
+ folder)
+
+ subdirs = ['cur', 'new', 'tmp']
+ mode = self._Cfg.dget('account.directory_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.__subscribe((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 VMMError(_(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 VMMError(_(
+ u'Detected owner/group mismatch in home directory.'),
+ ERR.MAILDIR_PERM_MISMATCH)
+ rmtree(userdir, ignore_errors=True)
+ else:
+ raise VMMError(_(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.dget('misc.base_directory')
+ domdirdirs = domdir.replace(basedir + '/', '').split('/')
+ domdirparent = os.path.join(basedir, domdirdirs[0])
+ if basedir.count('..') or domdir.count('..'):
+ raise VMMError(_(u'Found ".." in domain directory path.'),
+ ERR.FOUND_DOTS_IN_PATH)
+ if os.path.isdir(domdirparent):
+ os.chdir(domdirparent)
+ if os.lstat(domdirdirs[1]).st_gid != gid:
+ raise VMMError(_(
+ u'Detected group mismatch in domain 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.dget('bin.dovecotpw'), '-s', self._scheme,
+ '-p', password]
+ if self._Cfg.dget('misc.dovecot_version') >= 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 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 domainAdd(self, domainname, transport=None):
+ dom = self.__getDomain(domainname)
+ if transport is None:
+ dom.set_transport(Transport(self._dbh,
+ transport=self._Cfg.dget('misc.transport')))
+ else:
+ dom.set_transport(Transport(self._dbh, transport=transport))
+ dom.set_directory(self._Cfg.dget('misc.base_directory'))
+ dom.save()
+ self.__domDirMake(dom.directory, dom.gid)
+
+ def domainTransport(self, domainname, transport, force=None):
+ if force is not None and force != 'force':
+ raise DomainError(_(u"Invalid argument: “%s”") % force,
+ ERR.INVALID_OPTION)
+ dom = self.__getDomain(domainname)
+ trsp = Transport(self._dbh, transport=transport)
+ if force is None:
+ dom.update_transport(trsp)
+ else:
+ dom.update_transport(trsp, force=True)
+
+ def domainDelete(self, domainname, force=None):
+ if not force is None and force not in ['deluser', 'delalias',
+ 'delall']:
+ raise DomainError(_(u'Invalid argument: “%s”') %
+ force, ERR.INVALID_OPTION)
+ dom = self.__getDomain(domainname)
+ gid = dom.gid
+ domdir = dom.directory
+ if self._Cfg.dget('domain.force_deletion') or force == 'delall':
+ dom.delete(True, True)
+ elif force == 'deluser':
+ dom.delete(deluser=True)
+ elif force == 'delalias':
+ dom.delete(delalias=True)
+ else:
+ dom.delete()
+ if self._Cfg.dget('domain.delete_directory'):
+ self.__domDirDelete(domdir, gid)
+
+ def domainInfo(self, domainname, details=None):
+ if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
+ 'relocated']:
+ raise VMMError(_(u'Invalid argument: “%s”') % details,
+ ERR.INVALID_AGUMENT)
+ dom = self.__getDomain(domainname)
+ dominfo = dom.get_info()
+ if dominfo['domainname'].startswith('xn--'):
+ dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname'])
+ if details is None:
+ return dominfo
+ elif details == 'accounts':
+ return (dominfo, dom.get_accounts())
+ elif details == 'aliasdomains':
+ return (dominfo, dom.get_aliase_names())
+ elif details == 'aliases':
+ return (dominfo, dom.get_aliases())
+ elif details == 'relocated':
+ return(dominfo, dom.get_relocated())
+ else:
+ return (dominfo, dom.get_aliase_names(), dom.get_accounts(),
+ dom.get_aliases(), dom.get_relocated())
+
+ def aliasDomainAdd(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.__getDomain(domainname)
+ aliasDom = AliasDomain(self._dbh, aliasname)
+ aliasDom.set_destination(dom)
+ aliasDom.save()
+
+ def aliasDomainInfo(self, aliasname):
+ """Returns a dict (keys: "alias" and "domain") with the names of
+ the alias domain and its primary domain."""
+ self.__dbConnect()
+ aliasDom = AliasDomain(self._dbh, aliasname)
+ return aliasDom.info()
+
+ def aliasDomainSwitch(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.__getDomain(domainname)
+ aliasDom = AliasDomain(self._dbh, aliasname)
+ aliasDom.set_destination(dom)
+ aliasDom.switch()
+
+ def aliasDomainDelete(self, aliasname):
+ """Deletes the given alias domain.
+
+ Argument:
+
+ `aliasname` : basestring
+ The name of the alias domain
+ """
+ self.__dbConnect()
+ aliasDom = AliasDomain(self._dbh, aliasname)
+ aliasDom.delete()
+
+ def domainList(self, pattern=None):
+ 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 characters.") %
+ pattern, ERR.DOMAIN_INVALID)
+ self.__dbConnect()
+ return search(self._dbh, pattern=pattern, like=like)
+
+ def userAdd(self, emailaddress, password):
+ if password is None or (isinstance(password, basestring) and
+ not len(password)):
+ raise ValueError('could not accept password: %r' % password)
+ acc = self.__getAccount(emailaddress, self.__pwhash(password))
+ acc.save(self._Cfg.dget('maildir.name'),
+ self._Cfg.dget('misc.dovecot_version'),
+ self._Cfg.dget('account.smtp'),
+ self._Cfg.dget('account.pop3'),
+ self._Cfg.dget('account.imap'),
+ self._Cfg.dget('account.sieve'))
+ self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
+
+ def aliasAdd(self, aliasaddress, *targetaddresses):
+ """Creates a new `Alias` entry for the given *aliasaddress* with
+ the given *targetaddresses*."""
+ alias = self.__getAlias(aliasaddress)
+ destinations = [EmailAddress(address) for address in targetaddresses]
+ warnings = []
+ destinations = alias.add_destinations(destinations,
+ long(self._postconf.read('virtual_alias_expansion_limit')),
+ warnings)
+ if warnings:
+ self.__warnings.append(_('Ignored destination addresses:'))
+ self.__warnings.extend((' * %s' % w for w in warnings))
+ for destination in destinations:
+ gid = get_gid(self._dbh, destination.domainname)
+ if gid and (not Handler.accountExists(self._dbh, destination) and
+ not Handler.aliasExists(self._dbh, destination)):
+ self.__warnings.append(
+ _(u"The destination account/alias %r doesn't exist.") %
+ str(destination))
+
+ def userDelete(self, emailaddress, force=None):
+ if force not in [None, 'delalias']:
+ raise VMMError(_(u"Invalid argument: “%s”") % force,
+ ERR.INVALID_AGUMENT)
+ acc = self.__getAccount(emailaddress)
+ uid = acc.getUID()
+ gid = acc.getGID()
+ acc.delete(force)
+ if self._Cfg.dget('account.delete_directory'):
+ try:
+ self.__userDirDelete(acc.getDir('domain'), uid, gid)
+ except VMMError, 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
+
+ def aliasInfo(self, aliasaddress):
+ """Returns an iterator object for all destinations (`EmailAddress`
+ instances) for the `Alias` with the given *aliasaddress*."""
+ alias = self.__getAlias(aliasaddress)
+ try:
+ return alias.get_destinations()
+ except AliasError, err:
+ if err.code == ERR.NO_SUCH_ALIAS:
+ other = self._chk_other_address_types(alias.address,
+ TYPE_ALIAS)
+ if other is TYPE_ACCOUNT:
+ raise VMMError(_(u"There is already an account with the \
+address '%s'.") %
+ alias.address, ERR.ACCOUNT_EXISTS)
+ elif other is TYPE_RELOCATED:
+ raise VMMError(_(u"There is already a relocated user \
+with the address '%s'.") %
+ alias.address, ERR.RELOCATED_EXISTS)
+ else: # unknown address
+ raise
+ else: # something other went wrong
+ raise
+
+ def aliasDelete(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.__getAlias(aliasaddress)
+ if targetaddress is None:
+ alias.delete()
+ else:
+ alias.del_destination(EmailAddress(targetaddress))
+
+ def userInfo(self, emailaddress, details=None):
+ if details not in (None, 'du', 'aliases', 'full'):
+ raise VMMError(_(u'Invalid argument: “%s”') % details,
+ ERR.INVALID_AGUMENT)
+ acc = self.__getAccount(emailaddress)
+ info = acc.getInfo(self._Cfg.dget('misc.dovecot_version'))
+ if self._Cfg.dget('account.disk_usage') 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 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.__dbConnect()
+ return get_account_by_uid(uid, self._dbh)
+
+ def userPassword(self, emailaddress, password):
+ if password is None or (isinstance(password, basestring) and
+ not len(password)):
+ raise ValueError('could not accept password: %r' % password)
+ acc = self.__getAccount(emailaddress)
+ if acc.getUID() == 0:
+ raise VMMError(_(u"Account doesn't exist"),
+ ERR.NO_SUCH_ACCOUNT)
+ 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.dget('misc.dovecot_version'), 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.dget('misc.dovecot_version'), service)
+
+ def relocatedAdd(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.__getRelocated(emailaddress)
+ relocated.set_destination(EmailAddress(targetaddress))
+
+ def relocatedInfo(self, emailaddress):
+ """Returns the target address of the relocated user with the given
+ *emailaddress*."""
+ relocated = self.__getRelocated(emailaddress)
+ try:
+ return relocated.get_info()
+ except RelocatedError, err:
+ if err.code == ERR.NO_SUCH_RELOCATED:
+ other = self._chk_other_address_types(relocated.address,
+ TYPE_RELOCATED)
+ if other is TYPE_ACCOUNT:
+ raise VMMError(_(u"There is already an account with the \
+address '%s'.") %
+ relocated.address, ERR.ACCOUNT_EXISTS)
+ elif other is TYPE_ALIAS:
+ raise VMMError(_(u"There is already an alias with the \
+address '%s'.") %
+ relocated.address, ERR.ALIAS_EXISTS)
+ else: # unknown address
+ raise
+ else: # something other went wrong
+ raise
+
+ def relocatedDelete(self, emailaddress):
+ """Deletes the relocated user with the given *emailaddress* from
+ the database."""
+ relocated = self.__getRelocated(emailaddress)
+ relocated.delete()
+
+ def __del__(self):
+ if isinstance(self._dbh, PgSQL.Connection) and self._dbh._isOpen:
+ self._dbh.close()
--- a/VirtualMailManager/MailLocation.py Tue Apr 20 02:59:08 2010 +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 Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Relocated.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,102 +2,115 @@
# Copyright (c) 2008 - 2010, Pascal Volk
# See COPYING for distribution information.
-"""Virtual Mail Manager's Relocated class to manage relocated users."""
+"""
+ VirtualMailManager.Relocated
+
+ Virtual Mail Manager's Relocated class to handle relocated users.
+"""
-from __main__ import ERR
-from Exceptions import VMMRelocatedException as VMMRE
-from Domain import Domain
-from EmailAddress import EmailAddress
-import VirtualMailManager as VMM
+from VirtualMailManager.Domain import get_gid
+from VirtualMailManager.EmailAddress import EmailAddress
+from VirtualMailManager.errors import RelocatedError as RErr
+from VirtualMailManager.constants.ERROR import NO_SUCH_DOMAIN, \
+ NO_SUCH_RELOCATED, RELOCATED_ADDR_DEST_IDENTICAL, RELOCATED_EXISTS
+
+
+_ = lambda msg: msg
+
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)
+ """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 = 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)
+ self._gid = get_gid(self._dbh, self._addr.domainname)
+ if not self._gid:
+ raise RErr(_(u"The domain %r doesn't exist.") %
+ self._addr.domainname, NO_SUCH_DOMAIN)
+ self._dest = None
+
+ self.__load()
- def _exists(self):
+ 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 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)
+ 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]
+ if destination:
+ self._dest = EmailAddress(destination[0])
+
+ @property
+ def address(self):
+ """The Relocated's EmailAddress instance."""
+ return self._addr
+
+ def set_destination(self, destination):
+ """Sets/updates the new address of the relocated user."""
+ update = False
+ assert isinstance(destination, EmailAddress)
+ if self._addr == destination:
+ raise RErr(_(u'Address and destination are identical.'),
+ RELOCATED_ADDR_DEST_IDENTICAL)
+ if self._dest:
+ if self._dest == destination:
+ raise RErr(_(u"The relocated user '%s' already exists.") %
+ self._addr, RELOCATED_EXISTS)
+ else:
+ self._dest = destination
+ update = True
else:
- raise VMMRE(
- _(u"The relocated user “%s” doesn't exist.") % self._addr,
- ERR.NO_SUCH_RELOCATED)
+ self._dest = destination
+
+ dbc = self._dbh.cursor()
+ if not update:
+ dbc.execute('INSERT INTO relocated VALUES (%s, %s, %s)',
+ self._gid, self._addr.localpart, str(self._dest))
+ else:
+ dbc.execute('UPDATE relocated SET destination=%s \
+WHERE gid=%s AND address=%s',
+ str(self._dest), self._gid, self._addr.localpart)
+ self._dbh.commit()
+ dbc.close()
+
+ def get_info(self):
+ """Returns the address to which mails should be sent."""
+ if not self._dest:
+ raise RErr(_(u"The relocated user '%s' doesn't exist.") %
+ self._addr, NO_SUCH_RELOCATED)
+ return self._dest
def delete(self):
+ """Deletes the relocated entry from the database."""
+ if not self._dest:
+ raise RErr(_(u"The relocated user '%s' doesn't exist.") %
+ self._addr, NO_SUCH_RELOCATED)
dbc = self._dbh.cursor()
dbc.execute("DELETE FROM relocated WHERE gid = %s AND address = %s",
- self._gid, self._addr._localpart)
- rowcount = dbc.rowcount
- dbc.close()
- if rowcount > 0:
+ self._gid, self._addr.localpart)
+ if dbc.rowcount > 0:
self._dbh.commit()
- else:
- raise VMMRE(
- _(u"The relocated user “%s” doesn't exist.") % self._addr,
- ERR.NO_SUCH_RELOCATED)
+ dbc.close()
+ self._dest = None
+
+del _
--- a/VirtualMailManager/Transport.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/Transport.py Tue Apr 20 03:04:16 2010 +0000
@@ -2,89 +2,95 @@
# 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."""
+"""
+ VirtualMailManager.Transport
-from __main__ import ERR
-from Exceptions import VMMTransportException
+ Virtual Mail Manager's Transport class to manage the transport for
+ domains and accounts.
+"""
+
+from VirtualMailManager.constants.ERROR import UNKNOWN_TRANSPORT_ID
+from VirtualMailManager.errors import TransportError
+from VirtualMailManager.pycompat import any
+
class Transport(object):
"""A wrapper class that provides access to the transport table"""
- __slots__ = ('__id', '__transport', '_dbh')
+ __slots__ = ('_tid', '_transport', '_dbh')
+
def __init__(self, dbh, tid=None, transport=None):
"""Creates a new Transport instance.
- Either tid or transport must be specified.
+ 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 (long)
+ tid -- the id of a transport (int/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)
+ assert any((tid, transport))
+ if tid:
+ assert not isinstance(tid, bool) and isinstance(tid, (int, long))
+ self._tid = tid
self._loadByID()
else:
- self.__transport = transport
+ assert isinstance(transport, basestring)
+ self._transport = transport
self._loadByName()
+ @property
+ def tid(self):
+ """The transport's unique ID."""
+ return self._tid
+
+ @property
+ def transport(self):
+ """The transport's value, ex: 'dovecot:'"""
+ return self._transport
+
def __eq__(self, other):
if isinstance(other, self.__class__):
- return self.__id == other.getID()
+ return self._tid == other.tid
return NotImplemented
def __ne__(self, other):
if isinstance(other, self.__class__):
- return self.__id != other.getID()
+ return self._tid != other.tid
return NotImplemented
def __str__(self):
- return self.__transport
+ return self._transport
def _loadByID(self):
dbc = self._dbh.cursor()
- dbc.execute('SELECT transport FROM transport WHERE tid = %s', self.__id)
+ dbc.execute('SELECT transport FROM transport WHERE tid=%s', self._tid)
result = dbc.fetchone()
dbc.close()
- if result is not None:
- self.__transport = result[0]
+ if result:
+ self._transport = result[0]
else:
- raise VMMTransportException(_('Unknown tid specified.'),
- ERR.UNKNOWN_TRANSPORT_ID)
+ raise TransportError(_('Unknown tid specified.'),
+ UNKNOWN_TRANSPORT_ID)
def _loadByName(self):
dbc = self._dbh.cursor()
dbc.execute('SELECT tid FROM transport WHERE transport = %s',
- self.__transport)
+ self._transport)
result = dbc.fetchone()
dbc.close()
- if result is not None:
- self.__id = result[0]
+ if result:
+ self._tid = 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._tid = dbc.fetchone()[0]
+ dbc.execute('INSERT INTO transport VALUES (%s, %s)', self._tid,
+ 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 Tue Apr 20 02:59:08 2010 +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 Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/__init__.py Tue Apr 20 03:04:16 2010 +0000
@@ -1,15 +1,33 @@
# -*- coding: UTF-8 -*-
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.
-# package initialization code
-#
+
+"""
+ VirtualMailManager
+ VirtualMailManager package initialization code
+"""
+
+import gettext
import os
-import re
import locale
-from constants.VERSION import *
-import constants.ERROR as ERR
+
+from VirtualMailManager.constants.ERROR import \
+ NOT_EXECUTABLE, NO_SUCH_BINARY, NO_SUCH_DIRECTORY
+from VirtualMailManager.constants.version import __author__, __date__, \
+ __version__
+from VirtualMailManager.errors import VMMError
+
+
+__all__ = [
+ # version information from VERSION
+ '__author__', '__date__', '__version__',
+ # defined stuff
+ 'ENCODING', 'Configuration', 'exec_ok', 'expand_path', 'get_unicode',
+ 'is_dir', 'set_configuration',
+]
+
# Try to set all of the locales according to the current
# environment variables and get the character encoding.
@@ -19,33 +37,67 @@
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')
+Configuration = None
+
+gettext.install('vmm', '/usr/local/share/locale', unicode=1)
+
+
+_ = lambda msg: msg
+
+
+def set_configuration(cfg_obj):
+ """Assigns the *cfg_obj* to the global `Configuration`.
+ *cfg_obj* has to be a `VirtualMailManager.Config.Config` instance."""
+ from VirtualMailManager.Config import Config
+ assert isinstance(cfg_obj, Config)
+ global Configuration
+ Configuration = cfg_obj
+
+
+def get_unicode(string):
+ """Converts `string` to `unicode`, if necessary."""
+ if isinstance(string, unicode):
+ return string
+ return unicode(string, ENCODING, 'replace')
+
+
+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 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)
+
+def is_dir(path):
+ """Checks if `path` is a directory.
+
+ Throws a `VMMError` if `path` is not a directory.
+
+ """
+ path = expand_path(path)
+ if not os.path.isdir(path):
+ raise VMMError(_(u"'%s' is not a directory") % get_unicode(path),
+ NO_SUCH_DIRECTORY)
+ return path
+
-__all__ = [
- # imported modules
- 'os', 're', 'locale',
- # version information from VERSION
- '__author__', '__date__', '__version__',
- # error codes
- 'ERR',
- # defined stuff
- 'ENCODING', 'w_std', 'w_err'
- ]
-# EOF
+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"'%s' is not a file") % 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
+
+
+del _
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/Config.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,88 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+ VirtualMailManager.cli.CliConfig
+
+ Adds some interactive stuff to the Config class.
+"""
+
+from ConfigParser import RawConfigParser
+from shutil import copy2
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.Config import Config, ConfigValueError, LazyConfig
+from VirtualMailManager.errors import ConfigError
+from VirtualMailManager.cli import w_std
+from VirtualMailManager.constants.ERROR import VMM_TOO_MANY_FAILURES
+
+
+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 \
+[%(current_value)s]: ')
+ failures = 0
+
+ w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
+ for s in sections:
+ w_std(_(u'* Configuration section: %r') % s)
+ for opt, val in self.items(s):
+ 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' % (s, opt), newval)
+ break
+ except (ValueError, ConfigValueError), e:
+ w_std(_(u'Warning: %s') % e)
+ failures += 1
+ if failures > 2:
+ raise ConfigError(
+ _(u'Too many failures - try again later.'),
+ VMM_TOO_MANY_FAILURES)
+ else:
+ break
+ print
+ if self._modified:
+ self.__saveChanges()
+
+ def set(self, option, value):
+ """Set the value of an option.
+
+ If the new `value` has been set, the configuration file will be
+ immediately updated.
+
+ Throws a ``ValueError`` if `value` couldn't be converted to
+ ``LazyConfigOption.cls``"""
+ section, option_ = self._get_section_option(option)
+ val = self._cfg[section][option_].cls(value)
+ if self._cfg[section][option_].validate:
+ val = self._cfg[section][option_].validate(val)
+ # Do not write default values also skip identical values
+ if not self._cfg[section][option_].default is None:
+ old_val = self.dget(option)
+ else:
+ old_val = self.pget(option)
+ if val == old_val:
+ return
+ if not RawConfigParser.has_section(self, section):
+ self.add_section(section)
+ RawConfigParser.set(self, section, option_, val)
+ self.__saveChanges()
+
+ def __saveChanges(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()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/Handler.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,78 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+ VirtualMailManager.cli.Handler
+
+ A derived Handler class with a few changes/additions for cli use.
+"""
+
+import os
+
+from VirtualMailManager.errors import VMMError
+from VirtualMailManager.Handler import Handler
+from VirtualMailManager.cli import read_pass
+from VirtualMailManager.cli.Config import CliConfig as Cfg
+from VirtualMailManager.constants.ERROR import INVALID_SECTION
+from VirtualMailManager.ext.Postconf import Postconf
+
+
+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._cfgFileName)
+ self._Cfg.load()
+ if not skip_some_checks:
+ self._Cfg.check()
+ self._chkenv()
+ self._scheme = self._Cfg.dget('misc.password_scheme')
+ self._postconf = Postconf(self._Cfg.dget('bin.postconf'))
+
+ def cfgSet(self, option, value):
+ 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 userAdd(self, emailaddress, password):
+ if password is None:
+ password = read_pass()
+ super(CliHandler, self).userAdd(emailaddress, password)
+
+ def userPassword(self, emailaddress, password):
+ if password is None:
+ password = read_pass()
+ super(CliHandler, self).userPassword(emailaddress, password)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/__init__.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,107 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+ VirtualMailManager.cli
+
+ VirtualMailManager's command line interface.
+"""
+
+import os
+from cStringIO import StringIO
+from getpass import getpass
+from textwrap import TextWrapper
+
+from VirtualMailManager import ENCODING
+
+
+__all__ = ('get_winsize', 'read_pass', 'string_io', 'w_err', 'w_std')
+
+_std_write = os.sys.stdout.write
+_err_write = os.sys.stderr.write
+
+
+def w_std(*args):
+ """Writes a line for each arg of *args*, encoded in the current
+ ENCODING, to stdout.
+
+ """
+ _std_write('\n'.join(arg.encode(ENCODING, 'replace') for arg in args))
+ _std_write('\n')
+
+
+def w_err(code, *args):
+ """Writes a line for each arg of *args*, encoded in the current
+ ENCODING, to stderr.
+
+ This function additional interrupts the program execution and uses
+ *code* as the system exit status.
+
+ """
+ _err_write('\n'.join(arg.encode(ENCODING, 'replace') for arg in args))
+ _err_write('\n')
+ 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
+
+ from array import array
+ from fcntl import ioctl
+ from termios import TIOCGWINSZ
+
+ #"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 VMMException 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 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:
+ failures += 1
+ w_std(_(u'Sorry, passwords do not match'))
+ continue
+ if not clear0:
+ failures += 1
+ w_std(_(u'Sorry, empty passwords are not permitted'))
+ continue
+ mismatched = False
+ return clear0
+
+
+def string_io():
+ """Returns a new `cStringIO.StringIO` instance."""
+ return StringIO()
--- a/VirtualMailManager/constants/ERROR.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/constants/ERROR.py Tue Apr 20 03:04:16 2010 +0000
@@ -4,6 +4,7 @@
ACCOUNT_AND_ALIAS_PRESENT = 20
ACCOUNT_EXISTS = 21
+ACCOUNT_MISSING_PASSWORD = 69
ACCOUNT_PRESENT = 22
ALIASDOMAIN_EXISTS = 23
ALIASDOMAIN_ISDOMAIN = 24
@@ -46,7 +47,8 @@
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
+UNKNOWN_MAILLOCATION_NAME = 64
+UNKNOWN_SERVICE = 65
+UNKNOWN_TRANSPORT_ID = 66
+VMM_ERROR = 67
+VMM_TOO_MANY_FAILURES = 68
--- a/VirtualMailManager/constants/VERSION.py Tue Apr 20 02:59:08 2010 +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__']
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/constants/version.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,17 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+
+"""
+ VirtualMailManager.constants.version
+
+ VirtualMailManager's versions information.
+"""
+
+__all__ = ['__author__', '__date__', '__version__']
+AUTHOR = 'Pascal Volk <neverseen@users.sourceforge.net>'
+RELDATE = '2009-09-09'
+VERSION = '0.5.2'
+__author__ = AUTHOR
+__date__ = RELDATE
+__version__ = VERSION
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/errors.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,75 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2007 - 2010, 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
+
+
+class TransportError(VMMError):
+ """Exception class for Transport exceptions"""
+ pass
--- a/VirtualMailManager/ext/Postconf.py Tue Apr 20 02:59:08 2010 +0000
+++ b/VirtualMailManager/ext/Postconf.py Tue Apr 20 03:04:16 2010 +0000
@@ -4,10 +4,11 @@
"""A small - r/o - wrapper class for Postfix' postconf."""
+import re
from subprocess import Popen, PIPE
-from __main__ import re, ERR
-from VirtualMailManager.Exceptions import VMMException
+import VirtualMailManager.constants.ERROR as ERR
+from VirtualMailManager.errors import VMMError
RE_PC_PARAMS = """^\w+$"""
RE_PC_VARIABLES = r"""\$\b\w+\b"""
@@ -38,7 +39,7 @@
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\
+ raise VMMError(_(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:
@@ -65,16 +66,15 @@
out, err = Popen([self.__bin, '-h', parameter], stdout=PIPE,
stderr=PIPE).communicate()
if len(err):
- raise VMMException(err.strip(), ERR.VMM_ERROR)
+ raise VMMError(err.strip(), ERR.VMM_ERROR)
return out.strip()
def __readMulti(self, parameters):
cmd = [self.__bin]
- for parameter in parameters:
- cmd.append(parameter[1:])
+ cmd.extend(parameter[1:] for parameter in parameters)
out, err = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
if len(err):
- raise VMMException(err.strip(), ERR.VMM_ERROR)
+ raise VMMError(err.strip(), ERR.VMM_ERROR)
par_val = {}
for line in out.splitlines():
par, val = line.split(' = ')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/maillocation.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,109 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2010, 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.pycompat import any
+
+
+__all__ = ('MailLocation', 'known_format',
+ 'MAILDIR_ID', 'MBOX_ID', 'MDBOX_ID', 'SDBOX_ID')
+
+MAILDIR_ID = 0x1
+MBOX_ID = 0x2
+MDBOX_ID = 0x3
+SDBOX_ID = 0x4
+MAILDIR_NAME = 'Maildir'
+MBOX_NAME = 'mail'
+MDBOX_NAME = 'mdbox'
+SDBOX_NAME = 'dbox'
+
+_storage = {
+ MAILDIR_ID: dict(dovecot_version=10, postfix=True, prefix='maildir:',
+ directory=MAILDIR_NAME, mid=MAILDIR_ID),
+ MBOX_ID: dict(dovecot_version=10, postfix=True, prefix='mbox:',
+ directory=MBOX_NAME, mid=MBOX_ID),
+ MDBOX_ID: dict(dovecot_version=20, postfix=False, prefix='mdbox:',
+ directory=MDBOX_NAME, mid=MDBOX_ID),
+ SDBOX_ID: dict(dovecot_version=12, postfix=False, prefix='dbox:',
+ directory=SDBOX_NAME, mid=SDBOX_ID),
+}
+
+_format_id = {
+ 'maildir': MAILDIR_ID,
+ 'mbox': MBOX_ID,
+ 'mdbox': MDBOX_ID,
+ 'dbox': SDBOX_ID,
+}
+
+
+class MailLocation(object):
+ """A small class for mail_location relevant information."""
+ __slots__ = ('_info')
+
+ def __init__(self, mid=None, format=None):
+ """Creates a new MailLocation instance.
+
+ Either a mid or the format must be specified.
+
+ Keyword arguments:
+ mid -- the id of a mail_location (int)
+ one of the maillocation constants: `MAILDIR_ID`, `MBOX_ID`,
+ `MDBOX_ID` and `SDBOX_ID`
+ format -- the mailbox format of the mail_location. One out of:
+ ``maildir``, ``mbox``, ``dbox`` and ``mdbox``.
+ """
+ assert any((mid, format))
+ if mid:
+ assert isinstance(mid, (int, long)) and mid in _storage
+ self._info = _storage[mid]
+ else:
+ assert isinstance(format, basestring) and \
+ format.lower() in _format_id
+ self._info = _storage[_format_id[format.lower()]]
+
+ def __str__(self):
+ return '%(prefix)s~/%(directory)s' % self._info
+
+ @property
+ def directory(self):
+ """The mail_location's directory name."""
+ return self._info['directory']
+
+ @property
+ def dovecot_version(self):
+ """The required Dovecot version (concatenated major and minor
+ parts) for this mailbox format."""
+ return self._info['dovecot_version']
+
+ @property
+ def postfix(self):
+ """`True` if Postfix supports this mailbox format, else `False`."""
+ return self._info['postfix']
+
+ @property
+ def prefix(self):
+ """The prefix of the mail_location."""
+ return self._info['prefix']
+
+ @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._info['mid']
+
+
+def known_format(format):
+ """Checks if the mailbox *format* is known, returns bool."""
+ return format.lower() in _format_id
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/pycompat.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,38 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2010, 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/doc/Makefile Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 03:04:16 2010 +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 Tue Apr 20 02:59:08 2010 +0000
+++ b/install.sh Tue Apr 20 03:04:16 2010 +0000
@@ -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}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/de/man1/vmm.1.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,474 @@
+=====
+ vmm
+=====
+
+-----------------------------------------------------------------------------
+Kommandozeilenprogramm zur Verwaltung von E-Mail-Domains, -Konten und -Aliase
+-----------------------------------------------------------------------------
+
+:Author: Pascal Volk <neverseen@users.sourceforge.net>
+:Date: 2010-01-30
+:Version: vmm-0.6.0
+:Manual group: vmm Manual
+:Manual section: 1
+
+.. contents::
+ :backlinks: top
+ :class: htmlout
+
+SYNOPSIS
+========
+vmm *Unterbefehl* *Objekt* [ *Argumente* ]
+
+
+BESCHREIBUNG
+============
+**vmm** (a virtual mail manager) ist ein Kommandozeilenprogramm für
+Administratoren/Postmaster zur Verwaltung von (Alias-) Domains, Konten und
+Alias-Adressen. Es wurde entwickelt für Dovecot und Postfix mit einem
+PostgreSQL-Backend.
+
+
+UNTERBEFEHLE
+============
+Von jedem 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.
+
+
+ALLGEMEINE UNTERBEFEHLE
+-----------------------
+.. _configure:
+
+``configure (cf) [ Sektion ]``
+ Startet den interaktiven Konfiguration-Modus für alle Sektionen der
+ Konfiguration.
+
+ 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.
+
+ Wurde das optionale Argument *Sektion* angegeben, werden nur die Optionen
+ der angegebenen Sektion angezeigt und können geändert werden. Folgende
+ Sektionen sind vorhanden:
+
+ | - **account**
+ | - **bin**
+ | - **database**
+ | - **domain**
+ | - **maildir**
+ | - **misc**
+
+ Beispiel::
+
+ vmm configure domain
+ Verwende Konfigurationsdatei: /usr/local/etc/vmm.cfg
+
+ * Konfigurations Sektion: „domain“
+ Neuer Wert für Option directory_mode [504]:
+ Neuer Wert für Option delete_directory [False]: 1
+ Neuer Wert für Option auto_postmaster [True]:
+ Neuer Wert für Option force_deletion [False]: on
+
+.. _getuser:
+
+``getuser (gu) userid``
+ Wenn nur eine UserID vorhanden ist, zum Beispiel aus der Prozessliste,
+ kann mit dem Unterbefehl **getuser** die E-Mail-Adresse des Users
+ ermittelt werden.
+
+ Beispiel::
+
+ vmm getuser 70004
+ Account Informationen
+ ---------------------
+ UID............: 70004
+ GID............: 70000
+ Address........: c.user@example.com
+
+.. _listdomains:
+
+``listdomains (ld) [ Muster ]``
+ 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 *Muster*
+ angegeben wird.
+
+ Um eine Wildcard-Suche durchzuführen kann das **%**-Zeichen am Anfang
+ und/oder Ende des *Musters* verwendet werden.
+
+ Beispiel::
+
+ vmm listdomains %example%
+ Übereinstimmende Domains
+ ------------------------
+ [+] example.com
+ [-] e.g.example.com
+ [-] example.name
+ [+] example.net
+ [+] example.org
+
+.. _help:
+
+``help (h)``
+ Dieser Unterbefehl gibt alle verfügbaren Kommandos auf der Standardausgabe
+ (stdout) aus. Danach beendet sich **vmm**.
+
+.. _version:
+
+``version (v)``
+ Gibt Versionsinformationen zu **vmm** aus.
+
+DOMAIN UNTERBEFEHLE
+-------------------
+.. _domainadd:
+
+``domainadd (da) Domain [ Transport ]``
+ Fügt eine neue *Domain* in die Datenbank ein und erstellt das
+ Domain-Verzeichnis.
+
+ Ist das optionale Argument *Transport* angegeben, wird der
+ Vorgabe-Transport (|misc.transport|_) aus |vmm.cfg(5)|_ für diese *Domain*
+ ignoriert und der angegebene *Transport* verwendet. Der angegebene
+ *Transport* ist gleichzeitig der Vorgabe-Transport für alle neuen Konten,
+ die unter dieser Domain eingerichtet werden.
+
+ Beispiele::
+
+ vmm domainadd support.example.com smtp:mx1.example.com
+ vmm domainadd sales.example.com
+
+.. _domaininfo:
+
+``domaininfo (di) Domain [ Details ]``
+ Dieser Unterbefehl zeigt Information zur angegeben *Domain* an.
+
+ Um detaillierte Informationen über die *Domain* zu erhalten, kann das
+ optionale Argument *Details* angegeben werden. Ein möglicher Wert für
+ *Details* kann eines der folgenden fünf Schlüsselwörter sein:
+
+ ``accounts``
+ um alle existierenden Konten aufzulisten
+ ``aliasdomains``
+ um alle zugeordneten Alias-Domains aufzulisten
+ ``aliases``
+ um alle verfügbaren Alias-Adressen aufzulisten
+ ``relocated``
+ um alle Adressen der relocated Users aufzulisten
+ ``full``
+ um alle oben genannten Informationen aufzulisten
+
+ Beispiel::
+
+ vmm domaininfo sales.example.com
+ Domain Informationen
+ --------------------
+ Domainname.....: sales.example.com
+ GID............: 70002
+ Transport......: dovecot:
+ Domaindir......: /home/mail/5/70002
+ Aliasdomains...: 0
+ Accounts.......: 0
+ Aliases........: 0
+ Relocated......: 0
+
+.. _domaintransport:
+
+``domaintransport (dt) Domain Transport [ force ]``
+ Ein neuer *Transport* für die angegebene *Domain* kann mit diesem
+ Unterbefehl festgelegt werden.
+
+ Wurde das optionale Schlüsselwort **force** angegeben, so werden alle
+ bisherigen Transport-Einstellungen, der in dieser Domain vorhandenen
+ Konten, mit dem neuen *Transport* überschrieben.
+
+ Andernfalls gilt der neue *Transport* nur für Konten, die zukünftig
+ erstellt werden.
+
+ Beispiel::
+
+ vmm domaintransport support.example.com dovecot:
+
+.. _domaindelete:
+
+``domaindelete (dd) Domain [ delalias | deluser | delall ]``
+ Mit diesem Unterbefehl wird die angegebene *Domain* gelöscht.
+
+ Sollten der *Domain* Konten und/oder Aliase zugeordnet sein, wird **vmm**
+ 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: **delalias**, **deluser**
+ oder **delall**.
+
+ Sollten Sie wirklich immer wissen was Sie tun, so editieren Sie Ihre
+ *vmm.cfg* und setzen den Wert der Option |domain.force_deletion|_ auf
+ true. Dann werden Sie beim Löschen von Domains nicht mehr wegen vorhanden
+ Konten/Aliase gewarnt.
+
+
+ALIAS-DOMAIN UNTERBEFEHLE
+-------------------------
+.. _aliasdomainadd:
+
+``aliasdomainadd (ada) Aliasdomain Zieldomain``
+ Mit diesem Unterbefehl wird der *Zieldomain* die Alias-Domain
+ *Aliasdomain* zugewiesen.
+
+ Beispiel::
+
+ vmm aliasdomainadd example.name example.com
+
+.. _aliasdomaininfo:
+
+``aliasdomaininfo (adi) Aliasdomain``
+ Dieser Unterbefehl informiert darüber, welcher Domain die Alias-Domain
+ *Aliasdomain* zugeordnet ist.
+
+ Beispiel::
+
+ vmm aliasdomaininfo example.name
+ Alias-Domain Informationen
+ --------------------------
+ Die Alias-Domain example.name gehört zu:
+ * example.com
+
+.. _aliasdomainswitch:
+
+``aliasdomainswitch (ads) Aliasdomain Zieldomain``
+ Wenn das Ziel der vorhandenen *Aliasdomain* auf eine andere *Zieldomain*
+ geändert werden soll, ist dieser Unterbefehl zu verwenden.
+
+ Beispiel::
+
+ vmm aliasdomainswitch example.name example.org
+
+.. _aliasdomaindelete:
+
+``aliasdomaindelete (add) Aliasdomain``
+ Wenn die Alias-Domain mit dem Namen *Aliasdomain* gelöscht werden soll,
+ ist dieser Unterbefehl zu verwenden.
+
+ Beispiel::
+
+ vmm aliasdomaindelete e.g.example.com
+
+
+KONTO UNTERBEFEHLE
+------------------
+.. _useradd:
+
+``useradd (ua) Adresse [ Passwort ]``
+ Mit diesem Unterbefehl wird ein neues Konto für die angegebene *Adresse*
+ angelegt.
+
+ Wurde kein *Passwort* angegeben wird **vmm** dieses im interaktiven Modus
+ erfragen.
+
+ Beispiele::
+
+ vmm ua d.user@example.com 'A 5ecR3t P4s5\\/\\/0rd'
+ vmm ua e.user@example.com
+ Neues Passwort eingeben:
+ Neues Passwort wiederholen:
+
+.. _userinfo:
+
+``userinfo (ui) Adresse [ Details ]``
+ Dieser Unterbefehl zeigt einige Informationen über das Konto mit der
+ angegebenen *Adresse* an.
+
+ Wurde das optionale Argument *Details* angegeben, werden weitere
+ Informationen ausgegeben. Mögliche Werte für *Details* sind:
+
+ ``aliases``
+ um alle Alias-Adressen, mit dem Ziel *Adresse*, aufzulisten
+ ``du``
+ um zusätzlich die Festplattenbelegung des Maildirs eines Kontos
+ anzuzeigen. Soll die Festplattenbelegung jedes Mal mit der **userinfo**
+ ermittelt werden, ist in der *vmm.cfg* der Wert der Option
+ |account.disk_usage|_ auf true zu setzen.
+ ``full``
+ um alle oben genannten Informationen anzuzeigen
+
+.. _username:
+
+``username (un) Adresse 'Bürgerlicher Name'``
+ Der Bürgerliche Name des Konto-Inhabers mit der angegebenen *Adresse* kann
+ mit diesem Unterbefehl gesetzt/aktualisiert werden.
+
+ Beispiel::
+
+ vmm un d.user@example.com 'John Doe'
+
+.. _userpassword:
+
+``userpassword (up) Adresse [ Passwort ]``
+ Das *Passwort* eines Kontos kann mit diesem Unterbefehl aktualisiert
+ werden.
+
+ Wurde kein *Passwort* angegeben wird **vmm** dieses im interaktiven Modus
+ erfragen.
+
+ Beispiel::
+
+ vmm up d.user@example.com 'A |\\/|0r3 5ecur3 P4s5\\/\\/0rd?'
+
+.. _usertransport:
+
+``usertransport (ut) Adresse Transport``
+ Mit diesem Unterbefehl kann ein abweichender *Transport* für das Konto mit
+ der angegebenen *Adresse* bestimmt werden.
+
+ Beispiel::
+
+ vmm ut d.user@example.com smtp:pc105.it.example.com
+
+.. _userdisable:
+
+``userdisable (u0) Adresse [ Service ]``
+ Soll ein Anwender keinen Zugriff auf einen oder alle Service haben, kann
+ der Zugriff mit diesem Unterbefehl beschränkt werden.
+
+ Wurde weder ein *Service* noch das Schlüsselwort **all** angegeben, werden
+ alle Services (**smtp**, **pop3**, **imap**, und **sieve**) für das Konto
+ mit der angegebenen *Adresse* deaktiviert.
+
+ Andernfalls wird nur der Zugriff auf den angegeben *Service* gesperrt.
+
+ Beispiele::
+
+ vmm u0 b.user@example.com imap
+ vmm userdisable c.user@example.com
+
+.. _userenable:
+
+``userenable (u1) Adresse [ Service ]``
+ Um den Zugriff auf bestimmte oder alle gesperrten Service zu gewähren,
+ wird dieser Unterbefehl verwendet.
+
+ Wurde weder ein *Service* noch das Schlüsselwort **all** angegeben, werden
+ alle Services (**smtp**, **pop3**, **imap**, und **sieve**) für das Konto
+ mit der angegebenen *Adresse* aktiviert.
+
+ Andernfalls wird nur der Zugriff auf den angegeben *Service* gestattet.
+
+.. _userdelete:
+
+``userdelete (ud) Adresse [ delalias ]``
+ Verwenden Sie diesen Unterbefehl um, das Konto mit der angegebenen
+ *Adresse* zu löschen.
+
+ Sollte es einen oder mehrere Aliase geben, deren Zieladresse mit der
+ *Adresse* des zu löschenden Kontos identisch ist, wird **vmm** die
+ Ausführung des Befehls mit einer entsprechenden Fehlermeldung beenden. Um
+ dieses zu umgehen, kann das optionale Schlüsselwort **delalias**
+ angegebenen werden.
+
+
+ALIAS UNTERBEFEHLE
+------------------
+.. _aliasadd:
+
+``aliasadd (aa) Alias Ziel``
+ Mit diesem Unterbefehl werden neue Aliase erstellt.
+
+ Beispiele::
+
+ vmm aliasadd john.doe@example.com d.user@example.com
+ vmm aa support@example.com d.user@example.com
+ vmm aa support@example.com e.user@example.com
+
+.. _aliasinfo:
+
+``aliasinfo (ai) Alias``
+ Informationen zu einem Alias können mit diesem Unterbefehl ausgegeben
+ werden.
+
+ Beispiel::
+
+ vmm aliasinfo support@example.com
+ Alias Informationen
+ -------------------
+ E-Mails für support@example.com werden weitergeleitet an:
+ * d.user@example.com
+ * e.user@example.com
+
+.. _aliasdelete:
+
+``aliasdelete (ad) Alias [ Ziel ]``
+ Verwenden Sie diesen Unterbefehl um den angegebenen *Alias* zu löschen.
+
+ Wurde die optionale Zieladresse *Ziel* angegeben, so wird nur diese
+ Zieladresse vom angegebenen *Alias* entfernt.
+
+ Beispiel::
+
+ vmm ad support@example.com d.user@example.com
+
+
+RELOCATED UNTERBEFEHLE
+----------------------
+.. _relocatedadd:
+
+``relocatedadd (ra) alte_adresse neue_adresse``
+ Um einen neuen relocated User anzulegen kann dieser Unterbefehl verwendet
+ werden.
+
+ Dabei ist *alte_adresse* die ehemalige Adresse des Benutzers, zum Beispiel
+ b.user@example.com, und *neue_adresse* die neue Adresse, unter der
+ Benutzer erreichbar ist.
+
+ Beispiel::
+
+ vmm relocatedadd b.user@example.com b-user@company.tld
+
+.. _relocatedinfo:
+
+``relocatedinfo (ri) alte_adresse``
+ Dieser Unterbefehl zeigt die neue Adresse des relocated Users mit
+ *alte_adresse*.
+
+ Beispiel::
+
+ vmm relocatedinfo b.user@example.com
+ Relocated Informationen
+ -----------------------
+ Der Benutzer „b.user@example.com“ ist erreichbar unter „b-user@company.tld“
+
+.. _relocateddelete:
+
+``relocateddelete (rd) alte_adresse``
+ Mit diesem Unterbefehl kann der relocated User mit *alte_adresse*
+ gelöscht werden.
+
+ Beispiel::
+
+ vmm relocateddelete b.user@example.com
+
+
+DATEIEN
+=======
+*/root/vmm.cfg*
+ | Wird verwendet, falls vorhanden.
+*/usr/local/etc/vmm.cfg*
+ | Wird verwendet, sollte obige Datei nicht gefunden werden.
+*/etc/vmm.cfg*
+ | Wird verwendet, falls obengenannte Dateien nicht existieren.
+
+
+SIEHE AUCH
+==========
+|vmm.cfg(5)|_
+
+
+COPYING
+=======
+vmm und die dazugehörigen Manualseiten wurden von Pascal Volk geschrieben
+und sind unter den Bedingungen der BSD Lizenz lizenziert.
+
+.. include:: ../../substitute_links.rst
+.. include:: ../../substitute_links_1.rst
--- a/man/de/man5/vmm.cfg.5 Tue Apr 20 02:59:08 2010 +0000
+++ b/man/de/man5/vmm.cfg.5 Tue Apr 20 03:04:16 2010 +0000
@@ -1,273 +1,458 @@
-.TH vmm.cfg 5 "17 Aug 2009" "Pascal Volk"
+.\" Man page generated from reStructeredText.
+.
+.TH VMM.CFG 5 "2010-01-18" "vmm-0.6.0" "vmm Manual"
.SH NAME
vmm.cfg \- Konfigurationsdatei für vmm
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
.SH SYNOPSIS
+.sp
vmm.cfg
.SH BESCHREIBUNG
-\fBvmm\fR(1) liest Konfigurationsparameter aus der Datei \fIvmm.cfg\fP.
-.br
+.sp
+\fBvmm\fP(1) liest seine Konfigurationsparameter aus der Datei \fIvmm.cfg\fP.
+.sp
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
+wird mit dem, in eckigen Klammern \(aq\fB[\fP\(aq und \(aq\fB]\fP\(aq eingefassten, Namen des
+Abschnitts eingeleitet, gefolgt von \(aq\fIOption\fP = \fIWert\fP\(aq Einträgen:
+.sp
+.nf
+.ft C
+[database]
+host = 127.0.0.1
+.ft P
+.fi
+.sp
+Leerräume um das Gleichheitszeichen \(aq=\(aq und am Ende eines Wertes werden
ignoriert.
-.PP
-Leerzeilen und Zeilen, die mit einer '#' oder einem ';' anfangen, werden
+.sp
+Leerzeilen und Zeilen, die mit einer \(aq#\(aq oder einem \(aq;\(aq anfangen, werden
ignoriert.
-.PP
+.sp
Jeder Wert ist von einem der folgenden Datentypen:
-.IP \(bu
-.I Boolean
-um zu bestimmen, ob etwas eingeschaltet/aktiviert (true) oder
+.INDENT 0.0
+.IP \(bu 2
+.
+\fIBoolean\fP um zu bestimmen, 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.
-.br
-Mögliche Werte für \fBfalse\fP sind: \fB0\fP, \fBno\fP, \fBfalse\fP und
-\fBoff\fP.
-.IP \(bu
-.I Int
-eine Integer-Zahl, geschrieben ohne eine gebrochene oder dezimale Komponente.
-.br
-Beispielsweise sind \fB1\fP, \fB50\fP oder \fB321\fP Integer-Zahlen.
-.IP \(bu
-.I String
-eine Folge von Buchstaben und Zahlen.
-.br
-Zum Beispiel: '\fBWort\fP', '\fBHallo Welt\fP', oder '\fB/usr/bin/strings\fP'
+.nf
+Mögliche Werte für \fItrue\fP sind: \fB1\fP, \fByes\fP, \fBtrue\fP und \fBon\fP.
+Mögliche Werte für \fIfalse\fP sind: \fB0\fP, \fBno\fP, \fBfalse\fP und \fBoff\fP.
+.fi
+.sp
+.IP \(bu 2
+.
+\fIInt\fP eine Integer\-Zahl, geschrieben ohne eine gebrochene oder dezimale
+Komponente.
+.nf
+Beispielsweise \fB1\fP, \fB50\fP oder \fB321\fP sind Integer\-Zahlen.
+.fi
+.sp
+.IP \(bu 2
+.
+\fIString\fP eine Folge von Buchstaben und Zahlen.
+.nf
+Zum Beispiel: \(aq\fBWort\fP\(aq, \(aq\fBHallo Welt\fP\(aq oder \(aq\fB/usr/bin/strings\fP\(aq
+.fi
+.sp
+.UNINDENT
.SS SUCHREIHENFOLGE
-Standardmäßig sucht vmm die \fIvmm.cfg\fP in folgenden Verzeichnissen, in dieser
-Reihenfolge:
-.RS
-.PD 0
+.sp
+Standardmäßig sucht \fBvmm\fP(1) die \fIvmm.cfg\fP in folgenden Verzeichnissen,
+in der angegebenen Reihenfolge:
+.INDENT 0.0
+.INDENT 3.5
+.nf
+\fI/root\fP
+\fI/usr/local/etc\fP
+\fI/etc\fP
+.fi
+.sp
+.UNINDENT
+.UNINDENT
+.sp
+Die zuerst gefundene Datei wird verwendet.
+.SH ABSCHNITTE
+.sp
+Im Folgenden werden die Abschnitte der \fIvmm.cfg\fP und deren Optionen
+beschrieben.
+.SS ACCOUNT
+.sp
+Die Optionen des Abschnitts \fBaccount\fP legen Konto\-spezifische
+Einstellungen fest.
+.INDENT 0.0
.TP
-.I
-/root
-.TP
-.I
-/usr/local/etc
+.B \fCdelete_directory\fP
+\fIBoolean\fP
+.sp
+Bestimmt das Verhalten von \fBvmm\fP(1) beim Löschen eines Kontos.
+Wenn der Wert dieser Option \fItrue\fP ist, wird das Home\-Verzeichnis des
+zu löschenden Anwenders rekursiv gelöscht.
.TP
-.I
-/etc
-.PD
-.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
+.B \fCdirectory_mode\fP
+\fIInt\fP
+.sp
+Zugriffsbits des Home\-Verzeichnisses, sowie aller enthaltenen
+Verzeichnisse, in Dezimal\-Schreibweise (Basis 10).
+.nf
+Beispiel: \(aqdrwx\-\-\-\-\-\-\(aq \-> oktal 0700 \-> dezimal 448
+.fi
+.sp
.TP
-\fBname\fP (\fIString\fP)
-Name der zu verwendenden Datenbank.
+.B \fCdisk_usage\fP
+\fIBoolean\fP
+.sp
+Legt fest, ob die Festplattenbelegung des Maildirs eines Benutzers jedes
+Mal mit \fBdu\fP(1) ermittelt und mit den Konto\-Informationen ausgegeben
+werden soll.
+.sp
+Bei umfangreichen Maildirs kann das langsam sein. Falls Sie Quotas
+aktiviert haben, wird der \fBvmm\fP\-Unterbefehl \fBuserinfo\fP ebenfalls
+die aktuelle Quota\-Nutzung des Kontos mit ausgegeben. Sie können auch
+eines der optionalen Argumente \fBdu\fP oder \fBfull\fP an \fBuserinfo\fP
+übergeben, um sich die aktuelle Festplattenbelegung anzeigen zu lassen.
+.TP
+.B \fCimap\fP
+\fIBoolean\fP
+.sp
+Bestimmt, ob sich neu angelegte Benutzer per IMAP anmelden können sollen.
.TP
-\fBBeispiel\fP:
-[database]
-.br
-host = localhost
-.br
-user = vmm
-.br
-pass = T~_:L4OYyl]TU?)
-.br
-name = mailsys
-.\" -----
-.SH MAILDIR ABSCHNITT
-Im \fBmaildir\fP-Abschnitt werden die für die Maildirs erforderlichen Optionen
-festgelegt.
+.B \fCpassword_length\fP
+\fIInt\fP
+.sp
+Diese Option legt die Anzahl der Zeichen für automatisch erzeugte
+Passwörter fest. Alle Werte kleiner als 8 werden auf 8 erhöht.
+.TP
+.B \fCpop3\fP
+.sp
+Bestimmt, ob sich neu angelegte Benutzer per POP3 anmelden können sollen.
.TP
-\fBname\fP (\fIString\fP)
-Standard-Name des Maildir-Verzeichnisses im Verzeichnis des jeweiligen
-Anwenders.
+.B \fCrandom_password\fP
+\fIBoolean\fP
+.sp
+Mit dieser Option wird bestimmt , ob \fBvmm\fP(1) ein zufälliges Passwort
+generieren soll, wenn kein Passwort an den \fBuseradd\fP Unterbefehl
+übergeben wurde. Ist der Wert dieser Option \fIfalse\fP, wird \fBvmm\fP Sie
+auffordern, ein Passwort für den neun Account einzugeben.
+.sp
+Sie können die Länge für automatisch generierte Passwörter mit der
+Option \fBpassword_length\fP konfigurieren.
.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
+.B \fCsieve\fP
+\fIBoolean\fP
+.sp
+Bestimmt, ob sich neu angelegte Benutzer per ManageSieve anmelden
+können sollen.
.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.
-.TP
-\fBdelete\fP (\fIBoolean\fP)
-Bestimmt, ob das Maildir rekursiv gelöscht werden soll, wenn ein Konto gelöscht
-wird.
+.B \fCsmtp\fP
+\fIBoolean\fP
+.sp
+Bestimmt, ob sich neu angelegte Benutzer per SMTP (SMTP AUTH) anmelden
+können sollen.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[account]
+delete_directory = false
+directory_mode = 448
+disk_usage = false
+random_password = true
+password_length = 10
+smtp = true
+pop3 = true
+imap = true
+sieve = true
+.ft P
+.fi
+.SS BIN
+.sp
+Im \fBbin\fP\-Abschnitt werden Pfade zu Binaries angegeben, die von
+\fBvmm\fP(1) benötigt werden.
+.INDENT 0.0
.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.
+.B \fCdovecotpw\fP
+\fIString\fP
+.sp
+Der absolute Pfad zum dovecotpw Binary. Dieses Binary wird zur
+Hash\-Erzeugung verwendet, wenn \fBmisc.password_scheme\fP einen der
+nachfolgenden Werte hat: \(aqSMD5\(aq, \(aqSSHA\(aq, \(aqCRAM\-MD5\(aq, \(aqHMAC\-MD5\(aq,
+\(aqLANMAN\(aq, \(aqNTLM\(aq oder \(aqRPA\(aq.
+.TP
+.B \fCdu\fP
+\fIString\fP
+.sp
+Der absolute Pfad zu \fBdu\fP(1). Dieses Binary wird verwendet, wenn
+die Festplattenbelegung eines Kontos ermittelt wird.
.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.
-.TP
-\fBimap\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per IMAP einloggen kann.
+.B \fCpostconf\fP
+\fIString\fP
+.sp
+Der absolute Pfad zu Postfix\(aq \fBpostconf\fP(1). Dieses Binary wird
+verwendet, wenn \fBvmm\fP(1) diverse Postfix\-Einstellungen prüft, zum
+Beispiel das \fIvirtual_alias_expansion_limit\fP.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[bin]
+dovecotpw = /usr/sbin/dovecotpw
+du = /usr/bin/du
+postconf = /usr/sbin/postconf
+.ft P
+.fi
+.SS CONFIG
+.sp
+Beim \fBconfig\fP\-Abschnitt handelt es sich um einen internen
+Steuerungs\-Abschnitt.
+.INDENT 0.0
.TP
-\fBsieve\fP (\fIBoolean\fP)
-Legt fest, ob sich ein Anwender standardmäßig per MANAGESIEVE einloggen kann.
+.B \fCdone\fP
+\fIBoolean\fP
+.sp
+Diese Option hat den Wert \fIfalse\fP, wenn \fBvmm\fP(1) zum ersten Mal
+installiert wurde. Wenn Sie die Datei \fIvmm.cfg\fP von Hand editieren,
+weisen Sie dieser Option abschließend den Wert \fItrue\fP zu. Wird die
+Konfiguration über das Kommando \fBvmm configure\fP angepasst, wird der
+Wert dieser Option automatisch auf \fItrue\fP gesetzt.
+.sp
+Ist der Wert dieser Option \fIfalse\fP, so startet \fBvmm\fP(1) beim
+nächsten Aufruf im interaktiven Konfigurations\-Modus.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[config]
+done = true
+.ft P
+.fi
+.SS DATABASE
+.sp
+Der \fBdatabase\fP\-Abschnitt wird verwendet, um die für den Datenbankzugriff
+benötigten Optionen festzulegen.
+.INDENT 0.0
.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.
+.B \fChost\fP
+\fIString\fP
+.sp
+Der Hostname oder die IP\-Adresse des Datenbank\-Servers.
.TP
-\fBbase\fP (\fIString\fP)
-Alle Domain-Verzeichnisse werden unterhalb dieses Basis-Verzeichnisses angelegt.
+.B \fCname\fP
+\fIString\fP
+.sp
+Der Name der zu verwendenden Datenbank.
.TP
-\fBmode\fP (\fIInt\fP)
-Zugriffsbits des Domain-Verzeichnisses in Dezimal-Schreibweise (Basis 10).
-.br
-Beispiel: 'drwxrwx---' -> oktal 0770 -> dezimal 504
+.B \fCpass\fP
+\fIString\fP
+.sp
+Das Passwort des Datenbank\-Benutzers.
.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.
+.B \fCuser\fP
+\fIString\fP
+.sp
+Der Name des Datenbank\-Benutzers.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[database]
+host = localhost
+user = vmm
+pass = PY_SRJ}L/0p\-oOk
+name = mailsys
+.ft P
+.fi
+.SS DOMAIN
+.sp
+Im \fBdomain\fP\-Abschnitt werden Domain\-spezifische Informationen konfiguriert.
+.INDENT 0.0
+.TP
+.B \fCauto_postmaster\fP
+\fIBoolean\fP
+.sp
+Ist der Wert dieser Option \fItrue\fP, wird \fBvmm\fP(1) beim Anlegen einer
+Domain automatisch einen postmaster\-Account erstellen.
.TP
-\fBBeispiel\fP:
-[domdir]
-.br
-base = /srv/mail
-.br
-mode = 504
-.br
-delete = false
-.\" -----
-.SH BIN ABSCHNITT
-Der \fBbin\fP-Abschnitt wird verwendet, um Pfade zu Binaries, die von \fBvmm\fP
-benötigt werden, anzugeben.
+.B \fCdelete_directory\fP
+\fIBoolean\fP
+.sp
+Bestimmt, ob beim Löschen einer Domain das Verzeichnis einer Domain,
+inklusive aller Anwender\-Verzeichnisse, rekursiv gelöscht werden soll.
+.TP
+.B \fCdirectory_mode\fP
+\fIInt\fP
+.sp
+Zugriffsbits des Domain\-Verzeichnisses in Dezimal\-Schreibweise (Basis
+10).
+.nf
+Beispiel: \(aqdrwxrwx\-\-\-\(aq \-> oktal 0770 \-> dezimal 504
+.fi
+.sp
.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'.
-.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.
+.B \fCforce_deletion\fP
+\fIBoolean\fP
+.sp
+Erzwingt das Löschen von Konten und Aliase beim Löschen einer Domain.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[domain]
+auto_postmaster = true
+delete_directory = false
+directory_mode = 504
+force_deletion = false
+.ft P
+.fi
+.SS MAILDIR
+.sp
+Im \fBmaildir\fP\-Abschnitt werden die für die Maildirs erforderlichen Optionen
+festgelegt.
+.INDENT 0.0
.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.
+.B \fCfolders\fP
+\fIString\fP
+.sp
+Eine durch Doppelpunkten getrennte Liste mit Verzeichnisnamen, die
+innerhalb des Maildirs erstellt werden sollen. Sollen innerhalb des
+Maildirs keine Verzeichnisse angelegt werden, ist dieser Optionen ein
+einzelner Doppelpunkt (\(aq\fB:\fP\(aq) als Wert zuzuweisen.
+.sp
+Sollen Verzeichnisse mit Unterverzeichnissen angelegt werden, ist ein
+einzelner Punkt (\(aq\fB.\fP\(aq) als Separator zu verwenden.
.TP
-\fBpasswdscheme\fP (\fIString\fP)
-Das zu verwendende Passwort-Schema (siehe auch: dovecotpw -l)
+.B \fCname\fP
+\fIString\fP
+.sp
+Der Standard\-Name des Maildir\-Verzeichnisses im Verzeichnis des
+jeweiligen Anwenders.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
+[maildir]
+folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
+name = Maildir
+.ft P
+.fi
+.SS MISC
+.sp
+Im \fBmisc\fP\-Abschnitt werden verschiedene Einstellungen festgelegt.
+.INDENT 0.0
.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 \fCbase_directory\fP
+\fIString\fP
+.sp
+Alle Domain\-Verzeichnisse werden innerhalb dieses Basis\-Verzeichnisses
+angelegt.
.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.
+.B \fCpassword_scheme\fP
+\fIString\fP
+.sp
+Das zu verwendende Passwort\-Schema (siehe auch: \fBdovecotpw \-l\fP).
+.TP
+.B \fCgid_mail\fP
+\fIInt\fP
+.sp
+Die numerische Gruppen\-ID der Gruppe mail, bzw. der Gruppe aus
+\fImail_privileged_group\fP der Datei \fIdovecot.conf\fP.
.TP
-\fBtransport\fP (\fIString\fP)
-Der Standard-Transport aller Domains und Konten.
+.B \fCtransport\fP
+\fIString\fP
+.sp
+Der Standard\-Transport aller Domains und Konten. Siehe auch:
+\fBtransport\fP(5)
.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 \fCdovecot_version\fP
+\fIInt\fP
+.sp
+Die verketteten Major\- und Minor\-Teile der eingesetzten Dovecot\-Version
+(siehe: \fBdovecot \-\-version\fP).
+.sp
+Wenn das Kommando \fBdovecot \-\-version\fP zum Beispiel \fI1.1.18\fP ausgibt,
+ist dieser Option der Wert \fB11\fP zuzuweisen.
+.UNINDENT
+.sp
+Beispiel:
+.sp
+.nf
+.ft C
[misc]
-.br
-passwdscheme = CRAM-MD5
-.br
+base_directory = /srv/mail
+password_scheme = CRAM\-MD5
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.
+dovecot_version = 11
+.ft P
+.fi
+.SH DATEIEN
+.INDENT 0.0
+.TP
+.B \fI/root/vmm.cfg\fP
+.nf
+Wird verwendet, falls vorhanden.
+.fi
+.sp
.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.
+.B \fI/usr/local/etc/vmm.cfg\fP
+.nf
+Wird verwendet, sollte obige Datei nicht gefunden werden.
+.fi
+.sp
.TP
-\fBBeispiel\fP:
-[config]
-.br
-done = true
-.\" -----
-.SH DATEIEN
-vmm.cfg
+.B \fI/etc/vmm.cfg\fP
+.nf
+Wird verwendet, falls obengenannte Dateien nicht existieren.
+.fi
+.sp
+.UNINDENT
.SH SIEHE AUCH
-vmm(1), Programm für die Kommandozeile, um E-Mail-Domains, -Konten und -Aliase
+.sp
+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.
+.SH COPYING
+.sp
+vmm und die dazugehörigen Manualseiten wurden von Pascal Volk geschrieben
+und sind unter den Bedingungen der BSD Lizenz lizenziert.
+.SH AUTHOR
+Pascal Volk <neverseen@users.sourceforge.net>
+.\" Generated by docutils manpage writer.
+.\"
+.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/de/man5/vmm.cfg.5.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,404 @@
+=========
+ vmm.cfg
+=========
+
+---------------------------
+Konfigurationsdatei für vmm
+---------------------------
+
+:Author: Pascal Volk <neverseen@users.sourceforge.net>
+:Date: 2010-03-03
+:Version: vmm-0.6.0
+:Manual group: vmm Manual
+:Manual section: 5
+
+.. contents::
+ :backlinks: top
+ :class: htmlout
+
+SYNOPSIS
+========
+vmm.cfg
+
+
+BESCHREIBUNG
+============
+|vmm(1)|_ liest seine Konfigurationsparameter aus der Datei *vmm.cfg*.
+
+Die Konfigurationsdatei ist in mehrere Sektionen unterteilt. Jede Sektion
+wird mit dem in eckigen Klammern '**[**' und '**]**' eingefassten Namen der
+Sektion eingeleitet, gefolgt von '*Option* = *Wert*' Einträgen.
+
+Leerräume um das Gleichheitszeichen '=' und am Ende eines Wertes werden
+ignoriert.
+
+Leerzeilen und Zeilen, die mit einer '#' oder einem ';' anfangen, werden
+ignoriert.
+
+Jeder Wert ist von einem der folgenden Datentypen:
+
+* *Boolean* um zu bestimmen, ob etwas eingeschaltet/aktiviert (true) oder
+ ausgeschaltet/deaktiviert (false) ist.
+
+ | Mögliche Werte für *true* sind: **1**, **yes**, **true** und **on**.
+ | Mögliche Werte für *false* sind: **0**, **no**, **false** und **off**.
+
+* *Int* eine Integer-Zahl, geschrieben ohne eine gebrochene oder dezimale
+ Komponente.
+
+ | Beispielsweise **1**, **50** oder **321** sind Integer-Zahlen.
+
+* *String* eine Folge von Buchstaben und Zahlen.
+
+ | Zum Beispiel: '**Wort**', '**Hallo Welt**' oder '**/usr/bin/strings**'
+
+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 **#** oder **;**
+auskommentiert oder die Zeile wird einfach aus der *vmm.cfg* entfernt.
+
+Eine minimale *vmm.cfg* könnte so aussehen::
+
+ [database]
+ user = ich
+ pass = xxxxxxxx
+
+
+SUCHREIHENFOLGE
+---------------
+Standardmäßig sucht |vmm(1)|_ die *vmm.cfg* in folgenden Verzeichnissen,
+in der angegebenen Reihenfolge:
+
+ | */root*
+ | */usr/local/etc*
+ | */etc*
+
+Die zuerst gefundene Datei wird verwendet.
+
+
+SEKTIONEN
+=========
+Im Folgenden werden die Sektionen der *vmm.cfg* und deren Optionen
+beschrieben.
+
+
+ACCOUNT
+-------
+Die Optionen der Sektion **account** legen Konto-spezifische Einstellungen
+fest.
+
+.. _account.delete_directory:
+
+``delete_directory (Vorgabe: false)`` : *Boolean*
+ Bestimmt das Verhalten von |vmm(1)|_ beim Löschen eines Kontos
+ (|userdelete|_). Wenn der Wert dieser Option *true* ist, wird das
+ Home-Verzeichnis des zu löschenden Anwenders rekursiv gelöscht.
+
+.. _account.directory_mode:
+
+``directory_mode (Vorgabe: 448)`` : *Int*
+ Zugriffsbits des Home-Verzeichnisses, sowie aller enthaltenen
+ Verzeichnisse, in Dezimal-Schreibweise (Basis 10).
+
+ | Beispiel: 'drwx------' -> oktal 0700 -> dezimal 448
+
+.. _account.disk_usage:
+
+``disk_usage (Vorgabe: false)`` : *Boolean*
+ Legt fest, ob die Festplattenbelegung des Maildirs eines Benutzers jedes
+ Mal mit **du**\(1) ermittelt und mit den Konto-Informationen ausgegeben
+ werden soll.
+
+ Bei umfangreichen Maildirs kann das langsam sein. Falls Sie Quotas
+ aktiviert haben, wird der **vmm**-Unterbefehl |userinfo|_ ebenfalls die
+ aktuelle Quota-Nutzung des Kontos mit ausgegeben. Sie können auch eines
+ der optionalen Argumente **du** oder **full** an |userinfo|_ übergeben,
+ um sich die aktuelle Festplattenbelegung anzeigen zu lassen.
+
+.. _account.imap:
+
+``imap (Vorgabe: true)`` : *Boolean*
+ Bestimmt, ob sich neu angelegte Benutzer per IMAP anmelden können sollen.
+
+.. _account.password_length:
+
+``password_length (Vorgabe: 8)`` : *Int*
+ Diese Option legt die Anzahl der Zeichen für automatisch erzeugte
+ Passwörter fest. Alle Werte kleiner als 8 werden auf 8 erhöht.
+
+.. _account.pop3:
+
+``pop3 (Vorgabe: true)`` : *Boolean*
+ Bestimmt, ob sich neu angelegte Benutzer per POP3 anmelden können sollen.
+
+.. _account.random_password:
+
+``random_password (Vorgabe: false)`` : *Boolean*
+ Mit dieser Option wird bestimmt , ob **vmm** ein zufälliges Passwort
+ generieren soll, wenn kein Passwort an den Unterbefehl |useradd|_
+ übergeben wurde. Ist der Wert dieser Option *false*, wird **vmm** Sie
+ auffordern, ein Passwort für den neun Account einzugeben.
+
+ Sie können die Länge für automatisch generierte Passwörter mit der Option
+ |account.password_length|_ konfigurieren.
+
+.. _account.sieve:
+
+``sieve (Vorgabe: true)`` : *Boolean*
+ Bestimmt, ob sich neu angelegte Benutzer per ManageSieve anmelden können
+ sollen.
+
+.. _account.smtp:
+
+``smtp (Vorgabe: true)`` : *Boolean*
+ Bestimmt, ob sich neu angelegte Benutzer per SMTP (SMTP AUTH) anmelden
+ können sollen.
+
+Beispiel::
+
+ [account]
+ delete_directory = false
+ directory_mode = 448
+ disk_usage = false
+ random_password = true
+ password_length = 10
+ smtp = true
+ pop3 = true
+ imap = true
+ sieve = true
+
+
+BIN
+---
+In der **bin**-Sektion werden die Pfade zu den von |vmm(1)|_ benötigten
+Binaries angegeben.
+
+.. _bin.dovecotpw:
+
+``dovecotpw (Vorgabe: /usr/sbin/dovecotpw)`` : *String*
+ Der absolute Pfad zum dovecotpw Binary. Dieses Binary wird zur
+ Hash-Erzeugung verwendet, wenn |misc.password_scheme|_ einen der
+ nachfolgenden Werte hat: 'SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5', 'LANMAN',
+ 'NTLM' oder 'RPA'.
+
+.. _bin.du:
+
+``du (Vorgabe: /usr/bin/du)`` : *String*
+ Der absolute Pfad zu **du**\(1). Dieses Binary wird verwendet, wenn die
+ Festplattenbelegung eines Kontos ermittelt wird.
+
+.. _bin.postconf:
+
+``postconf (Vorgabe: /usr/sbin/postconf)`` : *String*
+ Der absolute Pfad zu Postfix' |postconf(1)|_. Dieses Binary wird
+ verwendet, wenn |vmm(1)|_ diverse Postfix-Einstellungen prüft, zum
+ Beispiel das |virtual_alias_expansion_limit|_.
+
+Beispiel::
+
+ [bin]
+ dovecotpw = /usr/sbin/dovecotpw
+ du = /usr/bin/du
+ postconf = /usr/sbin/postconf
+
+
+DATABASE
+--------
+Die **database**-Sektion wird verwendet, um die für den Datenbankzugriff
+benötigten Optionen festzulegen.
+
+.. _database.host:
+
+``host (Vorgabe: localhost)`` : *String*
+ Der Hostname oder die IP-Adresse des Datenbank-Servers.
+
+.. _database.name:
+
+``name (Vorgabe: mailsys)`` : *String*
+ Der Name der zu verwendenden Datenbank.
+
+.. _database.pass:
+
+``pass (Vorgabe: Nichts)`` : *String*
+ Das Passwort des Datenbank-Benutzers.
+
+.. _database.user:
+
+``user (Vorgabe: Nichts)`` : *String*
+ Der Name des Datenbank-Benutzers.
+
+Beispiel::
+
+ [database]
+ host = localhost
+ user = vmm
+ pass = PY_SRJ}L/0p-oOk
+ name = mailsys
+
+
+DOMAIN
+------
+In der **domain**-Sektion werden Domain-spezifische Informationen
+konfiguriert.
+
+.. _domain.auto_postmaster:
+
+``auto_postmaster (Vorgabe: true)`` : *Boolean*
+ Ist der Wert dieser Option *true*, wird |vmm(1)|_ beim Anlegen einer
+ Domain (|domainadd|_) automatisch einen postmaster-Account erstellen.
+
+.. _domain.delete_directory:
+
+``delete_directory (Vorgabe: false)`` : *Boolean*
+ Bestimmt, ob beim Löschen einer Domain (|domaindelete|_) das Verzeichnis
+ der zu löschenden Domain, inklusive aller Anwender-Verzeichnisse, rekursiv
+ gelöscht werden soll.
+
+.. _domain.directory_mode:
+
+``directory_mode (Vorgabe: 504)`` : *Int*
+ Zugriffsbits des Domain-Verzeichnisses in Dezimal-Schreibweise (Basis 10).
+
+ | Beispiel: 'drwxrwx---' -> oktal 0770 -> dezimal 504
+
+.. _domain.force_deletion:
+
+``force_deletion (Vorgabe: false)`` : *Boolean*
+ Erzwingt das Löschen aller zugeordneten Konten und Aliase beim Löschen
+ einer Domain (|domaindelete|_).
+
+Beispiel::
+
+ [domain]
+ auto_postmaster = true
+ delete_directory = false
+ directory_mode = 504
+ force_deletion = false
+
+
+MAILBOX
+-------
+In der **mailbox**-Sektion werden die für die Erstellung von Mailboxen
+erforderlichen Optionen festgelegt. Die INBOX wird in jedem Fall erstellt.
+
+.. _mailbox.folders:
+
+``folders (Vorgabe: Drafts:Sent:Templates:Trash)`` : *String*
+ Eine durch Doppelpunkten getrennte Liste mit Mailboxnamen die
+ erstellt werden sollen. (Wird derzeit nur berücksichtigt, wenn
+ |mailbox.format|_ entweder **maildir** oder **mbox** ist. Sollte das
+ gewählte Format ein anderes sein, kann Dovecots autocreate Plugin
+ <http://wiki.dovecot.org/Plugins/Autocreate> verwendet werden.) Sollen
+ keine zusätzlichen Mailboxen angelegt werden, ist dieser Optionen ein
+ einzelner Doppelpunkt ('**:**') als Wert zuzuweisen.
+
+ Sollen Verzeichnisse mit Unterverzeichnissen angelegt werden, ist ein
+ einzelner Punkt ('**.**') als Separator zu verwenden.
+
+.. _mailbox.format:
+
+``format (Vorgabe: maildir)`` : *String*
+ Das zu verwendende Format der Mailbox der Benutzer. Abhängig von der
+ verwendeten Dovecot-Version, stehen bis zu vier Formate zur Verfügung:
+
+ ``maildir``
+ seit Dovecot v1.0.0
+ ``mbox``
+ seit Dovecot v1.0.0
+ ``dbox``
+ seit Dovecot v1.2.0
+ ``mdbox``
+ seit Dovecot v2.0.0
+
+Beispiel::
+
+ [mailbox]
+ folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
+ format = maildir
+
+.. _imap_uft7:
+
+.. note:: Sollen in der **folders**-Einstellung internationalisierte Namen
+ für Maildir-Verzeichnisse verwendet werden, sind diese in einer
+ modifizierten Variante des UTF-7-Zeichensatzes (siehe :RFC:`3501`, Sektion
+ 5.1.3) anzugeben.
+
+ Dovecot stellt seit Version 1.2.0 das nützlich Hilfsprogramm **imap-utf7**
+ zur Verfügung. Dieses dient zur mUTF-7 <-> UTF-8 Konvertierung.
+..
+
+imap-utf7 Beispiel::
+
+ user@host:~$ /usr/local/libexec/dovecot/imap-utf7 -r Wysłane
+ Wys&AUI-ane
+ user@host:~$ /usr/local/libexec/dovecot/imap-utf7 "&AVo-mietnik"
+ Śmietnik
+
+
+MISC
+----
+In der **misc**-Sektion werden verschiedene Einstellungen festgelegt.
+
+.. _misc.base_directory:
+
+``base_directory (Vorgabe: /srv/mail)`` : *String*
+ Alle Domain-Verzeichnisse werden innerhalb dieses Basis-Verzeichnisses
+ angelegt.
+
+.. _misc.password_scheme:
+
+``password_scheme (Vorgabe: CRAM-MD5)`` : *String*
+ Das zu verwendende Passwort-Schema (siehe auch: **dovecotpw -l**).
+
+.. _misc.gid_mail:
+
+``gid_mail (Vorgabe: 8)`` : *Int*
+ Die numerische Gruppen-ID der Gruppe mail, bzw. der Gruppe aus
+ `mail_privileged_group` der Datei *dovecot.conf*.
+
+.. _misc.transport:
+
+``transport (Vorgabe: dovecot:)`` : *String*
+ Der Standard-Transport aller Domains und Konten. Siehe auch:
+ |transport(5)|_
+
+.. _misc.dovecot_version:
+
+``dovecot_version (Vorgabe: 12)`` : *Int*
+ Die verketteten Major- und Minor-Teile der eingesetzten Dovecot-Version
+ (siehe: **dovecot --version**).
+
+ Wenn das Kommando **dovecot --version** zum Beispiel *1.1.18* ausgibt, ist
+ dieser Option der Wert **11** zuzuweisen.
+
+Beispiel::
+
+ [misc]
+ base_directory = /srv/mail
+ password_scheme = PLAIN
+ gid_mail = 8
+ transport = dovecot:
+ dovecot_version = 11
+
+
+DATEIEN
+=======
+*/root/vmm.cfg*
+ | Wird verwendet, falls vorhanden.
+*/usr/local/etc/vmm.cfg*
+ | Wird verwendet, sollte obige Datei nicht gefunden werden.
+*/etc/vmm.cfg*
+ | Wird verwendet, falls obengenannte Dateien nicht existieren.
+
+SIEHE AUCH
+==========
+|vmm(1)|_
+
+
+COPYING
+=======
+vmm und die dazugehörigen Manualseiten wurden von Pascal Volk geschrieben
+und sind unter den Bedingungen der BSD Lizenz lizenziert.
+
+.. include:: ../../substitute_links.rst
+.. include:: ../../substitute_links_5.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/man1/vmm.1.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,445 @@
+=====
+ vmm
+=====
+
+----------------------------------------------------------
+command line tool to manage email domains/accounts/aliases
+----------------------------------------------------------
+
+:Author: Pascal Volk <neverseen@users.sourceforge.net>
+:Date: 2010-01-30
+:Version: vmm-0.6.0
+:Manual group: vmm Manual
+:Manual section: 1
+
+.. contents::
+ :backlinks: top
+ :class: htmlout
+
+SYNOPSIS
+========
+**vmm** *subcommand* *object* [ *arguments* ]
+
+
+DESCRIPTION
+===========
+**vmm** (a virtual mail manager) is a command line tool for
+administrators/postmasters to manage (alias) domains, accounts and alias
+addresses. It's designed for Dovecot and Postfix with a PostgreSQL backend.
+
+
+SUBCOMMANDS
+===========
+Each subcommand has both a long and a short form. The short form is shown
+enclosed in parentheses. Both forms are case sensitive.
+
+
+GENERAL SUBCOMMANDS
+-------------------
+.. _configure:
+
+``configure (cf) [ section ]``
+ Starts the interactive configuration for all configuration sections.
+
+ In this process the currently set value of each option will be shown in
+ square brackets. If no value is configured, the default value of each
+ option will be displayed in square brackets. Pres the enter key, to accept
+ the displayed value.
+
+ If the optional argument *section* is given, only the configuration
+ options from the given section will be displayed and will be
+ configurable. The following sections are available:
+
+ | - **account**
+ | - **bin**
+ | - **database**
+ | - **domain**
+ | - **maildir**
+ | - **misc**
+
+ Example::
+
+ vmm configure domain
+ Using configuration file: /usr/local/etc/vmm.cfg
+
+ * Configuration section: “domain”
+ Enter new value for option directory_mode [504]:
+ Enter new value for option delete_directory [True]: no
+ Enter new value for option auto_postmaster [True]:
+ Enter new value for option force_deletion [True]: off
+
+.. _getuser:
+
+``getuser (gu) userid``
+ If only the *userid* is available, for example from process list, the
+ subcommand **getuser** will show the user's address.
+
+ Example::
+
+ vmm getuser 70004
+ Account information
+ -------------------
+ UID............: 70004
+ GID............: 70000
+ Address........: c.user@example.com
+
+.. _listdomains:
+
+``listdomains (ld) [ pattern ]``
+ This subcommand lists all available domains. All domain names will be
+ prefixed either with '[+]', if the domain is a primary domain, or with
+ '[-]', if it is an alias domain name. The output can be limited with an
+ optional *pattern*.
+
+ To perform a wild card search, the **%** character can be used at the
+ start and/or the end of the *pattern*.
+
+ Example::
+
+ vmm listdomains %example%
+ Matching domains
+ ----------------
+ [+] example.com
+ [-] e.g.example.com
+ [-] example.name
+ [+] example.net
+ [+] example.org
+
+.. _help:
+
+``help (h)``
+ Prints all available subcommands to stdout. After this **vmm** exits.
+
+.. _version:
+
+``version (v)``
+ Prints the version information from **vmm**.
+
+
+DOMAIN SUBCOMMANDS
+------------------
+.. _domainadd:
+
+``domainadd (da) domain [ transport ]``
+ Adds the new *domain* into the database and creates the domain directory.
+
+ If the optional argument *transport* is given, it will overwrite the
+ default transport (|misc.transport|_) from |vmm.cfg(5)|_. The specified
+ *transport* will be the default transport for all new accounts in this
+ domain.
+
+ Examples::
+
+ vmm domainadd support.example.com smtp:mx1.example.com
+ vmm domainadd sales.example.com
+
+.. _domaininfo:
+
+``domaininfo (di) domain [ details ]``
+ This subcommand shows some information about the given *domain*.
+
+ For a more detailed information about the *domain* the optional argument
+ *details* can be specified. A possible *details* value may be one of the
+ following five keywords:
+
+ ``accounts``
+ to list all existing accounts
+ ``aliasdomains``
+ to list all assigned alias domains
+ ``aliases``
+ to list all available aliases addresses
+ ``relocated``
+ to list all relocated users
+ ``full``
+ to list all information mentioned above
+
+ Example::
+
+ vmm domaininfo sales.example.com
+ Domain information
+ ------------------
+ Domainname.....: sales.example.com
+ GID............: 70002
+ Transport......: dovecot:
+ Domaindir......: /home/mail/5/70002
+ Aliasdomains...: 0
+ Accounts.......: 0
+ Aliases........: 0
+ Relocated......: 0
+
+.. _domaintransport:
+
+``domaintransport (dt) domain transport [ force ]``
+ A new *transport* for the indicated *domain* can be set with this
+ subcommand.
+
+ If the additional keyword **force** is given all account specific
+ transport settings will be overwritten. Otherwise this setting will affect
+ only new created accounts.
+
+ Example::
+
+ vmm domaintransport support.example.com dovecot:
+
+.. _domaindelete:
+
+``domaindelete (dd) domain [ delalias | deluser | delall ]``
+ This subcommand deletes the specified *domain*.
+
+ If there are accounts and/or aliases assigned to the given domain, **vmm**
+ will abort the requested operation and show an error message. If you know,
+ what you are doing, you can specify one of the following keywords:
+ **delalias**, **deluser** or **delall**.
+
+ If you really always know what you are doing, edit your *vmm.cfg* and set
+ the option |domain.force_deletion|_ to true.
+
+
+ALIAS DOMAIN SUBCOMMANDS
+------------------------
+.. _aliasdomainadd:
+
+``aliasdomainadd (ada) aliasdomain targetdomain``
+ This subcommand adds the new *aliasdomain* to the *targetdomain* that
+ should be aliased.
+
+ Example::
+
+ vmm aliasdomainadd example.name example.com
+
+.. _aliasdomaininfo:
+
+``aliasdomaininfo (adi) aliasdomain``
+ This subcommand shows to which domain the *aliasdomain* is assigned to.
+
+ Example::
+
+ vmm aliasdomaininfo example.name
+ Alias domain information
+ ------------------------
+ The alias domain example.name belongs to:
+ * example.com
+
+.. _aliasdomainswitch:
+
+``aliasdomainswitch (ads) aliasdomain targetdomain``
+ If the target of the existing *aliasdomain* should be switched to another
+ *targetdomain* use this subcommand.
+
+ Example::
+
+ vmm aliasdomainswitch example.name example.org
+
+.. _aliasdomaindelete:
+
+``aliasdomaindelete (add) aliasdomain``
+ Use this subcommand if the alias domain *aliasdomain* should be removed.
+
+ Example::
+
+ vmm aliasdomaindelete e.g.example.com
+
+
+ACCOUNT SUBCOMMANDS
+-------------------
+.. _useradd:
+
+``useradd (ua) address [ password ]``
+ Use this subcommand to create a new email account for the given *address*.
+
+ If the *password* is not provided, **vmm** will prompt for it
+ interactively.
+
+ Examples::
+
+ vmm ua d.user@example.com 'A 5ecR3t P4s5\\/\\/0rd'
+ vmm ua e.user@example.com
+ Enter new password:
+ Retype new password:
+
+.. _userinfo:
+
+``userinfo (ui) address [ details ]``
+ This subcommand displays some information about the account specified by
+ *address*.
+
+ If the optional argument *details* is given some more information will be
+ displayed. Possible values for *details* are:
+
+ ``aliases``
+ to list all alias addresses with the destination *address*
+ ``du``
+ to display the disk usage of a user's Maildir. In order to summarize the
+ disk usage each time the this subcommand is executed automatically, set
+ |account.disk_usage|_ in the *vmm.cfg* to true.
+ ``full``
+ to list all information mentioned above
+
+.. _username:
+
+``username (un) address "User's Name"``
+ The user's real name can be set/updated with this subcommand.
+
+ Example::
+
+ vmm un d.user@example.com 'John Doe'
+
+.. _userpassword:
+
+``userpassword (up) address [ password ]``
+ The *password* from an account can be updated with this subcommand.
+
+ If the *password* is not provided, **vmm** will prompt for it
+ interactively.
+
+ Example::
+
+ vmm up d.user@example.com 'A |\\/|0r3 5ecur3 P4s5\\/\\/0rd?'
+
+.. _usertransport:
+
+``usertransport (ut) address transport``
+ A different *transport* for an account can be specified with this
+ subcommand.
+
+ Example::
+
+ vmm ut d.user@example.com smtp:pc105.it.example.com
+
+.. _userdisable:
+
+``userdisable (u0) address [ service ]``
+ If a user shouldn't have access to one or all services you can restrict
+ the access with this subcommand.
+
+ If neither a *service* nor the keyword **all** is given all services
+ (**smtp**, **pop3**, **imap**, and **sieve**) will be disabled for the
+ account with the specified *address*. Otherwise only the specified
+ *service* will be restricted.
+
+ Examples::
+
+ vmm u0 b.user@example.com imap
+ vmm userdisable c.user@example.com
+
+.. _userenable:
+
+``userenable (u1) address [ service ]``
+ To allow access to one or all restricted services use this subcommand.
+
+ If neither a *service* nor the keyword **all** is given all services
+ (**smtp**, **pop3**, **imap**, and **sieve**) will be enabled for the
+ account with the specified *address*. Otherwise only the specified
+ *service* will be enabled.
+
+.. _userdelete:
+
+``userdelete (ud) address [ delalias ]``
+ Use this subcommand to delete the account with the given *address*.
+
+ If there are one or more aliases with an identical destination *address*,
+ **vmm** will abort the requested operation and show an error message. To
+ prevent this, specify the optional keyword **delalias**.
+
+
+ALIAS SUBCOMMANDS
+-----------------
+.. _aliasadd:
+
+``aliasadd (aa) alias target``
+ This subcommand is used to create a new alias.
+
+ Examples::
+
+ vmm aliasadd john.doe@example.com d.user@example.com
+ vmm aa support@example.com d.user@example.com
+ vmm aa support@example.com e.user@example.com
+
+.. _aliasinfo:
+
+``aliasinfo (ai) alias``
+ Information about an alias can be displayed with this subcommand.
+
+ Example::
+
+ vmm aliasinfo support@example.com
+ Alias information
+ -----------------
+ Mail for support@example.com will be redirected to:
+ * d.user@example.com
+ * e.user@example.com
+
+.. _aliasdelete:
+
+``aliasdelete (ad) alias [ target ]``
+ Use this subcommand to delete the *alias*.
+
+ If the optional destination address *target* is given, only this
+ destination will be removed from the *alias*.
+
+ Example::
+
+ vmm ad support@example.com d.user@example.com
+
+
+RELOCATED SUBCOMMANDS
+---------------------
+.. _relocatedadd:
+
+``relocatedadd (ra) old_address new_address``
+ A new relocated user can be created with this subcommand.
+
+ *old_address* is the users ex-email address, for example
+ b.user@example.com, and *new_address* points to the new email address
+ where the user can be reached.
+
+ Example::
+
+ vmm relocatedadd b.user@example.com b-user@company.tld
+
+.. _relocatedinfo:
+
+``relocatedinfo (ri) old_address``
+ This subcommand shows the new address of the relocated user with the
+ *old_address*.
+
+ Example::
+
+ vmm relocatedinfo b.user@example.com
+ Relocated information
+ ---------------------
+ User “b.user@example.com” has moved to “b-user@company.tld”
+
+.. _relocateddelete:
+
+``relocateddelete (rd) old_address``
+ Use this subcommand in order to delete the relocated user with the
+ *old_address*.
+
+ Example::
+
+ vmm relocateddelete b.user@example.com
+
+
+FILES
+=====
+*/root/vmm.cfg*
+ | will be used when found.
+*/usr/local/etc/vmm.cfg*
+ | will be used when the above file doesn't exist.
+*/etc/vmm.cfg*
+ | will be used when none of the both above mentioned files exists.
+
+
+SEE ALSO
+========
+|vmm.cfg(5)|_
+
+
+COPYING
+=======
+vmm and its manual pages were written by Pascal Volk and are licensed under
+the terms of the BSD License.
+
+.. include:: ../substitute_links.rst
+.. include:: ../substitute_links_1.rst
--- a/man/man5/vmm.cfg.5 Tue Apr 20 02:59:08 2010 +0000
+++ b/man/man5/vmm.cfg.5 Tue Apr 20 03:04:16 2010 +0000
@@ -1,256 +1,444 @@
-.TH vmm.cfg 5 "17 Aug 2009" "Pascal Volk"
+.\" Man page generated from reStructeredText.
+.
+.TH VMM.CFG 5 "2010-01-18" "vmm-0.6.0" "vmm Manual"
.SH NAME
vmm.cfg \- configuration file for vmm
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
.SH SYNOPSIS
+.sp
vmm.cfg
.SH DESCRIPTION
-\fBvmm\fR(1) reads configuration data from \fIvmm.cfg\fP.
-.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.
-.PP
+.sp
+\fBvmm\fP(1) reads its configuration data from \fIvmm.cfg\fP.
+.sp
+The configuration file is split into multiple sections. A section starts with
+the section name, enclosed in square brackets \(aq\fB[\fP\(aq and \(aq\fB]\fP\(aq, followed
+by \(aq\fIoption\fP = \fIvalue\fP\(aq pairs:
+.sp
+.nf
+.ft C
+[database]
+host = 127.0.0.1
+.ft P
+.fi
+.sp
+Whitespace around the \(aq=\(aq and at the end of a value is ignored.
+.sp
+Empty lines and lines starting with \(aq#\(aq or \(aq;\(aq will be ignored.
+.sp
Each value uses one of the following data types:
-.IP \(bu
-.I Boolean
-to indicate if something is enabled/activated (true) or disabled/deactivated
-(false).
-.br
-Accepted values for \fBtrue\fP are: \fB1\fP, \fByes\fP, \fBtrue\fP and \fBon\fP.
-.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'
+.INDENT 0.0
+.IP \(bu 2
+.
+\fIBoolean\fP to indicate if something is enabled/activated (true) or
+disabled/deactivated (false).
+.nf
+Accepted values for \fItrue\fP are: \fB1\fP, \fByes\fP, \fBtrue\fP and \fBon\fP.
+Accepted values for \fIfalse\fP are: \fB0\fP, \fBno\fP, \fBfalse\fP and \fBoff\fP.
+.fi
+.sp
+.IP \(bu 2
+.
+\fIInt\fP an integer number, written without a fractional or decimal component.
+.nf
+For example \fB1\fP, \fB50\fP or \fB321\fP are integers.
+.fi
+.sp
+.IP \(bu 2
+.
+\fIString\fP a sequence of characters and numbers.
+.nf
+For example \(aq\fBword\fP\(aq, \(aq\fBhello world\fP\(aq or \(aq\fB/usr/bin/strings\fP\(aq
+.fi
+.sp
+.UNINDENT
.SS SEARCH ORDER
-By default vmm looks for \fIvmm.cfg\fP in the following directories in the
+.sp
+By default \fBvmm\fP(1) looks for \fIvmm.cfg\fP in the following directories in the
order listed:
-.RS
-.PD 0
+.INDENT 0.0
+.INDENT 3.5
+.nf
+\fI/root\fP
+\fI/usr/local/etc\fP
+\fI/etc\fP
+.fi
+.sp
+.UNINDENT
+.UNINDENT
+.sp
+The first configuration file found will be used.
+.SH SECTIONS
+.sp
+This section describes all sections and their options of the \fIvmm.cfg\fP.
+.SS ACCOUNT
+.sp
+The options in the section \fBaccount\fP are used to specify user account
+related settings.
+.INDENT 0.0
+.TP
+.B \fCdelete_directory\fP
+\fIBoolean\fP
+.sp
+Determines the behavior of \fBvmm\fP(1) when an account is deleted. If
+this option is set to \fItrue\fP the user\(aqs home directory will be deleted
+recursively.
+.TP
+.B \fCdirectory_mode\fP
+\fIInt\fP
+.sp
+Access mode for a user\(aqs home directory and all directories inside.
+The value has to be specified in decimal (base 10) notation.
+.nf
+For example: \(aqdrwx\-\-\-\-\-\-\(aq \-> octal 0700 \-> decimal 448
+.fi
+.sp
.TP
-.I
-/root
+.B \fCdisk_usage\fP
+\fIBoolean\fP
+.sp
+Determines whether the disk usage of a user\(aqs Maildir always should be
+summarized, using \fBdu\fP(1), and displayed with account information.
+.sp
+This could be slow on large Maildirs. When you have enabled quotas,
+\fBvmm\fP\(aqs \fBuserinfo\fP subcomammand will also display the current quota
+usage of the account. You may also use \fBuserinfo\fP\(aqs optional argument
+\fBdu\fP or \fBfull\fP, in order to display the current disk usage of an
+account.
+.TP
+.B \fCimap\fP
+\fIBoolean\fP
+.sp
+Determines whether a newly created user can log in via IMAP.
+.TP
+.B \fCpassword_length\fP
+\fIInt\fP
+.sp
+Determines how many characters and/or numbers should be used for random
+generated passwords. Any value less than 8 will be increased to 8.
+.TP
+.B \fCpop3\fP
+\fIBoolean\fP
+.sp
+Determines whether a newly created user can log in via POP3.
+.TP
+.B \fCrandom_password\fP
+\fIBoolean\fP
+.sp
+Determines whether \fBvmm\fP should generate a random password when no
+password was given for the \fBuseradd\fP subcommand. If this option is
+set to \fIfalse\fP \fBvmm\fP will prompt you to enter a password for the new
+account.
+.sp
+You can specify the password length of generated passwords with the
+\fBpassword_length\fP option.
+.TP
+.B \fCsieve\fP
+\fIBoolean\fP
+.sp
+Determines whether a newly created user can log in via ManageSieve.
.TP
-.I
-/usr/local/etc
+.B \fCsmtp\fP
+\fIBoolean\fP
+.sp
+Determines whether a newly created user can log in via SMTP (SMTP AUTH).
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
+[account]
+delete_directory = false
+directory_mode = 448
+disk_usage = false
+random_password = true
+password_length = 10
+smtp = true
+pop3 = true
+imap = true
+sieve = true
+.ft P
+.fi
+.SS BIN
+.sp
+The \fBbin\fP section is used to specify some paths to some binaries required
+by \fBvmm\fP(1).
+.INDENT 0.0
+.TP
+.B \fCdovecotpw\fP
+\fIString\fP
+.sp
+The absolute path to the dovecotpw binary. This binary is used to
+generate a password hash, if \fBmisc.password_scheme\fP is set to one of
+\(aqSMD5\(aq, \(aqSSHA\(aq, \(aqCRAM\-MD5\(aq, \(aqHMAC\-MD5\(aq, \(aqLANMAN\(aq, \(aqNTLM\(aq or \(aqRPA\(aq.
+.TP
+.B \fCdu\fP
+\fIString\fP
+.sp
+The absolute path to \fBdu\fP(1). This binary is used to summarize the
+disk usage of a user\(aqs Maildir.
.TP
-.I
-/etc
-.PD
-.RE
-.PP
-The first match it finds will be used.
-.\" -----
-.SH DATABASE SECTION
+.B \fCpostconf\fP
+\fIString\fP
+.sp
+The absolute path to Postfix\(aq \fBpostconf\fP(1). This binary is required
+when \fBvmm\fP(1) has to check for some Postfix settings, e.g.
+\fIvirtual_alias_expansion_limit\fP.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
+[bin]
+dovecotpw = /usr/sbin/dovecotpw
+du = /usr/bin/du
+postconf = /usr/sbin/postconf
+.ft P
+.fi
+.SS CONFIG
+.sp
+The \fBconfig\fP section is an internal used control section.
+.INDENT 0.0
+.TP
+.B \fCdone\fP
+\fIBoolean\fP
+.sp
+This option is set to \fIfalse\fP when \fBvmm\fP(1) 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 \fBvmm\fP(1) with the command \fBvmm
+configure\fP.
+.sp
+If this option is set to \fIfalse\fP, \fBvmm\fP(1) will start in the
+interactive configurations mode.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
+[config]
+done = true
+.ft P
+.fi
+.SS DATABASE
+.sp
The \fBdatabase\fP section is used to specify some options required to
connect to the database.
+.INDENT 0.0
.TP
-\fBhost\fP (\fIString\fP)
+.B \fChost\fP
+\fIString\fP
+.sp
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)
+.B \fCname\fP
+\fIString\fP
+.sp
Name of the database.
.TP
-\fBExample\fP:
+.B \fCpass\fP
+\fIString\fP
+.sp
+Database password.
+.TP
+.B \fCuser\fP
+\fIString\fP
+.sp
+Name of the database user.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
[database]
-.br
host = localhost
-.br
user = vmm
-.br
-pass = T~_:L4OYyl]TU?)
-.br
+pass = PY_SRJ}L/0p\-oOk
name = mailsys
-.\" -----
-.SH MAILDIR SECTION
-The \fBmaildir\fP section is used to specify some options for the Maildirs.
+.ft P
+.fi
+.SS DOMAIN
+.sp
+The \fBdomain\fP section specifies some domain related settings.
+.INDENT 0.0
.TP
-\fBname\fP (\fIString\fP)
-Default name of the maildir folder in users home directory.
+.B \fCauto_postmaster\fP
+\fIBoolean\fP
+.sp
+Determines if \fBvmm\fP(1) should create also a postmaster account when a
+new domain is created.
+.TP
+.B \fCdelete_directory\fP
+\fIBoolean\fP
+.sp
+Specifies whether the domain directory and all user directories inside
+should be deleted when a domain is deleted.
.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
+.B \fCdirectory_mode\fP
+\fIInt\fP
+.sp
+Access mode for the domain directory in decimal (base 10) notation.
+.nf
+For example: \(aqdrwxrwx\-\-\-\(aq \-> octal 0770 \-> decimal 504
+.fi
+.sp
.TP
-\fBdiskusage\fP (\fIBoolean\fP)
-Decides if the disk usage of users maildir always should be summarized and
-displayed with account information.
+.B \fCforce_deletion\fP
+\fIBoolean\fP
+.sp
+Force deletion of accounts and aliases when a domain is deleted.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
+[domain]
+auto_postmaster = true
+delete_directory = false
+directory_mode = 504
+force_deletion = false
+.ft P
+.fi
+.SS MAILDIR
+.sp
+The \fBmaildir\fP section is used to specify some default options for new
+created Maildirs and folders inside.
+.INDENT 0.0
.TP
-\fBdelete\fP (\fIBoolean\fP)
-Decides if the maildir should be deleted recursive when the account is deleted.
+.B \fCfolders\fP
+\fIString\fP
+.sp
+A colon separated list of folder names, that should be created. If no
+folders should be created inside the Maildir, set the value of this
+option to a single colon (\(aq\fB:\fP\(aq).
+.sp
+If you want to create folders containing one or more subfolders, separate
+them with a single dot (\(aq\fB.\fP\(aq).
.TP
-\fBExample\fP:
+.B \fCname\fP
+\fIString\fP
+.sp
+Default name of the Maildir folder in users home directories.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
[maildir]
-.br
+folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
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.
+.ft P
+.fi
+.SS MISC
+.sp
+The \fBmisc\fP section is used to define miscellaneous settings.
+.INDENT 0.0
.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)
+.B \fCbase_directory\fP
+\fIString\fP
+.sp
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
-.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]
-.br
-base = /srv/mail
-.br
-mode = 504
-.br
-delete = false
-.\" -----
-.SH BIN SECTION
-The \fBbin\fP section is used to specify some paths to some binaries required
-by \fBvmm\fP.
+.B \fCpassword_scheme\fP
+\fIString\fP
+.sp
+Password scheme to use (see also: \fBdovecotpw \-l\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'.
-.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.
+.B \fCgid_mail\fP
+\fIInt\fP
+.sp
+Numeric group ID of group mail (\fImail_privileged_group\fP from
+\fIdovecot.conf\fP)
.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)
+.B \fCtransport\fP
+\fIString\fP
+.sp
+Default transport for domains and accounts. For details see
+\fBtransport\fP(5).
.TP
-\fBgid_mail\fP (\fIInt\fP)
-Numeric group ID of group mail (mail_privileged_group from dovecot.conf)
-.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:
+.B \fCdovecot_version\fP
+\fIInt\fP
+.sp
+The concatenated major and minor version number of the currently used
+Dovecot version. (see: \fBdovecot \-\-version\fP).
+.sp
+When, for example, the command \fBdovecot \-\-version\fP prints \fI1.1.18\fP, set
+the value of this option to \fB11\fP.
+.UNINDENT
+.sp
+Example:
+.sp
+.nf
+.ft C
[misc]
-.br
-passwdscheme = CRAM-MD5
-.br
+base_directory = /srv/mail
+password_scheme = CRAM\-MD5
gid_mail = 8
-.br
-forcedel = false
-.br
transport = dovecot:
-.br
-dovecotvers = 11
-.\" -----
-.SH CONFIG SECTION
-The \fBconfig\fP section is a internal used control section.
+dovecot_version = 11
+.ft P
+.fi
+.SH FILES
+.INDENT 0.0
+.TP
+.B \fI/root/vmm.cfg\fP
+.nf
+will be used when found.
+.fi
+.sp
+.TP
+.B \fI/usr/local/etc/vmm.cfg\fP
+.nf
+will be used when the above file doesn\(aqt exist.
+.fi
+.sp
.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.
-.TP
-\fBExample\fP:
-[config]
-.br
-done = true
-.\" -----
-.SH FILES
-vmm.cfg
+.B \fI/etc/vmm.cfg\fP
+.nf
+will be used when none of the both above mentioned files exists.
+.fi
+.sp
+.UNINDENT
.SH SEE ALSO
+.sp
vmm(1), command line tool to manage email domains/accounts/aliases
+.SH COPYING
+.sp
+vmm and its manual pages were written by Pascal Volk and are licensed under
+the terms of the BSD License.
.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.
+Pascal Volk <neverseen@users.sourceforge.net>
+.\" Generated by docutils manpage writer.
+.\"
+.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/man5/vmm.cfg.5.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,390 @@
+=========
+ vmm.cfg
+=========
+
+--------------------------
+configuration file for vmm
+--------------------------
+
+:Author: Pascal Volk <neverseen@users.sourceforge.net>
+:Date: 2010-03-03
+:Version: vmm-0.6.0
+:Manual group: vmm Manual
+:Manual section: 5
+
+.. contents::
+ :backlinks: top
+ :class: htmlout
+
+SYNOPSIS
+========
+vmm.cfg
+
+
+DESCRIPTION
+===========
+|vmm(1)|_ reads its configuration data from *vmm.cfg*.
+
+The configuration file is split into multiple sections. A section starts with
+the section name, enclosed in square brackets '**[**' and '**]**', followed
+by '*option* = *value*' pairs.
+
+Whitespace around the '=' and at the end of a value is ignored.
+
+Empty lines and lines starting with '#' or ';' will be ignored.
+
+Each value uses one of the following data types:
+
+* *Boolean* to indicate if something is enabled/activated (true) or
+ disabled/deactivated (false).
+
+ | Accepted values for *true* are: **1**, **yes**, **true** and **on**.
+ | Accepted values for *false* are: **0**, **no**, **false** and **off**.
+
+* *Int* an integer number, written without a fractional or decimal component.
+
+ | For example **1**, **50** or **321** are integers.
+
+* *String* a sequence of characters and numbers.
+
+ | For example '**word**', '**hello world**' or '**/usr/bin/strings**'
+
+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 **#** or **;** or simply remove the setting from *vmm.cfg*.
+
+A minimal *vmm.cfg* would be::
+
+ [database]
+ user = me
+ pass = xxxxxxxx
+
+
+SEARCH ORDER
+-------------
+By default |vmm(1)|_ looks for *vmm.cfg* in the following directories in the
+order listed:
+
+ | */root*
+ | */usr/local/etc*
+ | */etc*
+
+The first configuration file found will be used.
+
+
+SECTIONS
+========
+This section describes all sections and their options of the *vmm.cfg*.
+
+
+ACCOUNT
+-------
+The options in the section **account** are used to specify user account
+related settings.
+
+.. _account.delete_directory:
+
+``delete_directory (default: false)`` : *Boolean*
+ Determines the behavior of |vmm(1)|_ when an account is deleted
+ (|userdelete|_). If this option is set to *true* the user's home directory
+ will be deleted recursively.
+
+.. _account.directory_mode:
+
+``directory_mode (default: 448)`` : *Int*
+ Access mode for a user's home directory and all directories inside. The
+ value has to be specified in decimal (base 10) notation.
+
+ | For example: 'drwx------' -> octal 0700 -> decimal 448
+
+.. _account.disk_usage:
+
+``disk_usage (default: false)`` : *Boolean*
+ Determines whether the disk usage of a user's Maildir always should be
+ summarized, using **du**\(1), and displayed with account information.
+
+ This could be slow on large Maildirs. When you have enabled quotas,
+ **vmm**'s |userinfo|_ subcomammand will also display the current quota
+ usage of the account. You may also use |userinfo|_'s optional argument
+ **du** or **full**, in order to display the current disk usage of an
+ account's Maildir.
+
+.. _account.imap:
+
+``imap (default: true)`` : *Boolean*
+ Determines whether a newly created user can log in via IMAP.
+
+.. _account.password_length:
+
+``password_length (default: 8)`` : *Int*
+ Determines how many characters and/or numbers should be used for randomly
+ generated passwords. Any value less than 8 will be increased to 8.
+
+.. _account.pop3:
+
+``pop3 (default: true)`` : *Boolean*
+ Determines whether a newly created user can log in via POP3.
+
+.. _account.random_password:
+
+``random_password (default: false)`` : *Boolean*
+ Determines whether **vmm** should generate a random password when no
+ password was given for the |useradd|_ subcommand. If this option is set to
+ *false* **vmm** will prompt you to enter a password for the new account.
+
+ You can specify the password length of generated passwords with the
+ |account.password_length|_ option.
+
+.. _account.sieve:
+
+``sieve (default: true)`` : *Boolean*
+ Determines whether a newly created user can log in via ManageSieve.
+
+.. _account.smtp:
+
+``smtp (default: true)`` : *Boolean*
+ Determines whether a newly created user can log in via SMTP (SMTP AUTH).
+
+Example::
+
+ [account]
+ delete_directory = false
+ directory_mode = 448
+ disk_usage = false
+ random_password = true
+ password_length = 10
+ smtp = true
+ pop3 = true
+ imap = true
+ sieve = true
+
+
+BIN
+---
+The **bin** section is used to specify some paths to some binaries required
+by |vmm(1)|_.
+
+.. _bin.dovecotpw:
+
+``dovecotpw (default: /usr/sbin/dovecotpw)`` : *String*
+ The absolute path to the dovecotpw binary. This binary is used to
+ generate a password hash, if |misc.password_scheme|_ is set to one of
+ 'SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5', 'LANMAN', 'NTLM' or 'RPA'.
+
+.. _bin.du:
+
+``du (default: /usr/bin/du)`` : *String*
+ The absolute path to **du**\(1). This binary is used to summarize the disk
+ usage of a user's Maildir.
+
+.. _bin.postconf:
+
+``postconf (default: /usr/sbin/postconf)`` : *String*
+ The absolute path to Postfix' |postconf(1)|_. This binary is required when
+ |vmm(1)|_ has to check for some Postfix settings, e.g.
+ |virtual_alias_expansion_limit|_.
+
+Example::
+
+ [bin]
+ dovecotpw = /usr/sbin/dovecotpw
+ du = /usr/bin/du
+ postconf = /usr/sbin/postconf
+
+
+DATABASE
+--------
+The **database** section is used to specify some options required to
+connect to the database.
+
+.. _database.host:
+
+``host (default: localhost)`` : *String*
+ Hostname or IP address of the database server.
+
+.. _database.name:
+
+``name (default: mailsys)`` : *String*
+ Name of the database.
+
+.. _database.pass:
+
+``pass (default: None)`` : *String*
+ Database password.
+
+.. _database.user:
+
+``user (default: None)`` : *String*
+ Name of the database user.
+
+Example::
+
+ [database]
+ host = localhost
+ user = vmm
+ pass = PY_SRJ}L/0p-oOk
+ name = mailsys
+
+
+DOMAIN
+------
+The **domain** section specifies some domain related settings.
+
+.. _domain.auto_postmaster:
+
+``auto_postmaster (default: true)`` : *Boolean*
+ Determines if |vmm(1)|_ should create also a postmaster account when a new
+ domain is created (|domainadd|_).
+
+.. _domain.delete_directory:
+
+``delete_directory (default: false)`` : *Boolean*
+ Specifies whether the domain directory and all user directories inside
+ should be deleted when a domain is deleted (|domaindelete|_).
+
+.. _domain.directory_mode:
+
+``directory_mode (default: 504)`` : *Int*
+ Access mode for the domain directory in decimal (base 10) notation.
+
+ | For example: 'drwxrwx---' -> octal 0770 -> decimal 504
+
+.. _domain.force_deletion:
+
+``force_deletion (default: false)`` : *Boolean*
+ Force deletion of accounts and aliases when a domain is deleted
+ (|domaindelete|_).
+
+Example::
+
+ [domain]
+ auto_postmaster = true
+ delete_directory = false
+ directory_mode = 504
+ force_deletion = false
+
+
+MAILBOX
+-------
+The **mailbox** section is used to specify some options for new created
+mailboxes in the users home directories. The INBOX will be created always.
+
+.. _mailbox.folders:
+
+``folders (default: Drafts:Sent:Templates:Trash)`` : *String*
+ A colon separated list of mailboxes that should be created. (Works currently
+ only if the |mailbox.format|_ is either **maildir** or **mbox**. For other
+ formats use Dovecot's autocreate plugin
+ <http://wiki.dovecot.org/Plugins/Autocreate>.) If no additionally mailboxes
+ should be created, set the value of this option to a single colon ('**:**').
+
+ If you want to create folders containing one or more subfolders, separate
+ them with a single dot ('**.**').
+
+.. _mailbox.format:
+
+``format (default: maildir)`` : *String*
+ The mailbox format to be used for a user's mailbox. Depending on the used
+ Dovecot version there are up to four supported formats:
+
+ ``maildir``
+ since Dovecot v1.0.0
+ ``mbox``
+ since Dovecot v1.0.0
+ ``dbox``
+ since Dovecot v1.2.0
+ ``mdbox``
+ comes with Dovecot v2.0.0
+
+
+Example::
+
+ [mailbox]
+ folders = Drafts:Sent:Templates:Trash:Lists.Dovecot:Lists.Postfix
+ format = maildir
+
+.. _imap_uft7:
+
+.. note:: If you want to use internationalized mailbox names in the
+ **folders** setting, you have to specify them in a modified version of the
+ UTF-7 encoding (see :RFC:`3501`, section 5.1.3).
+
+ Dovecot provides a useful utility for mUTF-7 <-> UTF-8 conversion:
+ **imap-utf7**, it's available since Dovecot version 1.2.0.
+..
+
+imap-utf7 example::
+
+ user@host:~$ /usr/local/libexec/dovecot/imap-utf7 -r Wysłane
+ Wys&AUI-ane
+ user@host:~$ /usr/local/libexec/dovecot/imap-utf7 "&AVo-mietnik"
+ Śmietnik
+
+
+MISC
+----
+The **misc** section is used to define miscellaneous settings.
+
+.. _misc.base_directory:
+
+``base_directory (default: /srv/mail)`` : *String*
+ All domain directories will be created inside this directory.
+
+.. _misc.password_scheme:
+
+``password_scheme (default: CRAM-MD5)`` : *String*
+ Password scheme to use (see also: **dovecotpw -l**).
+
+.. _misc.gid_mail:
+
+``gid_mail (default: 8)`` : *Int*
+ Numeric group ID of group mail (`mail_privileged_group` from
+ *dovecot.conf*)
+
+.. _misc.transport:
+
+``transport (default: dovecot:)`` : *String*
+ Default transport for domains and accounts. For details see
+ |transport(5)|_.
+
+.. _misc.dovecot_version:
+
+``dovecot_version (default: 12)`` : *Int*
+ The concatenated major and minor version number of the currently used
+ Dovecot version. (see: **dovecot --version**).
+
+ When, for example, the command **dovecot --version** prints *1.1.18*, set
+ the value of this option to **11**.
+
+Example::
+
+ [misc]
+ base_directory = /srv/mail
+ password_scheme = PLAIN
+ gid_mail = 8
+ transport = dovecot:
+ dovecot_version = 11
+
+
+FILES
+=====
+*/root/vmm.cfg*
+ | will be used when found.
+*/usr/local/etc/vmm.cfg*
+ | will be used when the above file doesn't exist.
+*/etc/vmm.cfg*
+ | will be used when none of the both above mentioned files exists.
+
+
+SEE ALSO
+========
+|vmm(1)|_
+
+
+COPYING
+=======
+vmm and its manual pages were written by Pascal Volk and are licensed under
+the terms of the BSD License.
+
+.. include:: ../substitute_links.rst
+.. include:: ../substitute_links_5.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/substitute_links.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,19 @@
+.. set references to other manpages and set links in the html output
+
+.. |vmm(1)| replace:: **vmm**\(1)
+.. _vmm(1): vmm.1
+
+.. |vmm.cfg(5)| replace:: **vmm.cfg**\(5)
+.. _vmm.cfg(5): vmm.cfg.5
+
+
+.. non vmm
+
+.. |postconf(1)| replace:: **postconf**\(1)
+.. _postconf(1): http://www.postfix.org/postconf.1.html
+
+.. |transport(5)| replace:: **transport**\(5)
+.. _transport(5): http://www.postfix.org/transport.5.html
+
+.. #EOF
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/substitute_links_1.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,12 @@
+.. Substitutions in section 1
+
+.. |account.disk_usage| replace:: `account.disk_usage`
+.. _account.disk_usage: vmm.cfg.5#account-disk-usage
+
+.. |domain.force_deletion| replace:: `domain.force_deletion`
+.. _domain.force_deletion: vmm.cfg.5#domain-force-deletion
+
+.. |misc.transport| replace:: `misc.transport`
+.. _misc.transport: vmm.cfg.5#misc-transport
+
+.. #EOF
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/man/substitute_links_5.rst Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,30 @@
+.. Substitutions in section 5
+
+.. |account.password_length| replace:: **password_length**
+.. |mailbox.format| replace:: **format**
+.. |misc.password_scheme| replace:: **misc.password_scheme**
+
+.. |vmm configure| replace:: **vmm configure**
+.. _`vmm configure`: vmm.1#configure
+
+.. |domainadd| replace:: **domainadd**
+.. _domainadd: vmm.1#domainadd
+
+.. |domaindelete| replace:: **domaindelete**
+.. _domaindelete: vmm.1#domaindelete
+
+.. |useradd| replace:: **useradd**
+.. _useradd: vmm.1#useradd
+
+.. |userdelete| replace:: **userdelete**
+.. _userdelete: vmm.1#userdelete
+
+.. |userinfo| replace:: **userinfo**
+.. _userinfo: vmm.1#userinfo
+
+.. non vmm
+.. |virtual_alias_expansion_limit| replace:: `virtual_alias_expansion_limit`
+.. _virtual_alias_expansion_limit:
+ http://www.postfix.org/postconf.5.html#virtual_alias_expansion_limit
+
+.. #EOF
--- a/pgsql/create_tables-dovecot-1.2.x.pgsql Tue Apr 20 02:59:08 2010 +0000
+++ b/pgsql/create_tables-dovecot-1.2.x.pgsql Tue Apr 20 03:04:16 2010 +0000
@@ -41,7 +41,7 @@
CREATE TABLE domain_data (
gid bigint NOT NULL DEFAULT nextval('domain_gid'),
- tid bigint NOT NULL DEFAULT 1, -- defualt transport
+ tid bigint NOT NULL DEFAULT 1, -- default transport
domaindir varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
CONSTRAINT pkey_domain_data PRIMARY KEY (gid),
CONSTRAINT fkey_domain_data_tid_transport FOREIGN KEY (tid)
--- a/pgsql/create_tables.pgsql Tue Apr 20 02:59:08 2010 +0000
+++ b/pgsql/create_tables.pgsql Tue Apr 20 03:04:16 2010 +0000
@@ -41,7 +41,7 @@
CREATE TABLE domain_data (
gid bigint NOT NULL DEFAULT nextval('domain_gid'),
- tid bigint NOT NULL DEFAULT 1, -- defualt transport
+ tid bigint NOT NULL DEFAULT 1, -- default transport
domaindir varchar(40) NOT NULL, --/srv/mail/$RAND/4294967294
CONSTRAINT pkey_domain_data PRIMARY KEY (gid),
CONSTRAINT fkey_domain_data_tid_transport FOREIGN KEY (tid)
--- a/po/de.po Tue Apr 20 02:59:08 2010 +0000
+++ b/po/de.po Tue Apr 20 03:04:16 2010 +0000
@@ -5,85 +5,90 @@
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"
+"Report-Msgid-Bugs-To: neverseen@users.sourceforge.net\n"
+"POT-Creation-Date: 2010-01-29 23:22+0100\n"
"PO-Revision-Date: 2009-08-25 06:11+0200\n"
-"Last-Translator: Pascal Volk <p.volk@veb-it.de>\n"
+"Last-Translator: Pascal Volk <neverseen@users.sourceforge.net>\n"
"Language-Team: German\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"
-#: 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:37 VirtualMailManager/Relocated.py:42
#, python-format
msgid "There is already an alias with the address “%s”."
msgstr "Es existiert bereits ein Alias mit der Adresse „%s“."
-#: VirtualMailManager/Account.py:41 VirtualMailManager/Alias.py:45
+#: VirtualMailManager/Account.py:42 VirtualMailManager/Alias.py:41
#, python-format
msgid "There is already a relocated user with the address “%s”."
msgstr "Es gibt bereits ein relocated User mit der 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
+#: VirtualMailManager/Account.py:62 VirtualMailManager/Alias.py:57
+#: VirtualMailManager/Domain.py:161 VirtualMailManager/Domain.py:187
+#: VirtualMailManager/Domain.py:218 VirtualMailManager/Relocated.py:58
#, python-format
-msgid "The domain “%s” doesn't exist yet."
-msgstr "Die Domain „%s“ existiert noch nicht."
+msgid "The domain “%s” doesn't exist."
+msgstr "Die Domain „%s“ existiert nicht."
-#: VirtualMailManager/Account.py:80
+#: VirtualMailManager/Account.py:81
#, python-format
msgid "Unknown service “%s”."
msgstr "Unbekannter Service „%s“."
-#: VirtualMailManager/Account.py:83 VirtualMailManager/Account.py:150
-#: VirtualMailManager/Account.py:178 VirtualMailManager/Account.py:212
+#: VirtualMailManager/Account.py:84 VirtualMailManager/Account.py:157
+#: VirtualMailManager/Account.py:188 VirtualMailManager/Account.py:223
#, python-format
-msgid "The account “%s” doesn't exists."
+msgid "The account “%s” doesn't exist."
msgstr "Der Account „%s“ existiert nicht."
-#: VirtualMailManager/Account.py:145
+#: VirtualMailManager/Account.py:152
#, python-format
msgid "The account “%s” already exists."
msgstr "Der Account „%s“ existiert bereits."
-#: VirtualMailManager/Account.py:186
+#. TP: A service (pop3/imap/…) is enabled/usable for a user
+#: VirtualMailManager/Account.py:197
msgid "enabled"
msgstr "aktiviert"
-#: VirtualMailManager/Account.py:188
+#. TP: A service (pop3/imap) isn't enabled/usable for a user
+#: VirtualMailManager/Account.py:200
msgid "disabled"
msgstr "deaktiviert"
-#: VirtualMailManager/Account.py:233
+#: VirtualMailManager/Account.py:244
#, 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:241
+#: VirtualMailManager/Account.py:252
msgid "uid must be an int/long."
msgstr "Die UID muss eine Ganzzahl sein."
-#: VirtualMailManager/Account.py:243
+#: VirtualMailManager/Account.py:254
msgid "uid must be greater than 0."
msgstr "Die UID muss größer als 0 sein."
-#: VirtualMailManager/Account.py:251
+#: VirtualMailManager/Account.py:262
#, python-format
msgid "There is no account with the UID “%d”."
msgstr "Es existiert kein Account mit der UID „%d“."
-#: VirtualMailManager/Alias.py:30 VirtualMailManager/Relocated.py:30
+#: VirtualMailManager/Alias.py:28 VirtualMailManager/Relocated.py:28
msgid "Address and destination are identical."
msgstr "Alias- und Ziel-Adresse sind identisch."
-#: VirtualMailManager/Alias.py:40 VirtualMailManager/Relocated.py:39
+#: VirtualMailManager/Alias.py:37 VirtualMailManager/Relocated.py:37
#, 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:67
#, python-format
msgid ""
"Can't add new destination to alias “%(address)s”.\n"
@@ -96,147 +101,183 @@
"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."
+#: VirtualMailManager/Alias.py:76
+#, fuzzy
+msgid "No destination address specified for alias."
msgstr "Keine Ziel-Adresse für den Alias angegeben."
-#: VirtualMailManager/Alias.py:91
+#: VirtualMailManager/Alias.py:87
#, 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."
+#: VirtualMailManager/Alias.py:100 VirtualMailManager/Alias.py:117
+#, fuzzy, python-format
+msgid "The alias “%s” doesn't exist."
msgstr "Der Alias „%s“ existiert nicht."
-#: VirtualMailManager/Alias.py:125
-#, python-format
-msgid "The alias “%(a)s” with destination “%(d)s” doesn't exists."
+#: VirtualMailManager/Alias.py:119
+#, fuzzy, python-format
+msgid "The alias “%(a)s” with destination “%(d)s” doesn't exist."
msgstr "Der Alias „%(a)s“ mit der Ziel-Adresse „%(d)s“ existiert nicht."
-#: VirtualMailManager/AliasDomain.py:32
+#: VirtualMailManager/AliasDomain.py:30
#, python-format
msgid "The domain “%s” is a primary domain."
msgstr "Die Domain „%s“ ist eine primäre Domain."
-#: VirtualMailManager/AliasDomain.py:37
+#: VirtualMailManager/AliasDomain.py:35
#, 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."
+#: VirtualMailManager/AliasDomain.py:38 VirtualMailManager/AliasDomain.py:68
+#, fuzzy
+msgid "No destination domain specified for alias domain."
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."
+#: VirtualMailManager/AliasDomain.py:41 VirtualMailManager/AliasDomain.py:71
+#, fuzzy, python-format
+msgid "The target domain “%s” doesn't exist."
msgstr "Die Ziel-Domain „%s“ existiert noch nicht."
-#: VirtualMailManager/AliasDomain.py:62
+#: VirtualMailManager/AliasDomain.py:60
#, 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."
+#: VirtualMailManager/AliasDomain.py:63 VirtualMailManager/AliasDomain.py:74
+#: VirtualMailManager/AliasDomain.py:97
+#, fuzzy, python-format
+msgid "The alias domain “%s” doesn't exist."
msgstr "Die Alias-Domain „%s“ existiert noch nicht."
-#: VirtualMailManager/AliasDomain.py:79
+#: VirtualMailManager/AliasDomain.py:77
#, python-format
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/Config.py:102 VirtualMailManager/Config.py:137
+#: VirtualMailManager/Config.py:105
+#, python-format
+msgid "Not a boolean: “%s”"
+msgstr ""
+
+#: VirtualMailManager/Config.py:134
+#, python-format
+msgid "Bad format: “%s” - expected: section.option"
+msgstr ""
+
+#: VirtualMailManager/Config.py:347
+msgid "Missing options, which have no default value.\n"
+msgstr ""
+
+#: VirtualMailManager/Config.py:348 VirtualMailManager/Config.py:416
#, python-format
msgid "Using configuration file: %s\n"
msgstr "Verwende Konfigurationsdatei: %s\n"
-#: VirtualMailManager/Config.py:106
+#: VirtualMailManager/Config.py:351
#, python-format
-msgid "missing section: %s\n"
-msgstr "Fehlender Abschnitt: %s\n"
+msgid "* Section: %s\n"
+msgstr "* Sektion: %s\n"
+
+#: VirtualMailManager/Config.py:367
+#, python-format
+msgid "“%s” is not a directory"
+msgstr ""
-#: VirtualMailManager/Config.py:108
+#: VirtualMailManager/Config.py:379
#, python-format
-msgid "missing options in section %s:\n"
-msgstr "Fehlende Optionen im Abschnitt %s:\n"
+msgid "“%s” is not a file"
+msgstr "„%s“ ist keine Datei."
+
+#: VirtualMailManager/Config.py:382
+#, python-format
+msgid "File is not executable: “%s”"
+msgstr ""
-#: VirtualMailManager/Config.py:140
+#: VirtualMailManager/Config.py:408
#, python-format
-msgid "* Config section: “%s”"
-msgstr "* Konfigurations Abschnitt: „%s“"
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
+msgstr "Neuer Wert für Option %(option)s [%(current_value)s]: "
+
+#: VirtualMailManager/Config.py:418
+#, python-format
+msgid "* Configuration section: “%s”"
+msgstr "* Konfigurations Sektion: „%s“"
-#: VirtualMailManager/Config.py:143
+#: VirtualMailManager/Config.py:429
#, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
-msgstr "Neuer Wert für Option %(opt)s [%(val)s]: "
+msgid "Warning: %s"
+msgstr "Warnungen: %s"
-#: VirtualMailManager/Domain.py:39
+#: VirtualMailManager/Config.py:433
+#: VirtualMailManager/VirtualMailManager.py:198
+msgid "Too many failures - try again later."
+msgstr ""
+
+#: VirtualMailManager/Domain.py:37
#, python-format
msgid "The domain “%s” is an alias domain."
msgstr "Die Domain „%s“ ist eine Alias-Domain."
-#: VirtualMailManager/Domain.py:124
+#: VirtualMailManager/Domain.py:122
msgid "There are accounts and aliases."
msgstr "Es sind noch Accounts und Aliase vorhanden."
-#: VirtualMailManager/Domain.py:127
+#: VirtualMailManager/Domain.py:125
msgid "There are accounts."
msgstr "Es sind noch Accounts vorhanden."
-#: VirtualMailManager/Domain.py:130
+#: VirtualMailManager/Domain.py:128
msgid "There are aliases."
msgstr "Es sind noch Aliase vorhanden."
-#: VirtualMailManager/Domain.py:145
+#: VirtualMailManager/Domain.py:143
#, python-format
msgid "The domain “%s” already exists."
msgstr "Die Domain „%s“ existiert bereits."
-#: VirtualMailManager/EmailAddress.py:46
+#: VirtualMailManager/EmailAddress.py:42
#, python-format
msgid "Missing '@' sign in e-mail address “%s”."
msgstr "In der E-Mail-Adresse „%s“ fehlt das '@'-Zeichen."
-#: VirtualMailManager/EmailAddress.py:49
+#: VirtualMailManager/EmailAddress.py:45
#, python-format
-msgid "“%s” looks not like an e-mail address."
+msgid "“%s” doesn't look like an e-mail address."
msgstr "„%s“ sieht nicht wie eine E-Mail-Adresse aus."
-#: VirtualMailManager/EmailAddress.py:54
+#: VirtualMailManager/EmailAddress.py:50
#, python-format
msgid "Missing domain name after “%s@”."
msgstr "Der Domain-Name nach „%s@“ fehlt."
-#: VirtualMailManager/EmailAddress.py:66
-msgid "No localpart specified."
+#: VirtualMailManager/EmailAddress.py:62
+msgid "No local-part specified."
msgstr "Kein local-part angegeben."
-#: VirtualMailManager/EmailAddress.py:69
+#: VirtualMailManager/EmailAddress.py:65
#, python-format
-msgid "The local part “%s” is too long"
+msgid "The local-part “%s” is too long"
msgstr "Der local-part „%s“ ist zu lang"
-#: VirtualMailManager/EmailAddress.py:76
+#: VirtualMailManager/EmailAddress.py:72
#, python-format
-msgid "The local part “%(lpart)s” contains invalid characters: %(ichrs)s"
+msgid "The local-part “%(lpart)s” contains invalid characters: %(ichrs)s"
msgstr "Der local-part „%(lpart)s“ enthält ungültige Zeichen: %(ichrs)s"
-#: VirtualMailManager/MailLocation.py:32
+#: VirtualMailManager/MailLocation.py:28
msgid "Either mid or maillocation must be specified."
msgstr "Entweder mid oder maillocation muss angegeben werden."
-#: VirtualMailManager/MailLocation.py:38
+#: VirtualMailManager/MailLocation.py:34
msgid "mid must be an int/long."
msgstr "Die MID muss eine Ganzzahl sein."
-#: VirtualMailManager/MailLocation.py:46
+#: VirtualMailManager/MailLocation.py:42
#, python-format
msgid ""
"Invalid folder name “%s”, it may consist only of\n"
@@ -245,37 +286,37 @@
"Unzulässiger Verzeichnisname „%s“, dieser darf nur aus\n"
"1 - 20 Einzelbytezeichen (A-Z, a-z, 0-9 und _) bestehen."
-#: VirtualMailManager/MailLocation.py:59
+#: VirtualMailManager/MailLocation.py:55
msgid "Unknown mid specified."
msgstr "Unbekannte MID angegeben."
-#: VirtualMailManager/Relocated.py:65
-msgid "No destination address for relocated user denoted."
+#: VirtualMailManager/Relocated.py:64
+msgid "No destination address specified for relocated user."
msgstr "Keine Ziel-Adresse für den relocated User angegeben."
-#: VirtualMailManager/Relocated.py:75
+#: VirtualMailManager/Relocated.py:74
#, 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/Relocated.py:88 VirtualMailManager/Relocated.py:101
#, python-format
-msgid "The relocated user “%s” doesn't exists."
+msgid "The relocated user “%s” doesn't exist."
msgstr "Der relocated User „%s“ existiert nicht."
-#: VirtualMailManager/Transport.py:29
+#: VirtualMailManager/Transport.py:27
msgid "Either tid or transport must be specified."
msgstr "Entweder tid oder transport muss angegeben werden."
-#: VirtualMailManager/Transport.py:35
+#: VirtualMailManager/Transport.py:33
msgid "tid must be an int/long."
msgstr "Die tid muss eine Ganzzahl sein."
-#: VirtualMailManager/Transport.py:63
+#: VirtualMailManager/Transport.py:61
msgid "Unknown tid specified."
msgstr "Unbekannte tid angegeben."
-#: VirtualMailManager/VirtualMailManager.py:54
+#: VirtualMailManager/VirtualMailManager.py:47
msgid ""
"You are not root.\n"
"\tGood bye!\n"
@@ -283,11 +324,11 @@
"Sie sind nicht root.\n"
"\tAuf Wiedersehen.\n"
-#: VirtualMailManager/VirtualMailManager.py:74
+#: VirtualMailManager/VirtualMailManager.py:66
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/VirtualMailManager.py:77
#, python-format
msgid ""
"fix permissions (%(perms)s) for “%(file)s”\n"
@@ -296,104 +337,100 @@
"Bitte Zugriffsrechte (%(perms)s) für „%(file)s“ anpassen\n"
"`chmod 0600 %(file)s` wäre großartig."
-#: VirtualMailManager/VirtualMailManager.py:100
+#: VirtualMailManager/VirtualMailManager.py:92
#, python-format
msgid ""
"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
+"(vmm.cfg: section \"misc\", option \"base_directory\")"
msgstr ""
"„%s“ ist kein Verzeichnis.\n"
-"(vmm.cfg: Abschnitt \"domdir\", Option \"base\")"
+"(vmm.cfg: Sektion \"misc\", Option \"base_directory\")"
-#: VirtualMailManager/VirtualMailManager.py:105
-#, python-format
+#: VirtualMailManager/VirtualMailManager.py:97
+#, fuzzy, python-format
msgid ""
-"“%(binary)s” doesn't exists.\n"
+"“%(binary)s” doesn't exist.\n"
"(vmm.cfg: section \"bin\", option \"%(option)s\")"
msgstr ""
"„%(binary)s“ existiert nicht.\n"
-"(vmm.cfg: Abschnitt \"bin\", Option \"%(option)s\")"
+"(vmm.cfg: Sektion \"bin\", Option \"%(option)s\")"
-#: VirtualMailManager/VirtualMailManager.py:109
+#: VirtualMailManager/VirtualMailManager.py:101
#, 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\")"
+"(vmm.cfg: Sektion \"bin\", Option \"%(option)s\")"
-#: VirtualMailManager/VirtualMailManager.py:166
+#: VirtualMailManager/VirtualMailManager.py:149
msgid "The domain name is too long."
msgstr "Der Domain-Name ist zu lang."
-#: VirtualMailManager/VirtualMailManager.py:169
+#: VirtualMailManager/VirtualMailManager.py:152
#, 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/VirtualMailManager.py:191
msgid "Enter new password: "
msgstr "Neues Passwort eingeben: "
-#: VirtualMailManager/VirtualMailManager.py:210
+#. TP: Please preserve the trailing space.
+#: VirtualMailManager/VirtualMailManager.py:193
msgid "Retype new password: "
msgstr "Neues Passwort wiederholen: "
-#: VirtualMailManager/VirtualMailManager.py:212
+#: VirtualMailManager/VirtualMailManager.py:204
msgid "Sorry, passwords do not match"
msgstr "Entschuldigung, die Passwörter stimmen nicht überein"
-#: VirtualMailManager/VirtualMailManager.py:216
+#: VirtualMailManager/VirtualMailManager.py:208
msgid "Sorry, empty passwords are not permitted"
msgstr "Entschuldigung, leere Passwörter sind nicht zulässig"
-#: VirtualMailManager/VirtualMailManager.py:265
-#: VirtualMailManager/VirtualMailManager.py:352
+#: VirtualMailManager/VirtualMailManager.py:256
+#: VirtualMailManager/VirtualMailManager.py:343
#, python-format
msgid "No such directory: %s"
msgstr "Verzeichnis nicht gefunden: %s"
-#: VirtualMailManager/VirtualMailManager.py:340
+#: VirtualMailManager/VirtualMailManager.py:331
msgid "Found \"..\" in home directory path."
msgstr "\"..\" im Pfad zum Benutzerverzeichnis entdeckt."
-#: VirtualMailManager/VirtualMailManager.py:348
-msgid "Owner/group mismatch in home directory detected."
+#: VirtualMailManager/VirtualMailManager.py:339
+#, fuzzy
+msgid "Detected owner/group mismatch in home directory."
msgstr "Benutzerverzeichnis gehört dem/der falschen Benutzer/Gruppe."
-#: VirtualMailManager/VirtualMailManager.py:364
-msgid "FATAL: \"..\" in domain directory path detected."
-msgstr "FATAL: \"..\" im Pfad zum Domain-Verzeichnis entdeckt."
-
-#: VirtualMailManager/VirtualMailManager.py:370
-msgid "FATAL: group mismatch in domain directory detected"
-msgstr "FATAL: Domain-Verzeichnis gehört der falschen Gruppe"
+#: VirtualMailManager/VirtualMailManager.py:354
+#, fuzzy
+msgid "Found \"..\" in domain directory path."
+msgstr "\"..\" im Pfad zum Benutzerverzeichnis entdeckt."
-#: VirtualMailManager/VirtualMailManager.py:457
-#, python-format
-msgid ""
-"Configurtion error: \"%s\"\n"
-"(in section \"connfig\", option \"done\") see also: vmm.cfg(5)\n"
-msgstr ""
-"Konfigurations Fehler: \"%s\"\n"
-"(im Abschnitt \"connfig\", Option \"done\") Siehe auch: vmm.cfg(5)\n"
+#: VirtualMailManager/VirtualMailManager.py:360
+#, fuzzy
+msgid "Detected group mismatch in domain directory."
+msgstr "Domain-Verzeichnis gehört der falschen Gruppe"
-#: VirtualMailManager/VirtualMailManager.py:477
+#: VirtualMailManager/VirtualMailManager.py:455
#, python-format
msgid "Invalid section: “%s”"
-msgstr "Ungültiger Abschnitt: „%s“"
+msgstr "Ungültige Sektion: „%s“"
-#: VirtualMailManager/VirtualMailManager.py:487
-#: VirtualMailManager/VirtualMailManager.py:497
-#: VirtualMailManager/VirtualMailManager.py:516
-#: VirtualMailManager/VirtualMailManager.py:624
-#: VirtualMailManager/VirtualMailManager.py:655
+#: VirtualMailManager/VirtualMailManager.py:465
+#: VirtualMailManager/VirtualMailManager.py:475
+#: VirtualMailManager/VirtualMailManager.py:494
+#: VirtualMailManager/VirtualMailManager.py:602
+#: VirtualMailManager/VirtualMailManager.py:633
#, python-format
msgid "Invalid argument: “%s”"
msgstr "Ungültiges Argument: „%s“"
-#: VirtualMailManager/VirtualMailManager.py:520
+#: VirtualMailManager/VirtualMailManager.py:498
msgid ""
"The keyword “detailed” is deprecated and will be removed in a future "
"release.\n"
@@ -403,35 +440,36 @@
" Version entfernt werden.\n"
" Verwenden Sie bitte das Schlüsselwort „full“, um alle Details zu erhalten."
-#: VirtualMailManager/VirtualMailManager.py:593
+#: VirtualMailManager/VirtualMailManager.py:571
#, python-format
msgid "The pattern “%s” contains invalid characters."
msgstr "Das Muster „%s“ enthält ungültige Zeichen."
-#: 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/VirtualMailManager.py:597
+#, fuzzy, python-format
+msgid "The destination account/alias “%s” doesn't exist."
+msgstr "Der Ziel-Account/-Alias „%s“ existiert nicht."
-#: VirtualMailManager/VirtualMailManager.py:636
-#, python-format
+#: VirtualMailManager/VirtualMailManager.py:614
+#, 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: %(raeson)s"
+" Reason: %(reason)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"
+" Grund: %(reason)s"
-#: VirtualMailManager/VirtualMailManager.py:676
-msgid "Account doesn't exists"
+#: VirtualMailManager/VirtualMailManager.py:653
+#, fuzzy
+msgid "Account doesn't exist"
msgstr "Der Account existiert nicht"
-#: VirtualMailManager/VirtualMailManager.py:692
-#: VirtualMailManager/VirtualMailManager.py:702
+#: VirtualMailManager/VirtualMailManager.py:669
+#: VirtualMailManager/VirtualMailManager.py:679
msgid ""
"The service name “managesieve” is deprecated and will be removed\n"
" in a future release.\n"
@@ -441,14 +479,23 @@
" Version entfernt werden.\n"
" Verwenden Sie stattdessen bitte den Servicename „sieve“."
-#: VirtualMailManager/ext/Postconf.py:44
-#, python-format
+#: VirtualMailManager/ext/Postconf.py:41
+#, fuzzy, python-format
msgid ""
-"The value “%s” looks not like a valid postfix configuration parameter name."
+"The value “%s” doesn't look like a valid postfix configuration parameter "
+"name."
msgstr ""
"„%s“ sieht nicht wie ein gültiger Postfix Konfigurationsparametername aus."
-#: vmm:34
+#. 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*
+#: vmm:26
#, python-format
msgid ""
"Usage: %s SUBCOMMAND OBJECT ARGS*\n"
@@ -459,57 +506,60 @@
" kurz lang\n"
" Unterbefehl Objekt args (* = optional)\n"
-#: vmm:73 vmm:84 vmm:494
-msgid "Error"
-msgstr "Fehler"
+#: vmm:65 vmm:76
+#, python-format
+msgid "Error: %s\n"
+msgstr "Fehler: %s\n"
-#: vmm:111
+#. TP: e.g. 'Domain information' or 'Account information'
+#: vmm:104
msgid "information"
msgstr "Informationen"
-#: vmm:121
+#. TP: e.g. 'Available alias addresses' or 'Available accounts'
+#: vmm:115
msgid "Available"
msgstr "Verfügbare"
-#: vmm:124 vmm:223 vmm:229
+#: vmm:118 vmm:218 vmm:224
msgid "alias domains"
msgstr "Alias-Domains"
-#: vmm:134 vmm:145 vmm:169
+#: vmm:128 vmm:139 vmm:163
msgid "\tNone"
msgstr "\tKeine"
-#: vmm:138
+#: vmm:132
msgid "Alias information"
msgstr "Alias Informationen"
-#: vmm:140
+#: vmm:134
#, python-format
msgid "\tMail for %s will be redirected to:"
msgstr "\tE-Mails für %s werden weitergeleitet an:"
-#: vmm:149
+#: vmm:143
msgid "Relocated information"
msgstr "Relocated Informationen"
-#: vmm:151
+#: vmm:145
#, python-format
msgid "\tUser “%(addr)s” has moved to “%(dest)s”"
msgstr "\tDer Benutzer „%(addr)s“ ist erreichbar unter „%(dest)s“"
-#: vmm:164
+#: vmm:158
msgid "Available domains"
msgstr "Verfügbare Domains"
-#: vmm:166
+#: vmm:160
msgid "Matching domains"
msgstr "Übereinstimmende Domains"
-#: vmm:180
+#: vmm:174
msgid "Alias domain information"
msgstr "Alias-Domain Informationen"
-#: vmm:186
+#: vmm:180
#, python-format
msgid ""
"\tThe alias domain %(alias)s belongs to:\n"
@@ -518,114 +568,142 @@
"\tDie Alias-Domain %(alias)s gehört zu:\n"
"\t * %(domain)s"
-#: vmm:197 vmm:205 vmm:213
+#: vmm:191 vmm:199 vmm:207
msgid "Missing domain name."
msgstr "Kein Domain-Name angegeben."
-#: vmm:215 vmm:219
+#: vmm:210 vmm:214
msgid "Domain"
msgstr "Domain"
-#: vmm:221 vmm:230
+#: vmm:216 vmm:225
msgid "accounts"
msgstr "Accounts"
-#: vmm:225 vmm:231
+#: vmm:220 vmm:226
msgid "aliases"
msgstr "Aliase"
-#: vmm:227 vmm:232
+#: vmm:222 vmm:227
msgid "relocated users"
msgstr "Relocated Users"
-#: vmm:236
+#: vmm:238
msgid "Missing domain name and new transport."
msgstr "Domain-Name und neuer Transport fehlen."
-#: vmm:238
+#: vmm:240
msgid "Missing new transport."
msgstr "Neuer Transport fehlt."
-#: vmm:247 vmm:262
+#: vmm:249 vmm:272
msgid "Missing alias domain name and target domain name."
msgstr "Domain-Namen für Alias- und Ziel-Domain fehlen."
-#: vmm:249 vmm:264
+#: vmm:251 vmm:274
msgid "Missing target domain name."
msgstr "Keine Ziel-Domain angegeben."
-#: vmm:255 vmm:270
+#: vmm:257 vmm:280
msgid "Missing alias domain name."
msgstr "Keine Alias-Domain angegeben."
-#: vmm:276 vmm:285 vmm:293 vmm:323 vmm:331 vmm:339
+#: vmm:286 vmm:295 vmm:303 vmm:345 vmm:353 vmm:361
msgid "Missing e-mail address."
msgstr "E-Mail-Adresse fehlt."
-#: vmm:301
+#: vmm:312
msgid "alias addresses"
msgstr "Alias-Adressen"
-#: vmm:307
-msgid "Missing e-mail address and users name."
+#: vmm:329
+#, fuzzy
+msgid "Missing e-mail address and user’s name."
msgstr "E-Mail-Adresse und der Name des Benutzers fehlen."
-#: vmm:309
-msgid "Missing users name."
+#: vmm:331
+#, fuzzy
+msgid "Missing user’s name."
msgstr "Name des Benutzers fehlt."
-#: vmm:315
+#: vmm:337
msgid "Missing e-mail address and transport."
msgstr "E-Mail-Adresse und Transport fehlen."
-#: vmm:317
+#: vmm:339
msgid "Missing transport."
msgstr "Transport fehlt."
-#: vmm:348
+#: vmm:370
msgid "Missing alias address and destination."
msgstr "Alias- und Ziel-Adresse fehlen."
-#: vmm:350 vmm:373
+#: vmm:372 vmm:407
msgid "Missing destination address."
msgstr "Die Ziel-Adresse fehlt."
-#: vmm:356 vmm:362
+#: vmm:378 vmm:396
msgid "Missing alias address"
msgstr "Die Alias-Adresse fehlt."
-#: vmm:371
+#: vmm:405
msgid "Missing relocated address and destination."
msgstr "Die Adresse des relocated Users und Ziel-Adresse fehlen."
-#: vmm:379 vmm:387
+#: vmm:413 vmm:431
msgid "Missing relocated address"
msgstr "Die Adresse des relocated Users fehlt."
-#: vmm:393
+#: vmm:437
msgid "Missing userid"
msgstr "Keine UID angegeben."
-#: vmm:406
+#: vmm:450
msgid "Warnings:"
msgstr "Warnungen:"
-#: vmm:412
+#: vmm:460
msgid "from"
msgstr "vom"
-#: vmm:412
+#. 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"
msgstr "Version"
-#: vmm:414
+#: vmm:463
msgid "on"
msgstr "auf"
-#: vmm:488
-msgid "Unknown subcommand"
-msgstr "Unbekannter Unterbefehl"
+#: vmm:464
+msgid "is free software and comes with ABSOLUTELY NO WARRANTY."
+msgstr ""
+
+#: vmm:472
+#, python-format
+msgid "Plan A failed ... trying Plan B: %(subcommand)s %(object)s"
+msgstr ""
+
+#: vmm:535
+#, python-format
+msgid "Unknown subcommand: “%s”"
+msgstr "Unbekannter Unterbefehl „%s“"
-#: vmm:491
-msgid "Ouch"
-msgstr "Autsch"
+#. TP: We have to cry, because root has killed/interrupted vmm
+#. with Ctrl+C or Ctrl+D.
+#: vmm:540
+msgid ""
+"\n"
+"Ouch!\n"
+msgstr ""
+"\n"
+"Autsch!\n"
+
+#: vmm:543
+#, python-format
+msgid "Error: %s"
+msgstr "Fehler: %s"
+
--- a/po/vmm.pot Tue Apr 20 02:59:08 2010 +0000
+++ b/po/vmm.pot Tue Apr 20 03:04:16 2010 +0000
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: vmm 0.5.2\n"
+"Project-Id-Version: vmm 0.6.0\n"
"Report-Msgid-Bugs-To: neverseen@users.sourceforge.net\n"
-"POT-Creation-Date: 2009-10-20 19:19+0200\n"
+"POT-Creation-Date: 2010-01-29 23:22+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"
@@ -153,29 +153,63 @@
"The alias domain “%(alias)s” is already assigned to the domain “%(domain)s”."
msgstr ""
-#: VirtualMailManager/Config.py:90 VirtualMailManager/Config.py:125
+#: VirtualMailManager/Config.py:105
+#, python-format
+msgid "Not a boolean: “%s”"
+msgstr ""
+
+#: VirtualMailManager/Config.py:134
+#, python-format
+msgid "Bad format: “%s” - expected: section.option"
+msgstr ""
+
+#: VirtualMailManager/Config.py:347
+msgid "Missing options, which have no default value.\n"
+msgstr ""
+
+#: VirtualMailManager/Config.py:348 VirtualMailManager/Config.py:416
#, python-format
msgid "Using configuration file: %s\n"
msgstr ""
-#: VirtualMailManager/Config.py:94
+#: VirtualMailManager/Config.py:351
+#, python-format
+msgid "* Section: %s\n"
+msgstr ""
+
+#: VirtualMailManager/Config.py:367
#, python-format
-msgid "missing section: %s\n"
+msgid "“%s” is not a directory"
+msgstr ""
+
+#: VirtualMailManager/Config.py:379
+#, python-format
+msgid "“%s” is not a file"
msgstr ""
-#: VirtualMailManager/Config.py:96
+#: VirtualMailManager/Config.py:382
#, python-format
-msgid "missing options in section %s:\n"
+msgid "File is not executable: “%s”"
+msgstr ""
+
+#: VirtualMailManager/Config.py:408
+#, python-format
+msgid "Enter new value for option %(option)s [%(current_value)s]: "
msgstr ""
-#: VirtualMailManager/Config.py:128
+#: VirtualMailManager/Config.py:418
#, python-format
-msgid "* Config section: “%s”"
+msgid "* Configuration section: “%s”"
msgstr ""
-#: VirtualMailManager/Config.py:131
+#: VirtualMailManager/Config.py:429
#, python-format
-msgid "Enter new value for option %(opt)s [%(val)s]: "
+msgid "Warning: %s"
+msgstr ""
+
+#: VirtualMailManager/Config.py:433
+#: VirtualMailManager/VirtualMailManager.py:198
+msgid "Too many failures - try again later."
msgstr ""
#: VirtualMailManager/Domain.py:37
@@ -280,126 +314,119 @@
"\tGood bye!\n"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:67
+#: VirtualMailManager/VirtualMailManager.py:66
msgid "No “vmm.cfg” found in: /root:/usr/local/etc:/etc"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:78
+#: VirtualMailManager/VirtualMailManager.py:77
#, python-format
msgid ""
"fix permissions (%(perms)s) for “%(file)s”\n"
"`chmod 0600 %(file)s` would be great."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:93
+#: VirtualMailManager/VirtualMailManager.py:92
#, python-format
msgid ""
"“%s” is not a directory.\n"
-"(vmm.cfg: section \"domdir\", option \"base\")"
+"(vmm.cfg: section \"misc\", option \"base_directory\")"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:98
+#: VirtualMailManager/VirtualMailManager.py:97
#, python-format
msgid ""
"“%(binary)s” doesn't exist.\n"
"(vmm.cfg: section \"bin\", option \"%(option)s\")"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:102
+#: VirtualMailManager/VirtualMailManager.py:101
#, python-format
msgid ""
"“%(binary)s” is not executable.\n"
"(vmm.cfg: section \"bin\", option \"%(option)s\")"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:150
+#: VirtualMailManager/VirtualMailManager.py:149
msgid "The domain name is too long."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:153
+#: VirtualMailManager/VirtualMailManager.py:152
#, python-format
msgid "The domain name “%s” is invalid."
msgstr ""
#. TP: Please preserve the trailing space.
-#: VirtualMailManager/VirtualMailManager.py:192
+#: VirtualMailManager/VirtualMailManager.py:191
msgid "Enter new password: "
msgstr ""
#. TP: Please preserve the trailing space.
-#: VirtualMailManager/VirtualMailManager.py:194
+#: VirtualMailManager/VirtualMailManager.py:193
msgid "Retype new password: "
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:200
+#: VirtualMailManager/VirtualMailManager.py:204
msgid "Sorry, passwords do not match"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:203
+#: VirtualMailManager/VirtualMailManager.py:208
msgid "Sorry, empty passwords are not permitted"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:251
-#: VirtualMailManager/VirtualMailManager.py:338
+#: VirtualMailManager/VirtualMailManager.py:256
+#: VirtualMailManager/VirtualMailManager.py:343
#, python-format
msgid "No such directory: %s"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:326
+#: VirtualMailManager/VirtualMailManager.py:331
msgid "Found \"..\" in home directory path."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:334
+#: VirtualMailManager/VirtualMailManager.py:339
msgid "Detected owner/group mismatch in home directory."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:349
+#: VirtualMailManager/VirtualMailManager.py:354
msgid "Found \"..\" in domain directory path."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:355
+#: VirtualMailManager/VirtualMailManager.py:360
msgid "Detected group mismatch in domain directory."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:439
-#, python-format
-msgid ""
-"Configuration error: \"%s\"\n"
-"(in section \"config\", option \"done\") see also: vmm.cfg(5)\n"
-msgstr ""
-
-#: VirtualMailManager/VirtualMailManager.py:459
+#: VirtualMailManager/VirtualMailManager.py:455
#, python-format
msgid "Invalid section: “%s”"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:469
-#: VirtualMailManager/VirtualMailManager.py:479
-#: VirtualMailManager/VirtualMailManager.py:498
-#: VirtualMailManager/VirtualMailManager.py:606
-#: VirtualMailManager/VirtualMailManager.py:637
+#: VirtualMailManager/VirtualMailManager.py:465
+#: VirtualMailManager/VirtualMailManager.py:475
+#: VirtualMailManager/VirtualMailManager.py:494
+#: VirtualMailManager/VirtualMailManager.py:602
+#: VirtualMailManager/VirtualMailManager.py:633
#, python-format
msgid "Invalid argument: “%s”"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:502
+#: VirtualMailManager/VirtualMailManager.py:498
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 ""
-#: VirtualMailManager/VirtualMailManager.py:575
+#: VirtualMailManager/VirtualMailManager.py:571
#, python-format
msgid "The pattern “%s” contains invalid characters."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:601
+#: VirtualMailManager/VirtualMailManager.py:597
#, python-format
msgid "The destination account/alias “%s” doesn't exist."
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:618
+#: VirtualMailManager/VirtualMailManager.py:614
#, python-format
msgid ""
"The account has been successfully deleted from the database.\n"
@@ -408,12 +435,12 @@
" Reason: %(reason)s"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:658
+#: VirtualMailManager/VirtualMailManager.py:653
msgid "Account doesn't exist"
msgstr ""
-#: VirtualMailManager/VirtualMailManager.py:674
-#: VirtualMailManager/VirtualMailManager.py:684
+#: VirtualMailManager/VirtualMailManager.py:669
+#: VirtualMailManager/VirtualMailManager.py:679
msgid ""
"The service name “managesieve” is deprecated and will be removed\n"
" in a future release.\n"
--- a/setup.py Tue Apr 20 02:59:08 2010 +0000
+++ b/setup.py Tue Apr 20 03:04:16 2010 +0000
@@ -5,51 +5,57 @@
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.ext',
+ 'VirtualMailManager.constants']
+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',
+ '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': 'neverseen@users.sourceforge.net',
+ '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'] = ['pyPgSQL']
+
+setup(**setup_args)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/update_config.py Tue Apr 20 03:04:16 2010 +0000
@@ -0,0 +1,117 @@
+#!/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')
+ 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 get_option(cp, src):
+ ss, so = src.split('.')
+ return cp.get(ss, so)
+
+def upd_052(cp):
+ global had_config
+
+ had_config = cp.remove_section('config')
+ 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, 'account.%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'),
+ ('misc.forcedel', 'domain.force_deletion'),
+ ('misc.passwdscheme', 'misc.password_scheme'),
+ ('misc.dovecotvers', 'misc.dovecot_version')):
+ move_option(cp, src, dst)
+ cp.remove_section('maildir')
+
+# def main():
+if __name__ == '__main__':
+ sect_opt = []
+ had_config = False
+ cf = get_config_file()
+ cp = get_cfg_parser(cf)
+ update(cp)
+ if len(sect_opt):
+ had_config = False
+ update_cfg_file(cp, cf)
+ sect_opt.sort()
+ print 'Please have a look at your configuration: %s' %cf
+ print 'This are your 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:
+ update_cfg_file(cp, cf)
+ print 'Removed section "config" with option "done" (obsolte)'
+ print
--- a/update_config_0.4.x-0.5.py Tue Apr 20 02:59:08 2010 +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 Tue Apr 20 02:59:08 2010 +0000
+++ b/upgrade.sh Tue Apr 20 03:04:16 2010 +0000
@@ -29,7 +29,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
rv=$?
if [ $rv -eq 2 ]; then
echo "please run the install.sh script"
@@ -43,7 +43,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 -m 0700 ${INSTALL_OPTS} vmm ${PREFIX}/sbin
@@ -58,14 +58,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 +66,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 Tue Apr 20 02:59:08 2010 +0000
+++ b/vmm Tue Apr 20 03:04:16 2010 +0000
@@ -5,10 +5,13 @@
"""This is the vmm main script."""
-import gettext
from time import strftime, strptime
from VirtualMailManager import *
+from VirtualMailManager.cli import w_std, w_err
+
+
+# TODO: FIXME
from VirtualMailManager.VirtualMailManager import VirtualMailManager
import VirtualMailManager.Exceptions as VMME
import VirtualMailManager.constants.EXIT as EXIT
@@ -77,7 +80,7 @@
def _getOrder():
order = ()
- if vmm.cfgGetInt('misc', 'dovecotvers') > 11:
+ if vmm.cfgDget('misc.dovecot_version') > 11:
sieve_name = u'sieve'
else:
sieve_name = u'managesieve'
@@ -87,7 +90,7 @@
(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'):
+ or vmm.cfgDget('account.disk_usage'):
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))
@@ -123,7 +126,7 @@
if not dom.startswith('xn--'):
w_std(u'\t%s' % dom)
else:
- w_std(u'\t%s (%s)' % (dom, vmm.ace2idna(dom)))
+ w_std(u'\t%s (%s)' % (dom, ace2idna(dom)))
else:
w_std(_(u'\tNone'))
print
@@ -147,7 +150,7 @@
def _formatDom(domain, main=True):
if domain.startswith('xn--'):
- domain = u'%s (%s)' % (domain, vmm.ace2idna(domain))
+ domain = u'%s (%s)' % (domain, ace2idna(domain))
if main:
return u'\t[+] %s' % domain
else:
@@ -174,14 +177,14 @@
msg = _('Alias domain information')
for k in ['alias', 'domain']:
if info[k].startswith('xn--'):
- info[k] = "%s (%s)" % (info[k], vmm.ace2idna(info[k]))
+ info[k] = "%s (%s)" % (info[k], 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:
+ if argc < 3:
vmm.configure()
else:
vmm.configure(argv[2])
@@ -378,18 +381,18 @@
usage(EXIT.MISSING_ARGS, _(u'Missing alias address'))
try:
_printAliases(argv[2].lower(), vmm.aliasInfo(argv[2].lower()))
- except VMME.VMMAliasException, e:
+ except VMME.VMMException, e:
if e.code() is ERR.ACCOUNT_EXISTS:
w_std(plan_a_b % {'subcommand': u'userinfo',
- 'object': argv[2].lower()})
+ '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()})
+ 'object': argv[2].lower()})
relocated_info()
else:
- raise e
+ raise
def alias_delete():
if argc < 3:
@@ -463,75 +466,15 @@
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()
+def main():
+ subcommand = os.sys.argv[1]
+ known_subcommand = False
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:
+ for s, l, f in subcmd_func.__iter__():
+ if subcommand in (s, l):
+ known_subcommand = True
+ f()
+ if not known_subcommand:
usage(EXIT.UNKNOWN_COMMAND, _(u'Unknown subcommand: “%s”')% argv[1])
show_warnings()
except (EOFError, KeyboardInterrupt):
@@ -543,3 +486,46 @@
w_err(e.code(), _(u'Error: %s') % e.msg())
else:
w_err(e.code(), unicode(e.msg(), ENCODING, 'replace'))
+
+if __name__ == '__main__':
+ __prog__ = os.path.basename(os.sys.argv[0])
+ 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()
+
+ subcmd_func = (
+ #short long function
+ ('da', 'domainadd', domain_add),
+ ('di', 'domaininfo', domain_info),
+ ('dt', 'domaintransport', domain_transport),
+ ('dd', 'domaindelete', domain_delete),
+ ('ada', 'aliasdomainadd', alias_domain_add),
+ ('adi', 'aliasdomaininfo', alias_domain_info),
+ ('ads', 'aliasdomainswitch', alias_domain_switch),
+ ('add', 'aliasdomaindelete', alias_domain_delete),
+ ('ua', 'useradd', user_add),
+ ('ui', 'userinfo', user_info),
+ ('un', 'username', user_name),
+ ('up', 'userpassword', user_password),
+ ('ut', 'usertransport', user_transport),
+ ('u0', 'userdisable', user_disable),
+ ('u1', 'userenable', user_enable),
+ ('ud', 'userdelete', user_delete),
+ ('aa', 'aliasadd', alias_add),
+ ('ai', 'aliasinfo', alias_info),
+ ('ad', 'aliasdelete', alias_delete),
+ ('ra', 'relocatedadd', relocated_add),
+ ('ri', 'relocatedinfo', relocated_info),
+ ('rd', 'relocateddelete', relocated_delete),
+ ('cf', 'configure', configure),
+ ('gu', 'getuser', user_byID),
+ ('ld', 'listdomains', domain_list),
+ ('h', 'help', usage),
+ ('v', 'version', show_version),)
+
+ main()
--- a/vmm.cfg Tue Apr 20 02:59:08 2010 +0000
+++ b/vmm.cfg Tue Apr 20 03:04:16 2010 +0000
@@ -7,55 +7,71 @@
#
[database]
; Hostname or IP address of the database server (String)
-host = 127.0.0.1
+host = localhost
; 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 four supported formats:
+; * maildir - since Dovecot v1.0.0
+; * mbox - since Dovecot v1.0.0
+; * dbox - since Dovecot v1.2.0
+; * mdbox - comes with Dovecot v2.0.0
+format = maildir
+; A colon separated list of mailbox names, that should be created (String)
+; Works currently only if the format is either 'maildir' or 'mbox' . For
+; other formats use Dovecot's Autocreate plugin:
+; <http://wiki.dovecot.org/Plugins/Autocreate>
+; 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
#
-# Services per user
+# Domain settings
#
-[services]
-; allow smtp by default? (Boolean)
-smtp = true
-; allow pop3 by default? (Boolean)
-pop3 = true
-; allow imap by default? (Boolean)
-imap = true
-; allow managesieve by default? (Boolean)
-sieve = true
+[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
#
-# 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
+; Allow smtp by default? (Boolean)
+smtp = true
+; Allow pop3 by default? (Boolean)
+pop3 = true
+; Allow imap by default? (Boolean)
+imap = true
+; Allow managesieve by default? (Boolean)
+sieve = true
#
# external binaries
@@ -72,17 +88,17 @@
# misc settings
#
[misc]
+; The base directory for all domains/accounts (String)
+base_directory = /srv/mail
; Password scheme to use (see also: dovecotpw -l) (String)
-passwdscheme = PLAIN
+password_scheme = CRAM-MD5
; 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
+; default transport for domains and accounts (String)
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
+dovecot_version = 12
#
# Configuration state