# HG changeset patch # User Pascal Volk # Date 1269165545 0 # Node ID 55503d63ba303a61db961eccdaa42c2ef4a18ca5 # Parent 3c766114d0b965615d40d0053fd8181bd5689409# Parent 8c4df3dd2d2c65acdf00e4a038b95152e89c285b merged changes from default(8c4df3dd2d2c) diff -r 8c4df3dd2d2c -r 55503d63ba30 .hgignore --- a/.hgignore Sun Mar 21 09:17:26 2010 +0000 +++ b/.hgignore Sun Mar 21 09:59:05 2010 +0000 @@ -5,3 +5,4 @@ *.py? .*.swp .swp +doc/build diff -r 8c4df3dd2d2c -r 55503d63ba30 INSTALL --- a/INSTALL Sun Mar 21 09:17:26 2010 +0000 +++ b/INSTALL Sun Mar 21 09:59:05 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, diff -r 8c4df3dd2d2c -r 55503d63ba30 TODO --- a/TODO Sun Mar 21 09:17:26 2010 +0000 +++ b/TODO Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Account.py --- a/VirtualMailManager/Account.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Account.py Sun Mar 21 09:59:05 2010 +0000 @@ -4,17 +4,22 @@ """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 +import VirtualMailManager.constants.ERROR as ERR +from VirtualMailManager.Domain import Domain +from VirtualMailManager.EmailAddress import EmailAddress +from VirtualMailManager.errors import AccountError as AccE +from VirtualMailManager.maillocation import MailLocation, known_format +from VirtualMailManager.Transport import Transport + + +_ = lambda msg: msg + class Account(object): """Class to manage e-mail accounts.""" - __slots__ = ('_addr','_base','_gid','_mid','_passwd','_tid','_uid','_dbh') + __slots__ = ('_addr', '_base', '_gid', '_mid', '_passwd', '_tid', '_uid', + '_dbh') + def __init__(self, dbh, address, password=None): self._dbh = dbh self._base = None @@ -29,24 +34,23 @@ self._passwd = password self._setAddr() self._exists() - if self._uid < 1 and VMM.VirtualMailManager.aliasExists(self._dbh, - self._addr): + from VirtualMailManager.Handler import Handler + if self._uid < 1 and Handler.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 an alias with the address “%s”.") % + self._addr, ERR.ALIAS_EXISTS) + if self._uid < 1 and Handler.relocatedExists(self._dbh, self._addr): raise AccE( - _(u"There is already a relocated user with the address “%s”.") %\ - self._addr, ERR.RELOCATED_EXISTS) + _(u"There is already a relocated user with the address “%s”.") % + self._addr, ERR.RELOCATED_EXISTS) def _exists(self): dbc = self._dbh.cursor() - dbc.execute("SELECT uid, mid, tid FROM users \ -WHERE gid=%s AND local_part=%s", - self._gid, self._addr._localpart) + dbc.execute( + "SELECT uid, mid, tid FROM users WHERE gid=%s AND local_part=%s", + self._gid, self._addr.localpart) result = dbc.fetchone() dbc.close() if result is not None: @@ -56,11 +60,11 @@ return False def _setAddr(self): - dom = Domain(self._dbh, self._addr._domainname) + 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) + raise AccE(_(u"The domain “%s” doesn't exist.") % + self._addr.domainname, ERR.NO_SUCH_DOMAIN) self._base = dom.getDir() self._tid = dom.getTransportID() @@ -71,8 +75,11 @@ dbc.close() def _prepare(self, maillocation): + if not known_format(maillocation): + raise AccE(_(u'Unknown mail_location mailbox format: %r') % + maillocation, ERR.UNKNOWN_MAILLOCATION_NAME) self._setID() - self._mid = MailLocation(self._dbh, maillocation=maillocation).getID() + self._mid = MailLocation(format=maillocation).mid def _switchState(self, state, dcvers, service): if not isinstance(state, bool): @@ -106,7 +113,7 @@ def __aliaseCount(self): dbc = self._dbh.cursor() q = "SELECT COUNT(destination) FROM alias WHERE destination = '%s'"\ - %self._addr + % self._addr dbc.execute(q) a_count = dbc.fetchone()[0] dbc.close() @@ -142,7 +149,7 @@ 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, + 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) @@ -163,7 +170,7 @@ dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s', value, self._uid) elif what == 'transport': - self._tid = Transport(self._dbh, transport=value).getID() + self._tid = Transport(self._dbh, transport=value).id dbc.execute('UPDATE users SET tid = %s WHERE uid = %s', self._tid, self._uid) else: @@ -188,7 +195,7 @@ raise AccE(_(u"The account “%s” doesn't exist.") % self._addr, ERR.NO_SUCH_ACCOUNT) else: - keys = ['name', 'uid', 'gid', 'maildir', 'transport', 'smtp', + keys = ['name', 'uid', 'gid', 'mid', 'transport', 'smtp', 'pop3', 'imap', sieve_col] info = dict(zip(keys, info)) for service in ('smtp', 'pop3', 'imap', sieve_col): @@ -199,11 +206,10 @@ # 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['home'] = '%s/%s' % (self._base, info['uid']) + info['mail_location'] = MailLocation(mid=info['mid']).mail_location info['transport'] = Transport(self._dbh, - tid=info['transport']).getTransport() + tid=info['transport']).transport return info def getAliases(self): @@ -242,9 +248,11 @@ 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) + “%(address)s”.") % {'count': a_count, 'address': self._addr}, + ERR.ALIAS_PRESENT) dbc.close() + def getAccountByID(uid, dbh): try: uid = long(uid) @@ -265,3 +273,5 @@ info = dict(zip(keys, info)) return info + +del _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Alias.py --- a/VirtualMailManager/Alias.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Alias.py Sun Mar 21 09:59:05 2010 +0000 @@ -2,121 +2,157 @@ # 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 %r 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)r. +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)r. +This alias already exceeds it's 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': str(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 + it's 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) + + 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 was 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 %r doesn't exist.") % str(self._addr), + NO_SUCH_ALIAS) + if not destination in self._dests: + raise AErr(_(u"The address %(d)r isn't a destination of \ +the alias %(a)r.") % + {'a': str(self._addr), 'd': str(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 %r doesn't exist.") % str(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 %r doesn't exist.") % str(self._addr), + NO_SUCH_ALIAS) + self.__delete() + del self._dests[:] + +del _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/AliasDomain.py --- a/VirtualMailManager/AliasDomain.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/AliasDomain.py Sun Mar 21 09:59:05 2010 +0000 @@ -4,16 +4,16 @@ """Virtual Mail Manager's AliasDomain class to manage alias domains.""" -from __main__ import ERR -from Exceptions import VMMAliasDomainException as VADE -import VirtualMailManager as VMM +import VirtualMailManager.constants.ERROR as ERR +from VirtualMailManager import check_domainname +from VirtualMailManager.errors import AliasDomainError as ADE class AliasDomain(object): """Class to manage e-mail alias domains.""" __slots__ = ('__gid', '__name', '_domain', '_dbh') def __init__(self, dbh, domainname, targetDomain=None): self._dbh = dbh - self.__name = VMM.VirtualMailManager.chkDomainname(domainname) + self.__name = check_domainname(domainname) self.__gid = 0 self._domain = targetDomain self._exists() @@ -27,18 +27,18 @@ if alias is not None: self.__gid, primary = alias if primary: - raise VADE(_(u"The domain “%s” is a primary domain.") % + raise ADE(_(u"The domain “%s” is a primary domain.") % self.__name, ERR.ALIASDOMAIN_ISDOMAIN) def save(self): if self.__gid > 0: - raise VADE(_(u'The alias domain “%s” already exists.') %self.__name, + raise ADE(_(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.'), + raise ADE(_(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.") % + raise ADE (_(u"The target domain “%s” doesn't exist.") % self._domain._name, ERR.NO_SUCH_DOMAIN) dbc = self._dbh.cursor() dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary)\ @@ -56,25 +56,25 @@ if domain is not None: return {'alias': self.__name, 'domain': domain[0]} else:# an almost unlikely case, isn't it? - raise VADE( + raise ADE( _(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) + raise ADE(_(u"The alias domain “%s” doesn't exist.") % self.__name, + ERR.NO_SUCH_ALIASDOMAIN) def switch(self): if self._domain is None: - raise VADE(_(u'No destination domain specified for alias domain.'), + raise ADE(_(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.") % + raise ADE (_(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) + raise ADE(_(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\ + raise ADE(_(u"The alias domain “%(alias)s” is already assigned to\ the domain “%(domain)s”.") % {'alias': self.__name, 'domain': self._domain._name}, ERR.ALIASDOMAIN_EXISTS) @@ -93,7 +93,6 @@ if dbc.rowcount > 0: self._dbh.commit() else: - raise VADE( - _(u"The alias domain “%s” doesn't exist.") % self.__name, - ERR.NO_SUCH_ALIASDOMAIN) + raise ADE(_(u"The alias domain “%s” doesn't exist.") % self.__name, + ERR.NO_SUCH_ALIASDOMAIN) diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Config.py --- a/VirtualMailManager/Config.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Config.py Sun Mar 21 09:59:05 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 _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Domain.py --- a/VirtualMailManager/Domain.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Domain.py Sun Mar 21 09:59:05 2010 +0000 @@ -6,16 +6,23 @@ from random import choice -from __main__ import ERR -from Exceptions import VMMDomainException as VMMDE -import VirtualMailManager as VMM -from Transport import Transport +from VirtualMailManager import check_domainname +from VirtualMailManager.constants.ERROR import \ + ACCOUNT_AND_ALIAS_PRESENT, ACCOUNT_PRESENT, ALIAS_PRESENT, \ + DOMAIN_ALIAS_EXISTS, DOMAIN_EXISTS, NO_SUCH_DOMAIN +from VirtualMailManager.errors import DomainError as DomErr +from VirtualMailManager.Transport import Transport + MAILDIR_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' +_ = lambda x: x + class Domain(object): """Class to manage e-mail domains.""" - __slots__ = ('_basedir','_domaindir','_id','_name','_transport','_dbh') + __slots__ = ('_basedir', '_domaindir', '_id', '_name', '_transport', + '_dbh') + def __init__(self, dbh, domainname, basedir=None, transport=None): """Creates a new Domain instance. @@ -25,7 +32,7 @@ transport -- default vmm.cfg/misc/transport (str) """ self._dbh = dbh - self._name = VMM.VirtualMailManager.chkDomainname(domainname) + self._name = check_domainname(domainname) self._basedir = basedir if transport is not None: self._transport = Transport(self._dbh, transport=transport) @@ -34,8 +41,8 @@ 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) + raise DomErr(_(u"The domain “%s” is an alias domain.") % + self._name, DOMAIN_ALIAS_EXISTS) def _exists(self): """Checks if the domain already exists. @@ -119,14 +126,12 @@ else: hasAlias = False if hasUser and hasAlias: - raise VMMDE(_(u'There are accounts and aliases.'), - ERR.ACCOUNT_AND_ALIAS_PRESENT) + raise DomErr(_(u'There are accounts and aliases.'), + ACCOUNT_AND_ALIAS_PRESENT) elif hasUser: - raise VMMDE(_(u'There are accounts.'), - ERR.ACCOUNT_PRESENT) + raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT) elif hasAlias: - raise VMMDE(_(u'There are aliases.'), - ERR.ALIAS_PRESENT) + raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT) def save(self): """Stores the new domain in the database.""" @@ -134,14 +139,14 @@ 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) + VALUES (%s, %s, %s)", self._id, self._transport.id, 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) + raise DomErr(_(u'The domain “%s” already exists.') % self._name, + DOMAIN_EXISTS) def delete(self, delUser=False, delAlias=False): """Deletes the domain. @@ -153,13 +158,14 @@ 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)) + for tbl in ('alias', 'users', 'relocated', 'domain_name', + 'domain_data'): + dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._id)) self._dbh.commit() dbc.close() else: - raise VMMDE(_(u"The domain “%s” doesn't exist.") % self._name, - ERR.NO_SUCH_DOMAIN) + raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, + NO_SUCH_DOMAIN) def updateTransport(self, transport, force=False): """Sets a new transport for the domain. @@ -169,23 +175,23 @@ force -- True/False force new transport for all accounts (bool) """ if self._id > 0: - if transport == self._transport.getTransport(): + if transport == self._transport.transport: 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) + trsp.id, self._id) if dbc.rowcount > 0: self._dbh.commit() if force: dbc.execute("UPDATE users SET tid = %s WHERE gid = %s", - trsp.getID(), self._id) + trsp.id, 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) + raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, + NO_SUCH_DOMAIN) def getID(self): """Returns the ID of the domain.""" @@ -197,11 +203,11 @@ def getTransport(self): """Returns domain's transport.""" - return self._transport.getTransport() + return self._transport.transport def getTransportID(self): """Returns the ID from the domain's transport.""" - return self._transport.getID() + return self._transport.id def getInfo(self): """Returns a dictionary with information about the domain.""" @@ -215,8 +221,8 @@ info = dbc.fetchone() dbc.close() if info is None: - raise VMMDE(_(u"The domain “%s” doesn't exist.") % self._name, - ERR.NO_SUCH_DOMAIN) + raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, + NO_SUCH_DOMAIN) else: keys = ['gid', 'domainname', 'transport', 'domaindir', 'aliasdomains', 'accounts', 'aliases', 'relocated'] @@ -240,7 +246,7 @@ """Returns a list with all aliases from the domain.""" dbc = self._dbh.cursor() dbc.execute("SELECT DISTINCT address FROM alias WHERE gid = %s\ - ORDER BY address", self._id) + ORDER BY address", self._id) addresses = dbc.fetchall() dbc.close() aliases = [] @@ -276,9 +282,10 @@ aliasdomains = [aname[0] for aname in anames] return aliasdomains + def search(dbh, pattern=None, like=False): if pattern is not None and like is False: - pattern = VMM.VirtualMailManager.chkDomainname(pattern) + pattern = check_domainname(pattern) sql = 'SELECT gid, domainname, is_primary FROM domain_name' if pattern is None: pass @@ -309,3 +316,21 @@ del doms return order, domdict + +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 + + +del _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/EmailAddress.py --- a/VirtualMailManager/EmailAddress.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/EmailAddress.py Sun Mar 21 09:59:05 2010 +0000 @@ -2,75 +2,83 @@ # 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. +""" -from __main__ import re, ERR -from Exceptions import VMMEmailAddressException as VMMEAE -import VirtualMailManager as VMM +from VirtualMailManager import check_domainname, check_localpart +from VirtualMailManager.constants.ERROR import \ + DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID +from VirtualMailManager.errors import EmailAddressError as EAErr -RE_LOCALPART = """[^\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 +del _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Exceptions.py --- a/VirtualMailManager/Exceptions.py Sun Mar 21 09:17:26 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) diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Handler.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/Handler.py Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,691 @@ +# -*- 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, ace2idna, exec_ok +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, get_gid +from VirtualMailManager.EmailAddress import EmailAddress +from VirtualMailManager.errors import VMMError, AliasError, DomainError +from VirtualMailManager.Relocated import Relocated +from VirtualMailManager.ext.Postconf import Postconf + + +SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' +RE_DOMAIN_SRCH = """^[a-z0-9-\.]+$""" +RE_MBOX_NAMES = """^[\x20-\x25\x27-\x7E]*$""" + + +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() + self._scheme = self._Cfg.dget('misc.password_scheme') + self._postconf = Postconf(self._Cfg.dget('bin.postconf')) + + 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 _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 Handler._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 Handler._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 Handler._exists(dbh, sql) + relocatedExists = staticmethod(relocatedExists) + + 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, transport=None): + if transport is None: + transport = self._Cfg.dget('misc.transport') + self.__dbConnect() + return Domain(self._dbh, domainname, + self._Cfg.dget('misc.base_directory'), 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.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): + 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 == '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 cfgDget(self, option): + return self._Cfg.dget(option) + + def cfgPget(self, option): + return self._Cfg.pget(option) + + 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 DomainError(_(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 DomainError(_(u'Invalid argument: “%s”') % + force, ERR.INVALID_OPTION) + dom = self.__getDomain(domainname) + gid = dom.getID() + domdir = dom.getDir() + 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.getInfo() + if dominfo['domainname'].startswith('xn--'): + dominfo['domainname'] += ' (%s)' % 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 + if pattern.startswith('%') and pattern.endswith('%'): + domain = pattern[1:-1] + elif pattern.startswith('%'): + domain = pattern[1:] + elif pattern.endswith('%'): + domain = pattern[:-1] + if not re.match(RE_DOMAIN_SRCH, domain): + 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, e: + if e.code == ERR.NO_SUCH_ALIAS: + if Handler.accountExists(self._dbh, alias._addr): + raise VMMError( + _(u'There is already an account with address “%s”.') % + aliasaddress, ERR.ACCOUNT_EXISTS) + if Handler.relocatedExists(self._dbh, alias._addr): + raise VMMError(_(u'There is already a relocated user \ +with the address “%s”.') % + aliasaddress, ERR.RELOCATED_EXISTS) + raise + else: + 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 userByID(self, uid): + from Handler.Account import getAccountByID + self.__dbConnect() + return getAccountByID(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) + return relocated.get_info() + + 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() diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/MailLocation.py --- a/VirtualMailManager/MailLocation.py Sun Mar 21 09:17:26 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 - diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Relocated.py --- a/VirtualMailManager/Relocated.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Relocated.py Sun Mar 21 09:59:05 2010 +0000 @@ -2,102 +2,106 @@ # 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 - def _exists(self): + self.__load() + + 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]) + + 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 %r 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 %r 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 %r 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 _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/Transport.py --- a/VirtualMailManager/Transport.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/Transport.py Sun Mar 21 09:59:05 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__ = ('_id', '_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._id = tid self._loadByID() else: - self.__transport = transport + assert isinstance(transport, basestring) + self._transport = transport self._loadByName() + @property + def id(self): + """The transport's unique ID.""" + return self._id + + @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._id == other.id return NotImplemented def __ne__(self, other): if isinstance(other, self.__class__): - return self.__id != other.getID() + return self._id != other.id 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._id) 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._id = result[0] else: self._save() def _save(self): dbc = self._dbh.cursor() dbc.execute("SELECT nextval('transport_id')") - self.__id = dbc.fetchone()[0] - dbc.execute('INSERT INTO transport (tid, transport) VALUES (%s, %s)', - self.__id, self.__transport) + self._id = dbc.fetchone()[0] + dbc.execute('INSERT INTO transport VALUES (%s, %s)', self._id, + self._transport) self._dbh.commit() dbc.close() - - def getID(self): - """Returns the unique ID of the transport.""" - return self.__id - - def getTransport(self): - """Returns the value of transport, ex: 'dovecot:'""" - return self.__transport diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/VirtualMailManager.py --- a/VirtualMailManager/VirtualMailManager.py Sun Mar 21 09:17:26 2010 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,714 +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 == '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 - if pattern.startswith('%') and pattern.endswith('%'): - domain = pattern[1:-1] - elif pattern.startswith('%'): - domain = pattern[1:] - elif pattern.endswith('%'): - domain = pattern[:-1] - 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() diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/__init__.py --- a/VirtualMailManager/__init__.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/__init__.py Sun Mar 21 09:59:05 2010 +0000 @@ -1,15 +1,36 @@ # -*- 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 encodings.idna import ToASCII, ToUnicode + +from VirtualMailManager.constants.ERROR import \ + DOMAIN_INVALID, DOMAIN_TOO_LONG, LOCALPART_INVALID, LOCALPART_TOO_LONG, \ + 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', 'ace2idna', 'check_domainname', 'check_localpart', 'exec_ok', + 'expand_path', 'get_unicode', 'idn2ascii', 'is_dir', +] + # Try to set all of the locales according to the current # environment variables and get the character encoding. @@ -19,33 +40,110 @@ 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') +# there may be many domain and e-mail address checks +RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$") +RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]") + +gettext.install('vmm', '/usr/local/share/locale', unicode=1) + + +_ = lambda msg: msg + + +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 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 + + +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. -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) + """ + 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 + + +def idn2ascii(domainname): + """Converts the idn domain name `domainname` into punycode.""" + return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl]) + + +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 `VMMError`, if the domain name is too long or doesn't + look like a valid domain name (label.label.label). -__all__ = [ - # imported modules - 'os', 're', 'locale', - # version information from VERSION - '__author__', '__date__', '__version__', - # error codes - 'ERR', - # defined stuff - 'ENCODING', 'w_std', 'w_err' - ] -# EOF + """ + if not RE_DOMAIN.match(domainname): + domainname = idn2ascii(domainname) + if len(domainname) > 255: + raise VMMError(_(u'The domain name is too long'), DOMAIN_TOO_LONG) + if not RE_DOMAIN.match(domainname): + raise VMMError(_(u'The domain name %r is invalid') % domainname, + DOMAIN_INVALID) + return domainname + + +def check_localpart(localpart): + """Returns the validated local-part `localpart`. + + Throws a `VMMError` if the local-part is too long or contains + invalid characters. + + """ + if len(localpart) > 64: + raise VMMError(_(u'The local-part %r 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 VMMError(_(u"The local-part %(l_part)r contains invalid \ +characters: %(i_chars)s") % + {'l_part': localpart, 'i_chars': i_chars}, + LOCALPART_INVALID) + return localpart + + +del _ diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/cli/Config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/cli/Config.py Sun Mar 21 09:59:05 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() diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/cli/Handler.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/cli/Handler.py Sun Mar 21 09:59:05 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) diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/cli/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/cli/__init__.py Sun Mar 21 09:59:05 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() diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/constants/ERROR.py --- a/VirtualMailManager/constants/ERROR.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/constants/ERROR.py Sun Mar 21 09:59:05 2010 +0000 @@ -46,7 +46,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 diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/constants/VERSION.py --- a/VirtualMailManager/constants/VERSION.py Sun Mar 21 09:17:26 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 ' -RELDATE = '2009-09-09' -VERSION = '0.5.2' -__author__ = AUTHOR -__date__ = RELDATE -__version__ = VERSION -__all__ = ['__author__', '__date__', '__version__'] diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/constants/version.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/constants/version.py Sun Mar 21 09:59:05 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 ' +RELDATE = '2009-09-09' +VERSION = '0.5.2' +__author__ = AUTHOR +__date__ = RELDATE +__version__ = VERSION diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/errors.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/errors.py Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/ext/Postconf.py --- a/VirtualMailManager/ext/Postconf.py Sun Mar 21 09:17:26 2010 +0000 +++ b/VirtualMailManager/ext/Postconf.py Sun Mar 21 09:59:05 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(' = ') diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/maillocation.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/maillocation.py Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 VirtualMailManager/pycompat.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/pycompat.py Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/Makefile Sun Mar 21 09:59:05 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 ' where 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." diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/conf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/conf.py Sun Mar 21 09:59:05 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 +# " v 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 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/index.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/index.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,29 @@ +====================== +VirtualMailManager API +====================== + +:Author: Pascal Volk +: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` + diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,132 @@ +:mod:`VirtualMailManager` --- Initialization code and some functions +===================================================================== + +.. module:: VirtualMailManager + :synopsis: Initialization code and some functions + +.. moduleauthor:: Pascal Volk + +.. 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 "", line 1, in + 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' + >>> + diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_alias.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_alias.rst Sun Mar 21 09:59:05 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 + +.. 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 + `_) + :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. diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_config.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_config.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,275 @@ +:mod:`VirtualMailManager.Config` --- Simplified configuration access +====================================================================== + +.. module:: VirtualMailManager.Config + :synopsis: Simplified configuration access + +.. moduleauthor:: Pascal Volk + +.. 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. diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_constants_error.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_constants_error.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,227 @@ +:mod:`VirtualMailManager.constants.ERROR` --- Error codes +========================================================= + +.. module:: VirtualMailManager.constants.ERROR + :synopsis: VirtualMailManager's error codes + +.. moduleauthor:: Pascal Volk + +.. 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_emailaddress.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_emailaddress.rst Sun Mar 21 09:59:05 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 + +.. 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 + >>> diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_errors.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_errors.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,122 @@ +:mod:`VirtualMailManager.errors` --- Exception classes +====================================================== + +.. module:: VirtualMailManager.errors + :synopsis: Exception classes + +.. moduleauthor:: Pascal Volk + +.. 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. + diff -r 8c4df3dd2d2c -r 55503d63ba30 doc/source/vmm_relocated.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/source/vmm_relocated.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,60 @@ +:mod:`VirtualMailManager.Relocated` --- Handling of relocated users +=================================================================== + +.. module:: VirtualMailManager.Relocated + :synopsis: Handling of relocated users + +.. moduleauthor:: Pascal Volk + +.. 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. diff -r 8c4df3dd2d2c -r 55503d63ba30 install.sh --- a/install.sh Sun Mar 21 09:17:26 2010 +0000 +++ b/install.sh Sun Mar 21 09:59:05 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} diff -r 8c4df3dd2d2c -r 55503d63ba30 man/de/man1/vmm.1.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/de/man1/vmm.1.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,474 @@ +===== + vmm +===== + +----------------------------------------------------------------------------- +Kommandozeilenprogramm zur Verwaltung von E-Mail-Domains, -Konten und -Aliase +----------------------------------------------------------------------------- + +:Author: Pascal Volk +: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 diff -r 8c4df3dd2d2c -r 55503d63ba30 man/de/man5/vmm.cfg.5 --- a/man/de/man5/vmm.cfg.5 Sun Mar 21 09:17:26 2010 +0000 +++ b/man/de/man5/vmm.cfg.5 Sun Mar 21 09:59:05 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 +.\" Generated by docutils manpage writer. +.\" +. diff -r 8c4df3dd2d2c -r 55503d63ba30 man/de/man5/vmm.cfg.5.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/de/man5/vmm.cfg.5.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,404 @@ +========= + vmm.cfg +========= + +--------------------------- +Konfigurationsdatei für vmm +--------------------------- + +:Author: Pascal Volk +: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 + 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 man/man1/vmm.1.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/man1/vmm.1.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,445 @@ +===== + vmm +===== + +---------------------------------------------------------- +command line tool to manage email domains/accounts/aliases +---------------------------------------------------------- + +:Author: Pascal Volk +: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 diff -r 8c4df3dd2d2c -r 55503d63ba30 man/man5/vmm.cfg.5 --- a/man/man5/vmm.cfg.5 Sun Mar 21 09:17:26 2010 +0000 +++ b/man/man5/vmm.cfg.5 Sun Mar 21 09:59:05 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 +.\" Generated by docutils manpage writer. +.\" +. diff -r 8c4df3dd2d2c -r 55503d63ba30 man/man5/vmm.cfg.5.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/man5/vmm.cfg.5.rst Sun Mar 21 09:59:05 2010 +0000 @@ -0,0 +1,390 @@ +========= + vmm.cfg +========= + +-------------------------- +configuration file for vmm +-------------------------- + +:Author: Pascal Volk +: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 + .) 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 man/substitute_links.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/substitute_links.rst Sun Mar 21 09:59:05 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 + diff -r 8c4df3dd2d2c -r 55503d63ba30 man/substitute_links_1.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/substitute_links_1.rst Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 man/substitute_links_5.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/substitute_links_5.rst Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 po/de.po --- a/po/de.po Sun Mar 21 09:17:26 2010 +0000 +++ b/po/de.po Sun Mar 21 09:59:05 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 \n" +"Last-Translator: Pascal Volk \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" + diff -r 8c4df3dd2d2c -r 55503d63ba30 po/vmm.pot --- a/po/vmm.pot Sun Mar 21 09:17:26 2010 +0000 +++ b/po/vmm.pot Sun Mar 21 09:59:05 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 \n" "Language-Team: LANGUAGE \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" diff -r 8c4df3dd2d2c -r 55503d63ba30 setup.py --- a/setup.py Sun Mar 21 09:17:26 2010 +0000 +++ b/setup.py Sun Mar 21 09:59:05 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) diff -r 8c4df3dd2d2c -r 55503d63ba30 update_config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/update_config.py Sun Mar 21 09:59:05 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 diff -r 8c4df3dd2d2c -r 55503d63ba30 update_config_0.4.x-0.5.py --- a/update_config_0.4.x-0.5.py Sun Mar 21 09:17:26 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 - - diff -r 8c4df3dd2d2c -r 55503d63ba30 upgrade.sh --- a/upgrade.sh Sun Mar 21 09:17:26 2010 +0000 +++ b/upgrade.sh Sun Mar 21 09:59:05 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} diff -r 8c4df3dd2d2c -r 55503d63ba30 vmm --- a/vmm Sun Mar 21 09:17:26 2010 +0000 +++ b/vmm Sun Mar 21 09:59:05 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() diff -r 8c4df3dd2d2c -r 55503d63ba30 vmm.cfg --- a/vmm.cfg Sun Mar 21 09:17:26 2010 +0000 +++ b/vmm.cfg Sun Mar 21 09:59:05 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: +; +; 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