--- a/VirtualMailManager/__init__.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/__init__.py Sun Jan 06 00:09:47 2013 +0000
@@ -32,4 +32,4 @@
locale.setlocale(locale.LC_ALL, 'C')
ENCODING = locale.nl_langinfo(locale.CODESET)
-gettext.install('vmm', '/usr/local/share/locale', unicode=1)
+gettext.install('vmm', '/usr/local/share/locale')
--- a/VirtualMailManager/account.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/account.py Sun Jan 06 00:09:47 2013 +0000
@@ -57,7 +57,7 @@
# TP: Hm, what “quotation marks” should be used?
# If you are unsure have a look at:
# http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
- raise AErr(_(u"The domain '%s' does not exist.") %
+ raise AErr(_("The domain '%s' does not exist.") %
self._addr.domainname, NO_SUCH_DOMAIN)
self._uid = 0
self._mail = None
@@ -69,7 +69,7 @@
self._new = True
self._load()
- def __nonzero__(self):
+ def __bool__(self):
"""Returns `True` if the Account is known, `False` if it's new."""
return not self._new
@@ -86,11 +86,7 @@
self._uid, _mid, _qid, _ssid, _tid, _note = result
def load_helper(ctor, own, field, dbresult):
- # Py25: cur = None if own is None else getattr(own, field)
- if own is None:
- cur = None
- else:
- cur = getattr(own, field)
+ cur = None if own is None else getattr(own, field)
if cur != dbresult:
kwargs = {field: dbresult}
if dbresult is None:
@@ -120,8 +116,8 @@
information in the database.
"""
if maillocation.dovecot_version > cfg_dget('misc.dovecot_version'):
- raise AErr(_(u"The mailbox format '%(mbfmt)s' requires Dovecot "
- u">= v%(version)s.") % {
+ raise AErr(_("The mailbox format '%(mbfmt)s' requires Dovecot "
+ ">= v%(version)s.") % {
'mbfmt': maillocation.mbformat,
'version': version_str(maillocation.dovecot_version)},
INVALID_MAIL_LOCATION)
@@ -137,7 +133,7 @@
`column` : basestring
Name of the table column. Currently: qid, ssid and tid
- `value` : long
+ `value` : int
The referenced key
"""
if column not in ('qid', 'ssid', 'tid'):
@@ -163,7 +159,7 @@
"""Raise an AccountError if the Account is new - not yet saved in the
database."""
if self._new:
- raise AErr(_(u"The account '%s' does not exist.") % self._addr,
+ raise AErr(_("The account '%s' does not exist.") % self._addr,
NO_SUCH_ACCOUNT)
@property
@@ -219,10 +215,10 @@
The password for the new Account.
"""
if not self._new:
- raise AErr(_(u"The account '%s' already exists.") % self._addr,
+ raise AErr(_("The account '%s' already exists.") % self._addr,
ACCOUNT_EXISTS)
- if not isinstance(password, basestring) or not password:
- raise AErr(_(u"Could not accept password: '%s'") % password,
+ if not isinstance(password, str) or not password:
+ raise AErr(_("Could not accept password: '%s'") % password,
ACCOUNT_MISSING_PASSWORD)
self._passwd = password
@@ -234,36 +230,29 @@
`note` : basestring or None
The note, or None to remove
"""
- assert note is None or isinstance(note, basestring)
+ assert note is None or isinstance(note, str)
self._note = note
def save(self):
"""Save the new Account in the database."""
if not self._new:
- raise AErr(_(u"The account '%s' already exists.") % self._addr,
+ raise AErr(_("The account '%s' already exists.") % self._addr,
ACCOUNT_EXISTS)
if not self._passwd:
- raise AErr(_(u"No password set for account: '%s'") % self._addr,
+ raise AErr(_("No password set for account: '%s'") % self._addr,
ACCOUNT_MISSING_PASSWORD)
self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
directory=cfg_dget('mailbox.root')))
dbc = self._dbh.cursor()
- qid = ssid = tid = None
- if self._qlimit:
- qid = self._qlimit.qid
- if self._services:
- ssid = self._services.ssid
- if self._transport:
- tid = self._transport.tid
dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
'qid, ssid, tid, note) '
'VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)',
(self._addr.localpart,
pwhash(self._passwd, user=self._addr), self._uid,
- self._domain.gid, self._mail.mid, qid, ssid, tid,
-# self._qlimit.qid if self._qlimit else None,
-# self._services.ssid if self._services else None,
-# self._transport.tid if self._transport else None,
+ self._domain.gid, self._mail.mid,
+ self._qlimit.qid if self._qlimit else None,
+ self._services.ssid if self._services else None,
+ self._transport.tid if self._transport else None,
self._note))
self._dbh.commit()
dbc.close()
@@ -282,7 +271,7 @@
The new value of the attribute.
"""
if field not in ('name', 'password', 'note'):
- raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
+ raise AErr(_("Unknown field: '%s'") % field, INVALID_ARGUMENT)
self._chk_state()
dbc = self._dbh.cursor()
if field == 'password':
@@ -304,8 +293,8 @@
the new quota limit of the domain.
"""
if cfg_dget('misc.dovecot_version') < 0x10102f00:
- raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
- u'Dovecot >= v1.1.2.'), VMM_ERROR)
+ raise VMMError(_('PostgreSQL-based dictionary quota requires '
+ 'Dovecot >= v1.1.2.'), VMM_ERROR)
self._chk_state()
if quotalimit == self._qlimit:
return
@@ -364,7 +353,7 @@
fmt = format_domain_default
ret = {}
- for service, state in services.iteritems():
+ for service, state in services.items():
# TP: A service (e.g. pop3 or imap) may be enabled/usable or
# disabled/unusable for a user.
ret[service] = fmt((_('disabled'), _('enabled'))[state])
@@ -387,7 +376,7 @@
info = dbc.fetchone()
dbc.close()
if info:
- info = dict(zip(('name', 'uq_bytes', 'uq_messages'), info))
+ info = dict(list(zip(('name', 'uq_bytes', 'uq_messages'), info)))
info.update(self._get_info_serviceset())
info['address'] = self._addr
info['gid'] = self._domain.gid
@@ -406,7 +395,7 @@
info['uid'] = self._uid
return info
# nearly impossible‽
- raise AErr(_(u"Could not fetch information for account: '%s'") %
+ raise AErr(_("Could not fetch information for account: '%s'") %
self._addr, NO_SUCH_ACCOUNT)
def get_aliases(self):
@@ -450,8 +439,8 @@
a_count = self._count_aliases()
if a_count > 0:
dbc.close()
- raise AErr(_(u"There are %(count)d aliases with the "
- u"destination address '%(address)s'.") %
+ raise AErr(_("There are %(count)d aliases with the "
+ "destination address '%(address)s'.") %
{'count': a_count, 'address': self._addr},
ALIAS_PRESENT)
dbc.execute('DELETE FROM users WHERE uid = %s', (self._uid,))
@@ -471,17 +460,17 @@
Argument:
- `uid` : long
+ `uid` : int
The Account unique ID.
`dbh` : pyPgSQL.PgSQL.Connection
a database connection for the database access.
"""
try:
- uid = long(uid)
+ uid = int(uid)
except ValueError:
- raise AErr(_(u'UID must be an int/long.'), INVALID_ARGUMENT)
+ raise AErr(_('UID must be an integer.'), INVALID_ARGUMENT)
if uid < 1:
- raise AErr(_(u'UID must be greater than 0.'), INVALID_ARGUMENT)
+ raise AErr(_('UID must be greater than 0.'), INVALID_ARGUMENT)
dbc = dbh.cursor()
dbc.execute("SELECT local_part||'@'|| domain_name.domainname AS address, "
"uid, users.gid, note FROM users LEFT JOIN domain_name ON "
@@ -490,9 +479,9 @@
info = dbc.fetchone()
dbc.close()
if not info:
- raise AErr(_(u"There is no account with the UID: '%d'") % uid,
+ raise AErr(_("There is no account with the UID: '%d'") % uid,
NO_SUCH_ACCOUNT)
- info = dict(zip(('address', 'uid', 'gid', 'note'), info))
+ info = dict(list(zip(('address', 'uid', 'gid', 'note'), info)))
return info
del _, cfg_dget
--- a/VirtualMailManager/alias.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/alias.py Sun Jan 06 00:09:47 2013 +0000
@@ -13,7 +13,6 @@
EmailAddress, DestinationEmailAddress as DestAddr
from VirtualMailManager.errors import AliasError as AErr
from VirtualMailManager.ext.postconf import Postconf
-from VirtualMailManager.pycompat import all
from VirtualMailManager.constants import \
ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
@@ -32,7 +31,7 @@
self._dbh = dbh
self._gid = get_gid(self._dbh, self._addr.domainname)
if not self._gid:
- raise AErr(_(u"The domain '%s' does not exist.") %
+ raise AErr(_("The domain '%s' does not exist.") %
self._addr.domainname, NO_SUCH_DOMAIN)
self._dests = []
@@ -52,20 +51,20 @@
def _check_expansion(self, count_new):
"""Checks the current expansion limit of the alias."""
postconf = Postconf(cfg_dget('bin.postconf'))
- limit = long(postconf.read('virtual_alias_expansion_limit'))
+ limit = int(postconf.read('virtual_alias_expansion_limit'))
dcount = len(self._dests)
failed = False
if dcount == limit or dcount + count_new > limit:
failed = True
errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
+"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
Currently this alias expands into %(count)i/%(limit)i recipients.
%(count_new)i additional destination(s) will render this alias unusable.
Hint: Increase Postfix' virtual_alias_expansion_limit""")
elif dcount > limit:
failed = True
errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
+"""Cannot add %(count_new)i new destination(s) to alias '%(address)s'.
This alias already exceeds its expansion limit (%(count)i/%(limit)i).
So its unusable, all messages addressed to this alias will be bounced.
Hint: Delete some destination addresses.""")
@@ -152,7 +151,7 @@
if not warnings is None:
warnings.append(self._addr)
if not self._dests:
- raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+ raise AErr(_("The alias '%s' does not exist.") % self._addr,
NO_SUCH_ALIAS)
unknown = destinations.difference(set(self._dests))
if unknown:
@@ -160,8 +159,8 @@
if not warnings is None:
warnings.extend(unknown)
if not destinations:
- raise AErr(_(u"No suitable destinations left to remove from alias "
- u"'%s'.") % self._addr, NO_SUCH_ALIAS)
+ raise AErr(_("No suitable destinations left to remove from alias "
+ "'%s'.") % self._addr, NO_SUCH_ALIAS)
self._delete(destinations)
for destination in destinations:
self._dests.remove(destination)
@@ -169,14 +168,14 @@
def get_destinations(self):
"""Returns an iterator for all destinations of the alias."""
if not self._dests:
- raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+ raise AErr(_("The alias '%s' does not exist.") % self._addr,
NO_SUCH_ALIAS)
return iter(self._dests)
def delete(self):
"""Deletes the alias with all its destinations."""
if not self._dests:
- raise AErr(_(u"The alias '%s' does not exist.") % self._addr,
+ raise AErr(_("The alias '%s' does not exist.") % self._addr,
NO_SUCH_ALIAS)
self._delete()
del self._dests[:]
--- a/VirtualMailManager/aliasdomain.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/aliasdomain.py Sun Jan 06 00:09:47 2013 +0000
@@ -47,7 +47,7 @@
dbc.close()
if result:
if result[1]:
- raise ADErr(_(u"The domain '%s' is a primary domain.") %
+ raise ADErr(_("The domain '%s' is a primary domain.") %
self._name, ALIASDOMAIN_ISDOMAIN)
self._gid = result[0]
@@ -66,13 +66,13 @@
def save(self):
"""Stores information about the new AliasDomain in the database."""
if self._gid > 0:
- raise ADErr(_(u"The alias domain '%s' already exists.") %
+ raise ADErr(_("The alias domain '%s' already exists.") %
self._name, ALIASDOMAIN_EXISTS)
if not self._domain:
- raise ADErr(_(u'No destination domain set for the alias domain.'),
+ raise ADErr(_('No destination domain set for the alias domain.'),
ALIASDOMAIN_NO_DOMDEST)
if self._domain.gid < 1:
- raise ADErr(_(u"The target domain '%s' does not exist.") %
+ raise ADErr(_("The target domain '%s' does not exist.") %
self._domain.name, NO_SUCH_DOMAIN)
dbc = self._dbh.cursor()
dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary) '
@@ -85,7 +85,7 @@
"""Returns a dict (keys: "alias" and "domain") with the names of the
AliasDomain and its primary domain."""
if self._gid < 1:
- raise ADErr(_(u"The alias domain '%s' does not exist.") %
+ raise ADErr(_("The alias domain '%s' does not exist.") %
self._name, NO_SUCH_ALIASDOMAIN)
dbc = self._dbh.cursor()
dbc.execute('SELECT domainname FROM domain_name WHERE gid = %s AND '
@@ -95,25 +95,25 @@
if domain:
return {'alias': self._name, 'domain': domain[0]}
else: # an almost unlikely case, isn't it?
- raise ADErr(_(u'There is no primary domain for the alias domain '
- u"'%s'.") % self._name, NO_SUCH_DOMAIN)
+ raise ADErr(_('There is no primary domain for the alias domain '
+ "'%s'.") % self._name, NO_SUCH_DOMAIN)
def switch(self):
"""Switch the destination of the AliasDomain to the new destination,
set with the method `set_destination()`.
"""
if not self._domain:
- raise ADErr(_(u'No destination domain set for the alias domain.'),
+ raise ADErr(_('No destination domain set for the alias domain.'),
ALIASDOMAIN_NO_DOMDEST)
if self._domain.gid < 1:
- raise ADErr(_(u"The target domain '%s' does not exist.") %
+ raise ADErr(_("The target domain '%s' does not exist.") %
self._domain.name, NO_SUCH_DOMAIN)
if self._gid < 1:
- raise ADErr(_(u"The alias domain '%s' does not exist.") %
+ raise ADErr(_("The alias domain '%s' does not exist.") %
self._name, NO_SUCH_ALIASDOMAIN)
if self._gid == self._domain.gid:
- raise ADErr(_(u"The alias domain '%(alias)s' is already assigned "
- u"to the domain '%(domain)s'.") %
+ raise ADErr(_("The alias domain '%(alias)s' is already assigned "
+ "to the domain '%(domain)s'.") %
{'alias': self._name, 'domain': self._domain.name},
ALIASDOMAIN_EXISTS)
dbc = self._dbh.cursor()
@@ -130,7 +130,7 @@
Raises an AliasDomainError if the AliasDomain doesn't exist.
"""
if self._gid < 1:
- raise ADErr(_(u"The alias domain '%s' does not exist.") %
+ raise ADErr(_("The alias domain '%s' does not exist.") %
self._name, NO_SUCH_ALIASDOMAIN)
dbc = self._dbh.cursor()
dbc.execute('DELETE FROM domain_name WHERE domainname = %s AND NOT '
--- a/VirtualMailManager/catchall.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/catchall.py Sun Jan 06 00:09:47 2013 +0000
@@ -23,7 +23,6 @@
EmailAddress, DestinationEmailAddress as DestAddr
from VirtualMailManager.errors import AliasError as AErr
from VirtualMailManager.ext.postconf import Postconf
-from VirtualMailManager.pycompat import all
from VirtualMailManager.constants import \
ALIAS_EXCEEDS_EXPANSION_LIMIT, NO_SUCH_ALIAS, NO_SUCH_DOMAIN
@@ -41,7 +40,7 @@
self._dbh = dbh
self._gid = get_gid(self._dbh, self.domain)
if not self._gid:
- raise AErr(_(u"The domain '%s' does not exist.") %
+ raise AErr(_("The domain '%s' does not exist.") %
self.domain, NO_SUCH_DOMAIN)
self._dests = []
@@ -60,13 +59,13 @@
def _check_expansion(self, count_new):
"""Checks the current expansion limit of the alias."""
postconf = Postconf(cfg_dget('bin.postconf'))
- limit = long(postconf.read('virtual_alias_expansion_limit'))
+ limit = int(postconf.read('virtual_alias_expansion_limit'))
dcount = len(self._dests)
failed = False
if dcount == limit or dcount + count_new > limit:
failed = True
errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to catch-all alias for
+"""Cannot add %(count_new)i new destination(s) to catch-all alias for
domain '%(domain)s'. Currently this alias expands into %(count)i/%(limit)i
recipients. %(count_new)i additional destination(s) will render this alias
unusable.
@@ -74,7 +73,7 @@
elif dcount > limit:
failed = True
errmsg = _(
-u"""Cannot add %(count_new)i new destination(s) to catch-all alias for domain
+"""Cannot add %(count_new)i new destination(s) to catch-all alias for domain
'%(domain)s'. This alias already exceeds its expansion limit \
(%(count)i/%(limit)i).
So its unusable, all messages addressed to this alias will be bounced.
@@ -148,16 +147,16 @@
if not warnings is None:
assert isinstance(warnings, list)
if not self._dests:
- raise AErr(_(u"There are no catch-all aliases defined for "
- u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+ raise AErr(_("There are no catch-all aliases defined for "
+ "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
unknown = destinations.difference(set(self._dests))
if unknown:
destinations.intersection_update(set(self._dests))
if not warnings is None:
warnings.extend(unknown)
if not destinations:
- raise AErr(_(u"No suitable destinations left to remove from the "
- u"catch-all alias of domain '%s'.") % self._domain,
+ raise AErr(_("No suitable destinations left to remove from the "
+ "catch-all alias of domain '%s'.") % self._domain,
NO_SUCH_ALIAS)
self._delete(destinations)
for destination in destinations:
@@ -166,15 +165,15 @@
def get_destinations(self):
"""Returns an iterator for all destinations of the catchall alias."""
if not self._dests:
- raise AErr(_(u"There are no catch-all aliases defined for "
- u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+ raise AErr(_("There are no catch-all aliases defined for "
+ "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
return iter(self._dests)
def delete(self):
"""Deletes all catchall destinations for the domain."""
if not self._dests:
- raise AErr(_(u"There are no catch-all aliases defined for "
- u"domain '%s'.") % self._domain, NO_SUCH_ALIAS)
+ raise AErr(_("There are no catch-all aliases defined for "
+ "domain '%s'.") % self._domain, NO_SUCH_ALIAS)
self._delete()
del self._dests[:]
--- a/VirtualMailManager/cli/__init__.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/__init__.py Sun Jan 06 00:09:47 2013 +0000
@@ -19,19 +19,20 @@
from VirtualMailManager.errors import VMMError
-__all__ = ('prog', 'get_winsize', 'read_pass', 'w_err', 'w_std')
+__all__ = ('get_winsize', 'read_pass', 'w_err', 'w_std')
_ = lambda msg: msg
_std_write = os.sys.stdout.write
_err_write = os.sys.stderr.write
-prog = os.path.basename(os.sys.argv[0])
def w_std(*args):
"""Writes a line for each arg of *args*, encoded in the current
ENCODING, to stdout.
"""
- _std_write('\n'.join(a.encode(ENCODING, 'replace') for a in args) + '\n')
+ _std_write('\n'.join(arg.encode(ENCODING, 'replace').decode(ENCODING,
+ 'replace')
+ for arg in args) + '\n')
def w_err(code, *args):
@@ -40,7 +41,9 @@
This function optionally interrupts the program execution if *code*
does not equal to 0. *code* will be used as the system exit status.
"""
- _err_write('\n'.join(a.encode(ENCODING, 'replace') for a in args) + '\n')
+ _err_write('\n'.join(arg.encode(ENCODING, 'replace').decode(ENCODING,
+ 'replace')
+ for arg in args) + '\n')
if code:
os.sys.exit(code)
@@ -75,24 +78,24 @@
Throws a VMMError after the third failure.
"""
# TP: Please preserve the trailing space.
- readp_msg0 = _(u'Enter new password: ').encode(ENCODING, 'replace')
+ readp_msg0 = _('Enter new password: ')
# TP: Please preserve the trailing space.
- readp_msg1 = _(u'Retype new password: ').encode(ENCODING, 'replace')
+ readp_msg1 = _('Retype new password: ')
mismatched = True
failures = 0
while mismatched:
if failures > 2:
- raise VMMError(_(u'Too many failures - try again later.'),
+ raise VMMError(_('Too many failures - try again later.'),
VMM_TOO_MANY_FAILURES)
clear0 = getpass(prompt=readp_msg0)
clear1 = getpass(prompt=readp_msg1)
if clear0 != clear1:
failures += 1
- w_err(0, _(u'Sorry, passwords do not match.'))
+ w_err(0, _('Sorry, passwords do not match.'))
continue
if not clear0:
failures += 1
- w_err(0, _(u'Sorry, empty passwords are not permitted.'))
+ w_err(0, _('Sorry, empty passwords are not permitted.'))
continue
mismatched = False
return clear0
--- a/VirtualMailManager/cli/clihelp.py Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2012 - 2013, Pascal Volk
-# See COPYING for distribution information.
-"""
- VirtualMailManager.cli.vmmhelp
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- Virtual Mail Manager's command line help.
-"""
-
-_ = lambda msg: msg
-
-help_msgs = {
-# TP: There are some words enclosed within angle brackets '<'word'>'. They
-# are used to indicate replaceable arguments. Please do not translate them.
-#
-# The descriptions of subcommands may contain the both keywords 'domain'
-# and 'force', enclosed within single quotes. Please keep them as they are.
-#
- # TP: description of subcommand configget
- 'configget': (_(u"""This subcommand is used to display the actual value
-of the given configuration <option>."""),),
- # TP: description of subcommand configset
- 'configset': (_(u"""Use this subcommand to set or update a single
-configuration option's value. <option> is the configuration option, <value>
-is the <option>'s new value."""),
-_(u"""Note: This subcommand will create a new vmm.cfg without any comments.
-Your current configuration file will be backed as vmm.cfg.bak."""),),
- # TP: description of subcommand configure
- 'configure': (_(u"""Starts the interactive configuration for all
-configuration sections."""),
-_(u"""In this process the currently set value of each option will be displayed
-in square brackets. If no value is configured, the default value of each
-option will be displayed in square brackets. Press the return key, to accept
-the displayed value."""),
-_(u"""If the optional argument <section> is given, only the configuration
-options from the given section will be displayed and will be configurable.
-The following sections are available:
-"""),
-""" account, bin, database, domain, mailbox, misc""",
-_(u"""All configuration options are described in vmm.cfg(5)."""),
-_(u"""Note: This subcommand will create a new vmm.cfg without any comments.
-Your current configuration file will be backed as vmm.cfg.bak."""),),
- # TP: description of subcommand getuser
- 'getuser': (_(u"""If only the <uid> is available, for example from process
-list, the subcommand getuser will show the user's address."""),),
- # TP: description of subcommand listaddresses
- 'listaddresses': (_(u"""This command lists all defined addresses.
-Addresses belonging to alias-domains are prefixed with a '-', addresses of
-regular domains with a '+'. Additionally, the letters 'u', 'a', and 'r'
-indicate the type of each address: user, alias and relocated respectively.
-The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
- # TP: description of subcommand listaliases
- 'listaliases': (_(u"""This command lists all defined aliases. Aliases
-belonging to alias-domains are prefixed with a '-', addresses of regular
-domains with a '+'. The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
- # TP: description of subcommand listdomains
- 'listdomains': (_(u"""This subcommand lists all available domains. All
-domain names will be prefixed either with `[+]', if the domain is a primary
-domain, or with `[-]', if it is an alias domain name. The output can be
-limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
- # TP: description of subcommand listpwschemes
- 'listpwschemes': (_(u"""This subcommand lists all password schemes which
-could be used in the vmm.cfg as value of the misc.password_scheme option.
-The output varies, depending on the used Dovecot version and the system's
-libc."""),
-_(u"""When your Dovecot installation isn't too old, you will see additionally
-a few usable encoding suffixes. One of them can be appended to the password
-scheme."""),),
- # TP: description of subcommand listrelocated
- 'listrelocated': (_(u"""This command lists all defined relocated addresses.
-Relocated entries belonging to alias-domains are prefixed with a '-', addresses
-of regular domains with a '+'. The output can be limited with an optional
-<pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the <pattern>."""),),
- # TP: description of subcommand listusers
- 'listusers': (_(u"""This command lists all user accounts. User accounts
-belonging to alias-domains are prefixed with a '-', addresses of regular
-domains with a '+'. The output can be limited with an optional <pattern>."""),
-_(u"""To perform a wild card search, the % character can be used at the start
-and/or the end of the pattern."""),),
- # TP: description of subcommand version
- 'version': (_(u"""Prints vmm's version and copyright information to stdout.
-After this vmm exits."""),),
- # TP: description of subcommand domainadd
- 'domainadd': (_(u"""Adds the new domain into the database and creates the
-domain directory."""),
-_(u"""If the optional argument <transport> is given, it will override the
-default transport (domain.transport) from vmm.cfg. The specified <transport>
-will be the default transport for all new accounts in this domain."""),
-_(u"""Configuration-related behavior:"""),
-u""" * domain.auto_postmaster""",
-_(u"""When that option is set to true (default) vmm will automatically create
-the postmaster account for the new domain and prompt for postmaster@<fqdn>'s
-password."""),
-u""" * account.random_password""",
-_(u"""When the value of that option is also set to true, vmm will automatically
-create the postmaster account for the new domain and print the generated
-postmaster password to stdout."""),),
- # TP: description of subcommand domaindelete
- 'domaindelete': (_(u"""This subcommand deletes the domain specified by
-<fqdn>."""),
-_(u"""If there are accounts, aliases and/or relocated users assigned to the
-given domain, vmm will abort the requested operation and show an error
-message. If you know, what you are doing, you can specify the optional keyword
-'force'."""),
-_(u"""If you really always know what you are doing, edit your vmm.cfg and set
-the option domain.force_deletion to true."""),),
- # TP: description of subcommand domaininfo
- 'domaininfo': (_(u"""This subcommand shows some information about the
-given domain."""),
-_(u"""For a more detailed information about the domain the optional argument
-<details> can be specified. A possible <details> value can be one of the
-following six keywords:"""),
-""" accounts, aliasdomains, aliases, catchall, relocated, full""",),
- # TP: description of subcommand domainquota
- 'domainquota': (_(u"""This subcommand is used to configure a new quota
-limit for the accounts of the domain - not for the domain itself."""),
-_(u"""The default quota limit for accounts is defined in the vmm.cfg
-(domain.quota_bytes and domain.quota_messages)."""),
-_(u"""The new quota limit will affect only those accounts for which the limit
-has not been overridden. If you want to restore the default to all accounts,
-you may pass the keyword 'force'. When the argument <messages> was omitted the
-default number of messages 0 (zero) will be applied."""),),
- # TP: description of subcommand domainservices
- 'domainservices': (_(u"""To define which services could be used by the
-users of the domain — with the given <fqdn> — use this subcommand."""),
-_(u"""Each specified <service> will be enabled/usable. All other services
-will be deactivated/unusable. Possible <service> names are:"""),
-u""" imap, pop3, sieve, smtp""",
-_(u"""The new service set will affect only those accounts for which the set has
-not been overridden. If you want to restore the default to all accounts, you
-may pass the keyword 'force'."""),),
- # TP: description of subcommand domaintransport
- 'domaintransport': (_(u"""A new transport for the indicated domain can be
-set with this subcommand."""),
-_(u"""The new transport will affect only those accounts for which the transport
-has not been overridden. If you want to restore the default to all accounts,
-you may pass the keyword 'force'."""),),
- # TP: description of subcommand domainnote
- 'domainnote': (_(u"""With this subcommand, it is possible to attach a
-note to the specified domain. Without an argument, an existing note is
-removed."""),),
- # TP: description of subcommand aliasdomainadd
- 'aliasdomainadd': (_(u"""This subcommand adds the new alias domain
-(<fqdn>) to the destination <domain> that should be aliased."""),),
- # TP: description of subcommand aliasdomaindelete
- 'aliasdomaindelete': (_(u"""Use this subcommand if the alias domain
-<fqdn> should be removed."""),),
- # TP: description of subcommand aliasdomaininfo
- 'aliasdomaininfo': (_(u"""This subcommand shows to which domain the alias
-domain <fqdn> is assigned to."""),),
- # TP: description of subcommand aliasdomainswitch
- 'aliasdomainswitch': (_(u"""If the destination of the existing alias
-domain <fqdn> should be switched to another <destination> use this
-subcommand."""),),
- # TP: description of subcommand useradd
- 'useradd': (_(u"""Use this subcommand to create a new e-mail account for
-the given <address>."""),
-_(u"""If the <password> is not provided, vmm will prompt for it interactively.
-When no <password> is provided and account.random_password is set to true, vmm
-will generate a random password and print it to stdout after the account has
-been created."""),),
- # TP: description of subcommand userdelete
- 'userdelete': (_(u"""Use this subcommand to delete the account with the
-given <address>."""),
-_(u"""If there are one or more aliases with an identical destination address,
-vmm will abort the requested operation and show an error message. To prevent
-this, specify the optional keyword 'force'."""),),
- # TP: description of subcommand userinfo
- 'userinfo': (_(u"""This subcommand displays some information about the
-account specified by <address>."""),
-_(u"""If the optional argument <details> is given some more information will be
-displayed. Possible values for <details> are:"""),
-u""" aliases, du. full""",),
- # TP: description of subcommand username
- 'username': (_(u"""The user's real <name> can be set/updated with this
-subcommand."""),
-_(u"""If no <name> is given, the value stored for the account is erased."""),
-),
- # TP: description of subcommand userpassword
- 'userpassword': (_(u"""The password of an account can be updated with this
-subcommand."""),
-_(u"""If no <password> was provided, vmm will prompt for it interactively."""),
-),
- # TP: description of subcommand usernote
- 'usernote': (_(u"""With this subcommand, it is possible to attach a note
-to the specified account. Without an argument, an existing note is
-removed."""),),
- # TP: description of subcommand userquota
- 'userquota': (_(u"""This subcommand is used to set a new quota limit for
-the given account."""),
-_(u"""When the argument <messages> was omitted the default number of messages
-0 (zero) will be applied."""),
-_(u"""Instead of <storage> pass the keyword 'domain' to remove the
-account-specific override, causing the domain's value to be in effect."""),),
- # TP: description of subcommand userservices
- 'userservices': (_(u"""To grant a user access to the specified services,
-use this command."""),
-_(u"""All omitted services will be deactivated/unusable for the user with the
-given <address>."""),
-_(u"""Instead of <service> pass 'domain' to remove the account-specific
-override, causing the domain's value to be in effect."""),),
- # TP: description of subcommand usertransport
- 'usertransport': (_(u"""A different <transport> for an account can be
-specified with this subcommand."""),
-_(u"""Instead of <transport> pass 'domain' to remove the account-specific
-override, causing the domain's value to be in effect."""),),
- # TP: description of subcommand aliasadd
- 'aliasadd': (_(u"""This subcommand is used to create a new alias
-<address> with one or more <destination> addresses."""),
-_(u"""Within the destination address, the placeholders '%n', '%d', and '%='
-will be replaced by the local part, the domain, or the email address with '@'
-replaced by '=' respectively. In combination with alias domains, this enables
-domain-specific destinations."""),),
- # TP: description of subcommand aliasdelete
- 'aliasdelete': (_(u"""This subcommand is used to delete one or multiple
-<destination>s from the alias with the given <address>."""),
-_(u"""When no <destination> address was specified the alias with all its
-destinations will be deleted."""),),
- # TP: description of subcommand aliasinfo
- 'aliasinfo': (_(u"""Information about the alias with the given <address>
-can be displayed with this subcommand."""),),
- # TP: description of subcommand relocatedadd
- 'relocatedadd': (_(u"""A new relocated user can be created with this
-subcommand."""),
-_(u"""<address> is the user's ex-email address, for example
-b.user@example.com, and <newaddress> points to the new email address where
-the user can be reached."""),),
- # TP: description of subcommand relocatedinfo
- 'relocatedinfo': (_(u"""This subcommand shows the new address of the
-relocated user with the given <address>."""),),
- # TP: description of subcommand relocateddelete
- 'relocateddelete': (_(u"""Use this subcommand in order to delete the
-relocated user with the given <address>."""),),
- # TP: description of subcommand catchalladd
- 'catchalladd': (_(u"""This subcommand allows to specify destination
-addresses for a domain, which shall receive mail addressed to unknown
-local-parts within that domain. Those catch-all aliases hence "catch all" mail
-to any address in the domain (unless a more specific alias, mailbox or
-relocated user exists)."""),
-_(u"""WARNING: Catch-all addresses can cause mail server flooding because
-spammers like to deliver mail to all possible combinations of names, e.g.
-to all addresses between abba@example.org and zztop@example.org."""),),
- # TP: description of subcommand catchallinfo
- 'catchallinfo': (_(u"""This subcommand displays information about catch-all
-aliases defined for the domain <fqdn>."""),),
- # TP: description of subcommand catchalldelete
- 'catchalldelete': (_(u"""With this subcommand, catch-all aliases defined
-for a domain can be removed, either all of them, or those <destination>s which
-were specified explicitly."""),),
-}
-
-del _
--- a/VirtualMailManager/cli/config.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/config.py Sun Jan 06 00:09:47 2013 +0000
@@ -8,8 +8,9 @@
Adds some interactive stuff to the Config class.
"""
-from ConfigParser import RawConfigParser
+from configparser import RawConfigParser
from shutil import copy2
+from string import Template
from VirtualMailManager import ENCODING
from VirtualMailManager.config import Config, ConfigValueError, LazyConfig
@@ -29,33 +30,35 @@
def configure(self, sections):
"""Interactive method for configuring all options of the given
iterable ``sections`` object."""
- input_fmt = _(u'Enter new value for option %(option)s '
- u'[%(current_value)s]: ')
+ input_tpl = Template(_('Enter new value for option $option '
+ '[$current_value]: '))
failures = 0
- w_std(_(u'Using configuration file: %s\n') % self._cfg_filename)
+ w_std(_('Using configuration file: %s\n') % self._cfg_filename)
for section in sections:
- w_std(_(u"* Configuration section: '%s'") % section)
+ w_std(_("* Configuration section: '%s'") % section)
for opt, val in self.items(section):
failures = 0
while True:
- newval = raw_input(input_fmt.encode(ENCODING, 'replace') %
- {'option': opt, 'current_value': val})
+ if isinstance(val, str):
+ val = val.encode(ENCODING, 'replace').decode(ENCODING)
+ newval = input(input_tpl.substitute(option=opt,
+ current_value=val))
if newval and newval != val:
try:
LazyConfig.set(self, '%s.%s' % (section, opt),
newval)
break
- except (ValueError, ConfigValueError, VMMError), err:
- w_err(0, _(u'Warning: %s') % err)
+ except (ValueError, ConfigValueError, VMMError) as err:
+ w_err(0, _('Warning: %s') % err)
failures += 1
if failures > 2:
- raise ConfigError(_(u'Too many failures - try '
- u'again later.'),
+ raise ConfigError(_('Too many failures - try '
+ 'again later.'),
VMM_TOO_MANY_FAILURES)
else:
break
- print
+ print()
if self._modified:
self._save_changes()
@@ -72,7 +75,7 @@
val = self._cfg[section][option_].cls(value)
if self._cfg[section][option_].validate:
val = self._cfg[section][option_].validate(val)
- except (ValueError, ConfigValueError), err:
+ except (ValueError, ConfigValueError) as err:
raise ConfigError(str(err), CONF_ERROR)
# Do not write default values also skip identical values
if not self._cfg[section][option_].default is None:
@@ -89,8 +92,7 @@
def _save_changes(self):
"""Writes changes to the configuration file."""
copy2(self._cfg_filename, self._cfg_filename + '.bak')
- self._cfg_file = open(self._cfg_filename, 'w')
- self.write(self._cfg_file)
- self._cfg_file.close()
+ with open(self._cfg_filename, 'w', encoding='utf-8') as self._cfg_file:
+ self.write(self._cfg_file)
del _
--- a/VirtualMailManager/cli/handler.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/handler.py Sun Jan 06 00:09:47 2013 +0000
@@ -63,7 +63,7 @@
elif self._cfg.has_section(section):
self._cfg.configure([section])
else:
- raise VMMError(_(u"Invalid section: '%s'") % section,
+ raise VMMError(_("Invalid section: '%s'") % section,
INVALID_SECTION)
def user_add(self, emailaddress, password=None):
@@ -74,7 +74,7 @@
"""
acc = self._get_account(emailaddress)
if acc:
- raise VMMError(_(u"The account '%s' already exists.") %
+ raise VMMError(_("The account '%s' already exists.") %
acc.address, ACCOUNT_EXISTS)
self._is_other_address(acc.address, TYPE_ACCOUNT)
rand_pass = self._cfg.dget('account.random_password')
@@ -90,9 +90,9 @@
password dialog."""
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
- if not isinstance(password, basestring) or not password:
+ if not isinstance(password, str) or not password:
password = read_pass()
acc.modify('password', password)
--- a/VirtualMailManager/cli/main.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/main.py Sun Jan 06 00:09:47 2013 +0000
@@ -8,16 +8,15 @@
VirtualMailManager's command line interface.
"""
-from ConfigParser import NoOptionError, NoSectionError
+from configparser import NoOptionError, NoSectionError
from VirtualMailManager import ENCODING, errors
from VirtualMailManager.config import BadOptionError, ConfigValueError
from VirtualMailManager.cli import w_err
from VirtualMailManager.cli.handler import CliHandler
-from VirtualMailManager.constants import DATABASE_ERROR, EX_MISSING_ARGS, \
- EX_SUCCESS, EX_UNKNOWN_COMMAND, EX_USER_INTERRUPT, INVALID_ARGUMENT
-from VirtualMailManager.cli.subcommands import RunContext, cmd_map, \
- update_cmd_map, usage
+from VirtualMailManager.constants import DATABASE_ERROR, EX_SUCCESS, \
+ EX_USER_INTERRUPT, INVALID_ARGUMENT
+from VirtualMailManager.cli.subcommands import RunContext, setup_parser
_ = lambda msg: msg
@@ -28,55 +27,41 @@
try:
handler = CliHandler()
except (errors.NotRootError, errors.PermissionError, errors.VMMError,
- errors.ConfigError), err:
- w_err(err.code, _(u'Error: %s') % err.msg)
+ errors.ConfigError) as err:
+ w_err(err.code, _('Error: %s') % err.msg)
else:
handler.cfg_install()
return handler
-def run(argv):
- update_cmd_map()
- if len(argv) < 2:
- usage(EX_MISSING_ARGS, _(u"You must specify a subcommand at least."))
-
- sub_cmd = argv[1].lower()
- if sub_cmd in cmd_map:
- cmd_func = cmd_map[sub_cmd].func
- else:
- for cmd in cmd_map.itervalues():
- if cmd.alias == sub_cmd:
- cmd_func = cmd.func
- sub_cmd = cmd.name
- break
- else:
- usage(EX_UNKNOWN_COMMAND, _(u"Unknown subcommand: '%s'") % sub_cmd)
-
+def run():
+ parser = setup_parser()
+ args = parser.parse_args()
handler = _get_handler()
- run_ctx = RunContext(argv, handler, sub_cmd)
+ run_ctx = RunContext(args, handler)
try:
- cmd_func(run_ctx)
+ args.func(run_ctx)
except (EOFError, KeyboardInterrupt):
# TP: We have to cry, because root has killed/interrupted vmm
# with Ctrl+C or Ctrl+D.
- w_err(EX_USER_INTERRUPT, '', _(u'Ouch!'), '')
- except errors.VMMError, err:
+ w_err(EX_USER_INTERRUPT, '', _('Ouch!'), '')
+ except errors.VMMError as err:
if err.code != DATABASE_ERROR:
if handler.has_warnings():
- w_err(0, _(u'Warnings:'), *handler.get_warnings())
- w_err(err.code, _(u'Error: %s') % err.msg)
- w_err(err.code, unicode(err.msg, ENCODING, 'replace'))
- except (BadOptionError, ConfigValueError), err:
- w_err(INVALID_ARGUMENT, _(u'Error: %s') % err)
- except NoSectionError, err:
+ w_err(0, _('Warnings:'), *handler.get_warnings())
+ w_err(err.code, _('Error: %s') % err.msg)
+ w_err(err.code, str(err.msg, ENCODING, 'replace'))
+ except (BadOptionError, ConfigValueError) as err:
+ w_err(INVALID_ARGUMENT, _('Error: %s') % err)
+ except NoSectionError as err:
w_err(INVALID_ARGUMENT,
- _(u"Error: Unknown section: '%s'") % err.section)
- except NoOptionError, err:
+ _("Error: Unknown section: '%s'") % err.section)
+ except NoOptionError as err:
w_err(INVALID_ARGUMENT,
- _(u"Error: No option '%(option)s' in section: '%(section)s'") %
+ _("Error: No option '%(option)s' in section: '%(section)s'") %
{'option': err.option, 'section': err.section})
if handler.has_warnings():
- w_err(0, _(u'Warnings:'), *handler.get_warnings())
+ w_err(0, _('Warnings:'), *handler.get_warnings())
return EX_SUCCESS
del _
--- a/VirtualMailManager/cli/subcommands.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/cli/subcommands.py Sun Jan 06 00:09:47 2013 +0000
@@ -9,150 +9,83 @@
"""
import locale
-import os
+import platform
+from argparse import Action, ArgumentParser, ArgumentTypeError, \
+ RawDescriptionHelpFormatter
from textwrap import TextWrapper
from time import strftime, strptime
from VirtualMailManager import ENCODING
-from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
-from VirtualMailManager.cli.clihelp import help_msgs
+from VirtualMailManager.cli import get_winsize, w_err, w_std
from VirtualMailManager.common import human_size, size_in_bytes, \
version_str, format_domain_default
from VirtualMailManager.constants import __copyright__, __date__, \
__version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
- DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \
- RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
+ DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, RELOCATED_EXISTS, TYPE_ACCOUNT, \
+ TYPE_ALIAS, TYPE_RELOCATED
from VirtualMailManager.errors import VMMError
from VirtualMailManager.password import list_schemes
from VirtualMailManager.serviceset import SERVICES
__all__ = (
- 'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
- 'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
- 'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete',
- 'config_get', 'config_set', 'configure',
- 'domain_add', 'domain_delete', 'domain_info', 'domain_quota',
- 'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_',
- 'list_domains', 'list_pwschemes', 'list_users', 'list_aliases',
- 'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete',
- 'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name',
- 'user_password', 'user_quota', 'user_services', 'user_transport',
- 'user_note', 'version',
+ 'RunContext', 'alias_add', 'alias_delete', 'alias_info', 'aliasdomain_add',
+ 'aliasdomain_delete', 'aliasdomain_info', 'aliasdomain_switch',
+ 'catchall_add', 'catchall_delete', 'catchall_info', 'config_get',
+ 'config_set', 'configure', 'domain_add', 'domain_delete', 'domain_info',
+ 'domain_note', 'domain_quota', 'domain_services', 'domain_transport',
+ 'get_user', 'list_addresses', 'list_aliases', 'list_domains',
+ 'list_pwschemes', 'list_relocated', 'list_users', 'relocated_add',
+ 'relocated_delete', 'relocated_info', 'setup_parser', 'user_add',
+ 'user_delete', 'user_info', 'user_name', 'user_note', 'user_password',
+ 'user_quota', 'user_services', 'user_transport',
)
+WS_ROWS = get_winsize()[1] - 2
+
_ = lambda msg: msg
-txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
-cmd_map = {}
-
-
-class Command(object):
- """Container class for command information."""
- __slots__ = ('name', 'alias', 'func', 'args', 'descr')
- FMT_HLP_USAGE = """
-usage: %(prog)s %(name)s %(args)s
- %(prog)s %(alias)s %(args)s
-"""
-
- def __init__(self, name, alias, func, args, descr):
- """Create a new Command instance.
-
- Arguments:
-
- `name` : str
- the command name, e.g. ``addalias``
- `alias` : str
- the command's short alias, e.g. ``aa``
- `func` : callable
- the function to handle the command
- `args` : str
- argument placeholders, e.g. ``aliasaddress``
- `descr` : str
- short description of the command
- """
- self.name = name
- self.alias = alias
- self.func = func
- self.args = args
- self.descr = descr
-
- @property
- def usage(self):
- """the command's usage info."""
- return u'%s %s %s' % (prog, self.name, self.args)
-
- def help_(self):
- """Print the Command's help message to stdout."""
- old_ii = txt_wrpr.initial_indent
- old_si = txt_wrpr.subsequent_indent
-
- txt_wrpr.subsequent_indent = (len(self.name) + 2) * ' '
- w_std(txt_wrpr.fill('%s: %s' % (self.name, self.descr)))
-
- info = Command.FMT_HLP_USAGE % dict(alias=self.alias, args=self.args,
- name=self.name, prog=prog)
- w_std(info)
-
- txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = ' '
- try:
- [w_std(txt_wrpr.fill(_(para)) + '\n') for para
- in help_msgs[self.name]]
- except KeyError:
- w_err(1, _(u"Subcommand '%s' is not yet documented." % self.name),
- 'see also: vmm(1)')
+txt_wrpr = TextWrapper(width=WS_ROWS)
class RunContext(object):
"""Contains all information necessary to run a subcommand."""
- __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
- plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
+ __slots__ = ('args', 'cget', 'hdlr')
+ plan_a_b = _('Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
- def __init__(self, argv, handler, command):
+ def __init__(self, args, handler):
"""Create a new RunContext"""
- self.argc = len(argv)
- self.args = [unicode(arg, ENCODING) for arg in argv]
+ self.args = args
self.cget = handler.cfg_dget
self.hdlr = handler
- self.scmd = command
def alias_add(ctx):
"""create a new alias e-mail address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
- ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
+ ctx.hdlr.alias_add(ctx.args.address.lower(), *ctx.args.destination)
def alias_delete(ctx):
"""delete the specified alias e-mail address or one of its destinations"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.alias_delete(ctx.args[2].lower())
- else:
- ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3:])
+ destination = ctx.args.destination if ctx.args.destination else None
+ ctx.hdlr.alias_delete(ctx.args.address.lower(), destination)
def alias_info(ctx):
"""show the destination(s) of the specified alias"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
- address = ctx.args[2].lower()
+ address = ctx.args.address.lower()
try:
_print_aliase_info(address, ctx.hdlr.alias_info(address))
- except VMMError, err:
+ except VMMError as err:
if err.code is ACCOUNT_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+ w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
'object': address})
- ctx.scmd = ctx.args[1] = 'userinfo'
+ ctx.args.scmd = 'userinfo'
+ ctx.args.details = None
user_info(ctx)
elif err.code is RELOCATED_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
+ w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
'object': address})
- ctx.scmd = ctx.args[1] = 'relocatedinfo'
+ ctx.args.scmd = 'relocatedinfo'
relocated_info(ctx)
else:
raise
@@ -160,33 +93,25 @@
def aliasdomain_add(ctx):
"""create a new alias for an existing domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
- u'domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
- ctx.scmd)
- ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
+ ctx.hdlr.aliasdomain_add(ctx.args.fqdn.lower(),
+ ctx.args.destination.lower())
def aliasdomain_delete(ctx):
"""delete the specified alias domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
- ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
+ ctx.hdlr.aliasdomain_delete(ctx.args.fqdn.lower())
def aliasdomain_info(ctx):
"""show the destination of the given alias domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
+ fqdn = ctx.args.fqdn.lower()
try:
- _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
- except VMMError, err:
+ _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(fqdn))
+ except VMMError as err:
if err.code is ALIASDOMAIN_ISDOMAIN:
- w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'domaininfo'
+ w_err(0, ctx.plan_a_b % {'subcommand': 'domaininfo',
+ 'object': fqdn})
+ ctx.args.scmd = 'domaininfo'
domain_info(ctx)
else:
raise
@@ -194,327 +119,162 @@
def aliasdomain_switch(ctx):
"""assign the given alias domain to an other domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
- u'domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
- ctx.scmd)
- ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
+ ctx.hdlr.aliasdomain_switch(ctx.args.fqdn.lower(),
+ ctx.args.destination.lower())
def catchall_add(ctx):
"""create a new catchall alias e-mail address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain and destination.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
- ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:])
+ ctx.hdlr.catchall_add(ctx.args.fqdn.lower(), *ctx.args.destination)
def catchall_delete(ctx):
"""delete the specified destination or all of the catchall destination"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.catchall_delete(ctx.args[2].lower())
- else:
- ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3:])
+ destination = ctx.args.destination if ctx.args.destination else None
+ ctx.hdlr.catchall_delete(ctx.args.fqdn.lower(), destination)
def catchall_info(ctx):
"""show the catchall destination(s) of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- address = ctx.args[2].lower()
+ address = ctx.args.fqdn.lower()
_print_catchall_info(address, ctx.hdlr.catchall_info(address))
def config_get(ctx):
"""show the actual value of the configuration option"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u"Missing option name."), ctx.scmd)
-
noop = lambda option: option
opt_formater = {
'misc.dovecot_version': version_str,
'domain.quota_bytes': human_size,
}
- option = ctx.args[2].lower()
+ option = ctx.args.option.lower()
w_std('%s = %s' % (option, opt_formater.get(option,
noop)(ctx.cget(option))))
def config_set(ctx):
"""set a new value for the configuration option"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing option and new value.'), ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing new configuration value.'),
- ctx.scmd)
- ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3])
+ ctx.hdlr.cfg_set(ctx.args.option.lower(), ctx.args.value)
def configure(ctx):
"""start interactive configuration mode"""
- if ctx.argc < 3:
- ctx.hdlr.configure()
- else:
- ctx.hdlr.configure(ctx.args[2].lower())
+ ctx.hdlr.configure(ctx.args.section)
def domain_add(ctx):
"""create a new domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.domain_add(ctx.args[2].lower())
- else:
- ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
+ fqdn = ctx.args.fqdn.lower()
+ transport = ctx.args.transport.lower if ctx.args.transport else None
+ ctx.hdlr.domain_add(fqdn, transport)
if ctx.cget('domain.auto_postmaster'):
- w_std(_(u'Creating account for postmaster@%s') % ctx.args[2].lower())
- ctx.scmd = 'useradd'
- ctx.args = [prog, ctx.scmd, u'postmaster@' + ctx.args[2].lower()]
- ctx.argc = 3
+ w_std(_('Creating account for postmaster@%s') % fqdn)
+ ctx.args.scmd = 'useradd'
+ ctx.args.address = 'postmaster@%s' % fqdn
+ ctx.args.password = None
user_add(ctx)
def domain_delete(ctx):
"""delete the given domain and all its alias domains"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.domain_delete(ctx.args[2].lower())
- elif ctx.args[3].lower() == 'force':
- ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
- else:
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
- ctx.scmd)
+ ctx.hdlr.domain_delete(ctx.args.fqdn.lower(), ctx.args.force)
def domain_info(ctx):
"""display information about the given domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- if ctx.argc < 4:
- details = None
- else:
- details = ctx.args[3].lower()
- if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
- 'relocated', 'catchall'):
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
- ctx.scmd)
+ fqdn = ctx.args.fqdn.lower()
+ details = ctx.args.details
try:
- info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
- except VMMError, err:
+ info = ctx.hdlr.domain_info(fqdn, details)
+ except VMMError as err:
if err.code is DOMAIN_ALIAS_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'aliasdomaininfo'
+ w_err(0, ctx.plan_a_b % {'subcommand': 'aliasdomaininfo',
+ 'object': fqdn})
+ ctx.args.scmd = 'aliasdomaininfo'
aliasdomain_info(ctx)
else:
raise
else:
- q_limit = u'Storage: %(bytes)s; Messages: %(messages)s'
+ q_limit = 'Storage: %(bytes)s; Messages: %(messages)s'
if not details:
info['bytes'] = human_size(info['bytes'])
- info['messages'] = locale.format('%d', info['messages'],
- True).decode(ENCODING, 'replace')
+ info['messages'] = locale.format('%d', info['messages'], True)
info['quota limit/user'] = q_limit % info
- _print_info(ctx, info, _(u'Domain'))
+ _print_info(ctx, info, _('Domain'))
else:
info[0]['bytes'] = human_size(info[0]['bytes'])
info[0]['messages'] = locale.format('%d', info[0]['messages'],
- True).decode(ENCODING,
- 'replace')
+ True)
info[0]['quota limit/user'] = q_limit % info[0]
- _print_info(ctx, info[0], _(u'Domain'))
- if details == u'accounts':
- _print_list(info[1], _(u'accounts'))
- elif details == u'aliasdomains':
- _print_list(info[1], _(u'alias domains'))
- elif details == u'aliases':
- _print_list(info[1], _(u'aliases'))
- elif details == u'relocated':
- _print_list(info[1], _(u'relocated users'))
- elif details == u'catchall':
- _print_list(info[1], _(u'catch-all destinations'))
+ _print_info(ctx, info[0], _('Domain'))
+ if details == 'accounts':
+ _print_list(info[1], _('accounts'))
+ elif details == 'aliasdomains':
+ _print_list(info[1], _('alias domains'))
+ elif details == 'aliases':
+ _print_list(info[1], _('aliases'))
+ elif details == 'relocated':
+ _print_list(info[1], _('relocated users'))
+ elif details == 'catchall':
+ _print_list(info[1], _('catch-all destinations'))
else:
- _print_list(info[1], _(u'alias domains'))
- _print_list(info[2], _(u'accounts'))
- _print_list(info[3], _(u'aliases'))
- _print_list(info[4], _(u'relocated users'))
- _print_list(info[5], _(u'catch-all destinations'))
+ _print_list(info[1], _('alias domains'))
+ _print_list(info[2], _('accounts'))
+ _print_list(info[3], _('aliases'))
+ _print_list(info[4], _('relocated users'))
+ _print_list(info[5], _('catch-all destinations'))
def domain_quota(ctx):
"""update the quota limit of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name and storage value.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
- messages = 0
- force = None
- try:
- bytes_ = size_in_bytes(ctx.args[3])
- except (ValueError, TypeError):
- usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
- ctx.args[3], ctx.scmd)
- if ctx.argc < 5:
- pass
- elif ctx.argc < 6:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- if ctx.args[4].lower() != 'force':
- usage(INVALID_ARGUMENT,
- _(u"Neither a valid number of messages nor the keyword "
- u"'force': '%s'") % ctx.args[4], ctx.scmd)
- force = 'force'
- else:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- usage(INVALID_ARGUMENT,
- _(u"Not a valid number of messages: '%s'") % ctx.args[4],
- ctx.scmd)
- if ctx.args[5].lower() != 'force':
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[5],
- ctx.scmd)
- force = 'force'
- ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force)
+ force = 'force' if ctx.args.force else None
+ ctx.hdlr.domain_quotalimit(ctx.args.fqdn.lower(), ctx.args.storage,
+ ctx.args.messages, force)
def domain_services(ctx):
"""allow all named service and block the uncredited."""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
- services = []
- force = False
- if ctx.argc is 3:
- pass
- elif ctx.argc is 4:
- arg = ctx.args[3].lower()
- if arg in SERVICES:
- services.append(arg)
- elif arg == 'force':
- force = True
- else:
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % arg,
- ctx.scmd)
- else:
- services.extend([service.lower() for service in ctx.args[3:-1]])
- arg = ctx.args[-1].lower()
- if arg == 'force':
- force = True
- else:
- services.append(arg)
- unknown = [service for service in services if service not in SERVICES]
- if unknown:
- usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
- ' '.join(unknown), ctx.scmd)
- ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force],
- *services)
+ force = 'force' if ctx.args.force else None
+ services = ctx.args.services if ctx.args.services else []
+ ctx.hdlr.domain_services(ctx.args.fqdn.lower(), force, *services)
def domain_transport(ctx):
"""update the transport of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
- if ctx.argc < 5:
- ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
- else:
- force = ctx.args[4].lower()
- if force != 'force':
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
- ctx.scmd)
- ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
+ force = 'force' if ctx.args.force else None
+ ctx.hdlr.domain_transport(ctx.args.fqdn.lower(),
+ ctx.args.transport.lower(), force)
def domain_note(ctx):
"""update the note of the given domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing domain name.'),
- ctx.scmd)
- elif ctx.argc < 4:
- note = None
- else:
- note = ' '.join(ctx.args[3:])
- ctx.hdlr.domain_note(ctx.args[2].lower(), note)
+ ctx.hdlr.domain_note(ctx.args.fqdn.lower(), ctx.args.note)
def get_user(ctx):
"""get the address of the user with the given UID"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing UID.'), ctx.scmd)
- _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
-
-
-def help_(ctx):
- """print help messages."""
- if ctx.argc > 2:
- hlptpc = ctx.args[2].lower()
- if hlptpc in cmd_map:
- topic = hlptpc
- else:
- for scmd in cmd_map.itervalues():
- if scmd.alias == hlptpc:
- topic = scmd.name
- break
- else:
- usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
- ctx.args[2], ctx.scmd)
- if topic != u'help':
- return cmd_map[topic].help_()
-
- old_ii = txt_wrpr.initial_indent
- old_si = txt_wrpr.subsequent_indent
- txt_wrpr.initial_indent = ' '
- # len(max(_overview.iterkeys(), key=len)) #Py25
- txt_wrpr.subsequent_indent = 20 * ' '
- order = cmd_map.keys()
- order.sort()
-
- w_std(_(u'List of available subcommands:') + '\n')
- for key in order:
- w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
-
- txt_wrpr.initial_indent = old_ii
- txt_wrpr.subsequent_indent = old_si
- txt_wrpr.initial_indent = ''
+ _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args.uid), _('Account'))
def list_domains(ctx):
"""list all domains / search domains by pattern"""
- matching = ctx.argc > 2
- if matching:
- gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
- else:
- gids, domains = ctx.hdlr.domain_list()
+ matching = True if ctx.args.pattern else False
+ pattern = ctx.args.pattern.lower() if matching else None
+ gids, domains = ctx.hdlr.domain_list(pattern)
_print_domain_list(gids, domains, matching)
def list_pwschemes(ctx_unused):
"""Prints all usable password schemes and password encoding suffixes."""
- # TODO: Remove trailing colons from keys.
- # For now it is to late, the translators has stared their work
- keys = (_(u'Usable password schemes:'), _(u'Usable encoding suffixes:'))
+ keys = (_('Usable password schemes'), _('Usable encoding suffixes'))
old_ii, old_si = txt_wrpr.initial_indent, txt_wrpr.subsequent_indent
txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
txt_wrpr.width = txt_wrpr.width - 8
for key, value in zip(keys, list_schemes()):
- if key.endswith(':'): # who knows … (see TODO above)
- #key = key.rpartition(':')[0]
- key = key[:-1] # This one is for Py24
w_std(key, len(key) * '-')
- w_std('\n'.join(txt_wrpr.wrap(' '.join(value))), '')
+ w_std('\n'.join(txt_wrpr.wrap(' '.join(sorted(value)))), '')
txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si
txt_wrpr.width = txt_wrpr.width + 8
@@ -527,11 +287,9 @@
combining all three."""
if limit is None:
limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
- matching = ctx.argc > 2
- if matching:
- gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower())
- else:
- gids, addresses = ctx.hdlr.address_list(limit)
+ matching = True if ctx.args.pattern else False
+ pattern = ctx.args.pattern.lower() if matching else None
+ gids, addresses = ctx.hdlr.address_list(limit, pattern)
_print_address_list(limit, gids, addresses, matching)
@@ -552,39 +310,31 @@
def relocated_add(ctx):
"""create a new record for a relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS,
- _(u'Missing relocated address and destination.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
- ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
+ ctx.hdlr.relocated_add(ctx.args.address.lower(), ctx.args.newaddress)
def relocated_delete(ctx):
"""delete the record of the relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
- ctx.hdlr.relocated_delete(ctx.args[2].lower())
+ ctx.hdlr.relocated_delete(ctx.args.address.lower())
def relocated_info(ctx):
"""print information about a relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
- relocated = ctx.args[2].lower()
+ relocated = ctx.args.address.lower()
try:
_print_relocated_info(addr=relocated,
dest=ctx.hdlr.relocated_info(relocated))
- except VMMError, err:
+ except VMMError as err:
if err.code is ACCOUNT_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+ w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
'object': relocated})
- ctx.scmd = ctx.args[1] = 'userinfoi'
+ ctx.args.scmd = 'userinfo'
+ ctx.args.details = None
user_info(ctx)
elif err.code is ALIAS_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
+ w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
'object': relocated})
- ctx.scmd = ctx.args[1] = 'aliasinfo'
+ ctx.args.scmd = 'aliasinfo'
alias_info(ctx)
else:
raise
@@ -592,65 +342,43 @@
def user_add(ctx):
"""create a new e-mail user with the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- password = None
- else:
- password = ctx.args[3]
- gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password)
- if ctx.argc < 4 and gen_pass:
- w_std(_(u"Generated password: %s") % gen_pass)
+ gen_pass = ctx.hdlr.user_add(ctx.args.address.lower(), ctx.args.password)
+ if not ctx.args.password and gen_pass:
+ w_std(_("Generated password: %s") % gen_pass)
def user_delete(ctx):
"""delete the specified user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.user_delete(ctx.args[2].lower())
- elif ctx.args[3].lower() == 'force':
- ctx.hdlr.user_delete(ctx.args[2].lower(), True)
- else:
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
- ctx.scmd)
+ ctx.hdlr.user_delete(ctx.args.address.lower(), ctx.args.force)
def user_info(ctx):
"""display information about the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
- if ctx.argc < 4:
- details = None
- else:
- details = ctx.args[3].lower()
- if details not in ('aliases', 'du', 'full'):
- usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
- ctx.scmd)
+ address = ctx.args.address.lower()
try:
- info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
- except VMMError, err:
+ info = ctx.hdlr.user_info(address, ctx.args.details)
+ except VMMError as err:
if err.code is ALIAS_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'aliasinfo'
+ w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
+ 'object': address})
+ ctx.args.scmd = 'aliasinfo'
alias_info(ctx)
elif err.code is RELOCATED_EXISTS:
- w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'relocatedinfo'
+ w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
+ 'object': address})
+ ctx.args.scmd = 'relocatedinfo'
relocated_info(ctx)
else:
raise
else:
- if details in (None, 'du'):
+ if ctx.args.details in (None, 'du'):
info['quota storage'] = _format_quota_usage(info['ql_bytes'],
info['uq_bytes'], True, info['ql_domaindefault'])
info['quota messages'] = \
_format_quota_usage(info['ql_messages'],
info['uq_messages'],
domaindefault=info['ql_domaindefault'])
- _print_info(ctx, info, _(u'Account'))
+ _print_info(ctx, info, _('Account'))
else:
info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'],
info[0]['uq_bytes'], True, info[0]['ql_domaindefault'])
@@ -658,286 +386,650 @@
_format_quota_usage(info[0]['ql_messages'],
info[0]['uq_messages'],
domaindefault=info[0]['ql_domaindefault'])
- _print_info(ctx, info[0], _(u'Account'))
- _print_list(info[1], _(u'alias addresses'))
+ _print_info(ctx, info[0], _('Account'))
+ _print_list(info[1], _('alias addresses'))
def user_name(ctx):
"""set or update the real name for an address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u"Missing e-mail address and user's name."),
- ctx.scmd)
- elif ctx.argc < 4:
- name = None
- else:
- name = ctx.args[3]
- ctx.hdlr.user_name(ctx.args[2].lower(), name)
+ ctx.hdlr.user_name(ctx.args.address.lower(), ctx.args.name)
def user_password(ctx):
"""update the password for the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- password = None
- else:
- password = ctx.args[3]
- ctx.hdlr.user_password(ctx.args[2].lower(), password)
+ ctx.hdlr.user_password(ctx.args.address.lower(), ctx.args.password)
def user_note(ctx):
"""update the note of the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'),
- ctx.scmd)
- elif ctx.argc < 4:
- note = None
- else:
- note = ' '.join(ctx.args[3:])
- ctx.hdlr.user_note(ctx.args[2].lower(), note)
+ ctx.hdlr.user_note(ctx.args.address.lower(), ctx.args.note)
def user_quota(ctx):
"""update the quota limit for the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address and storage value.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
- if ctx.args[3] != 'domain':
- try:
- bytes_ = size_in_bytes(ctx.args[3])
- except (ValueError, TypeError):
- usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
- ctx.args[3], ctx.scmd)
- else:
- bytes_ = ctx.args[3]
- if ctx.argc < 5:
- messages = 0
- else:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- usage(INVALID_ARGUMENT,
- _(u"Not a valid number of messages: '%s'") % ctx.args[4],
- ctx.scmd)
- ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages)
+ ctx.hdlr.user_quotalimit(ctx.args.address.lower(), ctx.args.storage,
+ ctx.args.messages)
def user_services(ctx):
"""allow all named service and block the uncredited."""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
- services = []
- if ctx.argc >= 4:
- services.extend([service.lower() for service in ctx.args[3:]])
- unknown = [service for service in services if service not in SERVICES]
- if unknown and ctx.args[3] != 'domain':
- usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
- ' '.join(unknown), ctx.scmd)
- ctx.hdlr.user_services(ctx.args[2].lower(), *services)
+ if 'domain' in ctx.args.services:
+ services = ['domain']
+ else:
+ services = ctx.args.services
+ ctx.hdlr.user_services(ctx.args.address.lower(), *services)
def user_transport(ctx):
"""update the transport of the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
- ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
-
-
-def usage(errno, errmsg, subcommand=None):
- """print usage message for the given command or all commands.
- When errno > 0, sys,exit(errno) will interrupt the program.
- """
- if subcommand and subcommand in cmd_map:
- w_err(errno, _(u"Error: %s") % errmsg,
- _(u"usage: ") + cmd_map[subcommand].usage)
-
- # TP: Please adjust translated words like the original text.
- # (It's a table header.) Extract from usage text:
- # usage: vmm subcommand arguments
- # short long
- # subcommand arguments
- #
- # da domainadd fqdn [transport]
- # dd domaindelete fqdn [force]
- u_head = _(u"""usage: %s subcommand arguments
- short long
- subcommand arguments\n""") % prog
- order = cmd_map.keys()
- order.sort()
- w_err(0, u_head)
- for key in order:
- scmd = cmd_map[key]
- w_err(0, ' %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
- w_err(errno, '', _(u"Error: %s") % errmsg)
-
-
-def version(ctx_unused):
- """Write version and copyright information to stdout."""
- w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
- # TP: The words 'from', 'version' and 'on' are used in
- # the version information, e.g.:
- # vmm, version 0.5.2 (from 09/09/09)
- # Python 2.5.4 on FreeBSD
- _(u'version'), __version__, _(u'from'),
- strftime(locale.nl_langinfo(locale.D_FMT),
- strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
- os.sys.version.split()[0], _(u'on'), os.uname()[0],
- __copyright__, prog,
- _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
+ ctx.hdlr.user_transport(ctx.args.address.lower(), ctx.args.transport)
-def update_cmd_map():
- """Update the cmd_map, after gettext's _ was installed."""
- cmd = Command
- cmd_map.update({
- # Account commands
- 'getuser': cmd('getuser', 'gu', get_user, 'uid',
- _(u'get the address of the user with the given UID')),
- 'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
- _(u'create a new e-mail user with the given address')),
- 'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
- _(u'delete the specified user')),
- 'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
- _(u'display information about the given address')),
- 'username': cmd('username', 'un', user_name, 'address [name]',
- _(u'set, update or delete the real name for an address')),
- 'userpassword': cmd('userpassword', 'up', user_password,
- 'address [password]',
- _(u'update the password for the given address')),
- 'userquota': cmd('userquota', 'uq', user_quota,
- 'address storage [messages] | address domain',
- _(u'update the quota limit for the given address')),
- 'userservices': cmd('userservices', 'us', user_services,
- 'address [service ...] | address domain',
- _(u'enables the specified services and disables all '
- u'not specified services')),
- 'usertransport': cmd('usertransport', 'ut', user_transport,
- 'address transport | address domain',
- _(u'update the transport of the given address')),
- 'usernote': cmd('usernote', 'uo', user_note, 'address [note]',
- _(u'set, update or delete the note of the given address')),
- # Alias commands
- 'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
- _(u'create a new alias e-mail address with one or more '
- u'destinations')),
- 'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
- 'address [destination ...]',
- _(u'delete the specified alias e-mail address or one '
- u'of its destinations')),
- 'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
- _(u'show the destination(s) of the specified alias')),
- # AliasDomain commands
- 'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
- 'fqdn destination',
- _(u'create a new alias for an existing domain')),
- 'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
- 'fqdn', _(u'delete the specified alias domain')),
- 'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
- _(u'show the destination of the given alias domain')),
- 'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
- 'fqdn destination', _(u'assign the given alias '
- 'domain to an other domain')),
- # CatchallAlias commands
- 'catchalladd': cmd('catchalladd', 'caa', catchall_add,
- 'fqdn destination ...',
- _(u'add one or more catch-all destinations for a '
- u'domain')),
- 'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete,
- 'fqdn [destination ...]',
- _(u'delete the specified catch-all destination or all '
- u'of a domain\'s destinations')),
- 'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn',
- _(u'show the catch-all destination(s) of the '
- u'specified domain')),
- # Domain commands
- 'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
- _(u'create a new domain')),
- 'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
- _(u'delete the given domain and all its alias domains')),
- 'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
- _(u'display information about the given domain')),
- 'domainquota': cmd('domainquota', 'dq', domain_quota,
- 'fqdn storage [messages] [force]',
- _(u'update the quota limit of the specified domain')),
- 'domainservices': cmd('domainservices', 'ds', domain_services,
- 'fqdn [service ...] [force]',
- _(u'enables the specified services and disables all '
- u'not specified services of the given domain')),
- 'domaintransport': cmd('domaintransport', 'dt', domain_transport,
- 'fqdn transport [force]',
- _(u'update the transport of the specified domain')),
- 'domainnote': cmd('domainnote', 'do', domain_note, 'fqdn [note]',
- _(u'set, update or delete the note of the given domain')),
- # List commands
- 'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
- _(u'list all domains or search for domains by pattern')),
- 'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]',
- _(u'list all addresses or search for addresses by '
- u'pattern')),
- 'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
- _(u'list all user accounts or search for accounts by '
- u'pattern')),
- 'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]',
- _(u'list all aliases or search for aliases by pattern')),
- 'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]',
- _(u'list all relocated users or search for relocated '
- u'users by pattern')),
- # Relocated commands
- 'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
- 'address newaddress',
- _(u'create a new record for a relocated user')),
- 'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
- 'address',
- _(u'delete the record of the relocated user')),
- 'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
- _(u'print information about a relocated user')),
- # cli commands
- 'configget': cmd('configget', 'cg', config_get, 'option',
- _('show the actual value of the configuration option')),
- 'configset': cmd('configset', 'cs', config_set, 'option value',
- _('set a new value for the configuration option')),
- 'configure': cmd('configure', 'cf', configure, '[section]',
- _(u'start interactive configuration mode')),
- 'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '',
- _(u'lists all usable password schemes and password '
- u'encoding suffixes')),
- 'help': cmd('help', 'h', help_, '[subcommand]',
- _(u'show a help overview or help for the given subcommand')),
- 'version': cmd('version', 'v', version, '',
- _(u'show version and copyright information')),
- })
+def setup_parser():
+ """Create the argument parser, add all the subcommands and return it."""
+ class ArgParser(ArgumentParser):
+ """This class fixes the 'width detection'."""
+ def _get_formatter(self):
+ return self.formatter_class(prog=self.prog, width=WS_ROWS,
+ max_help_position=26)
+
+ class VersionAction(Action):
+ """Show version and copyright information."""
+ def __call__(self, parser, namespace, values, option_string=None):
+ """implements the Action API."""
+ vers_info = _('{program}, version {version} (from {rel_date})\n'
+ 'Python {py_vers} on {sysname}'.format(
+ program=parser.prog, version=__version__,
+ rel_date=strftime(
+ locale.nl_langinfo(locale.D_FMT),
+ strptime(__date__, '%Y-%m-%d')),
+ py_vers=platform.python_version(),
+ sysname=platform.system()))
+ copy_info = _('{copyright}\n{program} is free software and comes '
+ 'with ABSOLUTELY NO WARRANTY.'.format(
+ copyright=__copyright__, program=parser.prog))
+ parser.exit(message='\n\n'.join((vers_info, copy_info)) + '\n')
+
+ def quota_storage(string):
+ if string == 'domain':
+ return string
+ try:
+ storage = size_in_bytes(string)
+ except (TypeError, ValueError) as error:
+ raise ArgumentTypeError(str(error))
+ return storage
+
+ old_rw = txt_wrpr.replace_whitespace
+ txt_wrpr.replace_whitespace = False
+ fill = lambda t: '\n'.join(txt_wrpr.fill(l) for l in t.splitlines(True))
+ mklst = lambda iterable: '\n\t - ' + '\n\t - '.join(iterable)
+
+ description = _('%(prog)s - command line tool to manage email '
+ 'domains/accounts/aliases/...')
+ epilog = _('use "%(prog)s <subcommand> -h" for information about the '
+ 'given subcommand')
+ parser = ArgParser(description=description, epilog=epilog)
+ parser.add_argument('-v', '--version', action=VersionAction, nargs=0,
+ help=_("show %(prog)s's version and copyright "
+ "information and exit"))
+ subparsers = parser.add_subparsers(metavar=_('<subcommand>'),
+ title=_('list of available subcommands'))
+ a = subparsers.add_parser
+
+ ###
+ # general subcommands
+ ###
+ cg = a('configget', aliases=('cg',),
+ help=_('show the actual value of the configuration option'),
+ epilog=_("This subcommand is used to display the actual value of "
+ "the given configuration option."))
+ cg.add_argument('option', help=_('the name of a configuration option'))
+ cg.set_defaults(func=config_get, scmd='configget')
+
+ cs = a('configset', aliases=('cs',),
+ help=_('set a new value for the configuration option'),
+ epilog=fill(_("Use this subcommand to set or update a single "
+ "configuration option's value. option is the configuration "
+ "option, value is the option's new value.\n\nNote: This "
+ "subcommand will create a new vmm.cfg without any comments. "
+ "Your current configuration file will be backed as "
+ "vmm.cfg.bak.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ cs.add_argument('option', help=_('the name of a configuration option'))
+ cs.add_argument('value', help=_("the option's new value"))
+ cs.set_defaults(func=config_set, scmd='configset')
+
+ sections = ('account', 'bin', 'database', 'domain', 'mailbox', 'misc')
+ cf = a('configure', aliases=('cf',),
+ help=_('start interactive configuration mode'),
+ epilog=fill(_("Starts the interactive configuration for all "
+ "configuration sections.\n\nIn this process the currently set "
+ "value of each option will be displayed in square brackets. "
+ "If no value is configured, the default value of each option "
+ "will be displayed in square brackets. Press the return key, "
+ "to accept the displayed value.\n\n"
+ "If the optional argument section is given, only the "
+ "configuration options from the given section will be "
+ "displayed and will be configurable. The following sections "
+ "are available:\n") + mklst(sections)),
+ formatter_class=RawDescriptionHelpFormatter)
+ cf.add_argument('-s', choices=sections, metavar='SECTION', dest='section',
+ help=_("configure only options of the given section"))
+ cf.set_defaults(func=configure, scmd='configure')
+
+ gu = a('getuser', aliases=('gu',),
+ help=_('get the address of the user with the given UID'),
+ epilog=_("If only the uid is available, for example from process "
+ "list, the subcommand getuser will show the user's "
+ "address."))
+ gu.add_argument('uid', type=int, help=_("a user's unique identifier"))
+ gu.set_defaults(func=get_user, scmd='getuser')
+
+ ll = a('listaddresses', aliases=('ll',),
+ help=_('list all addresses or search for addresses by pattern'),
+ epilog=fill(_("This command lists all defined addresses. "
+ "Addresses belonging to alias-domains are prefixed with a '-', "
+ "addresses of regular domains with a '+'. Additionally, the "
+ "letters 'u', 'a', and 'r' indicate the type of each address: "
+ "user, alias and relocated respectively. The output can be "
+ "limited with an optional pattern.\n\nTo perform a wild card "
+ "search, the % character can be used at the start and/or the "
+ "end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ll.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ ll.set_defaults(func=list_addresses, scmd='listaddresses')
+
+ la = a('listaliases', aliases=('la',),
+ help=_('list all aliases or search for aliases by pattern'),
+ epilog=fill(_("This command lists all defined aliases. Aliases "
+ "belonging to alias-domains are prefixed with a '-', addresses "
+ "of regular domains with a '+'. The output can be limited "
+ "with an optional pattern.\n\nTo perform a wild card search, "
+ "the % character can be used at the start and/or the end of "
+ "the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ la.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ la.set_defaults(func=list_aliases, scmd='listaliases')
+
+ ld = a('listdomains', aliases=('ld',),
+ help=_('list all domains or search for domains by pattern'),
+ epilog=fill(_("This subcommand lists all available domains. All "
+ "domain names will be prefixed either with `[+]', if the "
+ "domain is a primary domain, or with `[-]', if it is an alias "
+ "domain name. The output can be limited with an optional "
+ "pattern.\n\nTo perform a wild card search, the % character "
+ "can be used at the start and/or the end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ld.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ ld.set_defaults(func=list_domains, scmd='listdomains')
+
+ lr = a('listrelocated', aliases=('lr',),
+ help=_('list all relocated users or search for relocated users by '
+ 'pattern'),
+ epilog=fill(_("This command lists all defined relocated addresses. "
+ "Relocated entries belonging to alias-domains are prefixed "
+ "with a '-', addresses of regular domains with a '+'. The "
+ "output can be limited with an optional pattern.\n\nTo "
+ "perform a wild card search, the % character can be used at "
+ "the start and/or the end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lr.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ lr.set_defaults(func=list_relocated, scmd='listrelocated')
+
+ lu = a('listusers', aliases=('lu',),
+ help=_('list all user accounts or search for accounts by pattern'),
+ epilog=fill(_("This command lists all user accounts. User accounts "
+ "belonging to alias-domains are prefixed with a '-', "
+ "addresses of regular domains with a '+'. The output can be "
+ "limited with an optional pattern.\n\nTo perform a wild card "
+ "search, the % character can be used at the start and/or the "
+ "end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lu.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ lu.set_defaults(func=list_users, scmd='listusers')
+
+ lp = a('listpwschemes', aliases=('lp',),
+ help=_('lists all usable password schemes and password encoding '
+ 'suffixes'),
+ epilog=fill(_("This subcommand lists all password schemes which "
+ "could be used in the vmm.cfg as value of the "
+ "misc.password_scheme option. The output varies, depending "
+ "on the used Dovecot version and the system's libc.\nWhen "
+ "your Dovecot installation isn't too old, you will see "
+ "additionally a few usable encoding suffixes. One of them can "
+ "be appended to the password scheme.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lp.set_defaults(func=list_pwschemes, scmd='listpwschemes')
+
+ ###
+ # domain subcommands
+ ###
+ da = a('domainadd', aliases=('da',), help=_('create a new domain'),
+ epilog=fill(_("Adds the new domain into the database and creates "
+ "the domain directory.\n\nIf the optional argument transport "
+ "is given, it will override the default transport "
+ "(domain.transport) from vmm.cfg. The specified transport "
+ "will be the default transport for all new accounts in this "
+ "domain.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ da.add_argument('fqdn', help=_('a fully qualified domain name'))
+ da.add_argument('-t', metavar='TRANSPORT', dest='transport',
+ help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ da.set_defaults(func=domain_add, scmd='domainadd')
+
+ details = ('accounts', 'aliasdomains', 'aliases', 'catchall', 'relocated',
+ 'full')
+ di = a('domaininfo', aliases=('di',),
+ help=_('display information about the given domain'),
+ epilog=fill(_("This subcommand shows some information about the "
+ "given domain.\n\nFor a more detailed information about the "
+ "domain the optional argument details can be specified. A "
+ "possible details value can be one of the following six "
+ "keywords:\n") + mklst(details)),
+ formatter_class=RawDescriptionHelpFormatter)
+ di.add_argument('fqdn', help=_('a fully qualified domain name'))
+ di.add_argument('-d', choices=details, dest='details', metavar='DETAILS',
+ help=_('additionally details to display'))
+ di.set_defaults(func=domain_info, scmd='domaininfo')
+
+ do = a('domainnote', aliases=('do',),
+ help=_('set, update or delete the note of the given domain'),
+ epilog=_('With this subcommand, it is possible to attach a note to '
+ 'the specified domain. Without an argument, an existing '
+ 'note is removed.'))
+ do.add_argument('fqdn', help=_('a fully qualified domain name'))
+ do.add_argument('-n', metavar='NOTE', dest='note',
+ help=_('the note that should be set'))
+ do.set_defaults(func=domain_note, scmd='domainnote')
+
+ dq = a('domainquota', aliases=('dq',),
+ help=_('update the quota limit of the specified domain'),
+ epilog=fill(_("This subcommand is used to configure a new quota "
+ "limit for the accounts of the domain - not for the domain "
+ "itself.\n\nThe default quota limit for accounts is defined "
+ "in the vmm.cfg (domain.quota_bytes and "
+ "domain.quota_messages).\n\nThe new quota limit will affect "
+ "only those accounts for which the limit has not been "
+ "overridden. If you want to restore the default to all "
+ "accounts, you may pass the optional argument --force. When "
+ "the argument messages was omitted the default number of "
+ "messages 0 (zero) will be applied.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dq.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dq.add_argument('storage', type=quota_storage,
+ help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+ 'or 2048M',))
+ dq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+ dest='messages',
+ help=_('quota limit in number of messages (default: 0)'))
+ dq.add_argument('--force', action='store_true',
+ help=_('enforce the limit for all accounts'))
+ dq.set_defaults(func=domain_quota, scmd='domainquota')
+
+ ds = a('domainservices', aliases=('ds',),
+ help=_('enables the specified services and disables all not '
+ 'specified services of the given domain'),
+ epilog=fill(_("To define which services could be used by the users "
+ "of the domain — with the given fqdn — use this "
+ "subcommand.\n\nEach specified service will be enabled/"
+ "usable. All other services will be deactivated/unusable. "
+ "Possible service names are: imap, pop3, sieve and smtp.\nThe "
+ "new service set will affect only those accounts for which "
+ "the set has not been overridden. If you want to restore the "
+ "default to all accounts, you may pass --force.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ds.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ds.add_argument('-s', choices=SERVICES,
+ help=_('services which should be usable'),
+ metavar='SERVICE', nargs='+', dest='services')
+ ds.add_argument('--force', action='store_true',
+ help=_('enforce the service set for all accounts'))
+ ds.set_defaults(func=domain_services, scmd='domainservices')
+
+ dt = a('domaintransport', aliases=('dt',),
+ help=_('update the transport of the specified domain'),
+ epilog=fill(_("A new transport for the indicated domain can be set "
+ "with this subcommand.\n\nThe new transport will affect only "
+ "those accounts for which the transport has not been "
+ "overridden. If you want to restore the default to all "
+ "accounts, you may pass --force.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dt.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dt.add_argument('transport', help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ dt.add_argument('--force', action='store_true',
+ help=_('enforce the transport for all accounts'))
+ dt.set_defaults(func=domain_transport, scmd='domaintransport')
+
+ dd = a('domaindelete', aliases=('dd',),
+ help=_('delete the given domain and all its alias domains'),
+ epilog=fill(_("This subcommand deletes the domain specified by "
+ "fqdn.\n\nIf there are accounts, aliases and/or relocated "
+ "users assigned to the given domain, vmm will abort the "
+ "requested operation and show an error message. If you know, "
+ "what you are doing, you can specify the optional argument "
+ "--force.\n\nIf you really always know what you are doing, "
+ "edit your vmm.cfg and set the option domain.force_deletion "
+ "to true.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dd.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dd.add_argument('--force', action='store_true',
+ help=_('also delete all accounts, aliases and/or '
+ 'relocated users'))
+ dd.set_defaults(func=domain_delete, scmd='domaindelete')
+
+ ###
+ # alias domain subcommands
+ ###
+ ada = a('aliasdomainadd', aliases=('ada',),
+ help=_('create a new alias for an existing domain'),
+ epilog=_('This subcommand adds the new alias domain (fqdn) to '
+ 'the destination domain that should be aliased.'))
+ ada.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ada.add_argument('destination',
+ help=_('the fqdn of the destination domain'))
+ ada.set_defaults(func=aliasdomain_add, scmd='aliasdomainadd')
+
+ adi = a('aliasdomaininfo', aliases=('adi',),
+ help=_('show the destination of the given alias domain'),
+ epilog=_('This subcommand shows to which domain the alias domain '
+ 'fqdn is assigned to.'))
+ adi.add_argument('fqdn', help=_('a fully qualified domain name'))
+ adi.set_defaults(func=aliasdomain_info, scmd='aliasdomaininfo')
+
+ ads = a('aliasdomainswitch', aliases=('ads',),
+ help=_('assign the given alias domain to an other domain'),
+ epilog=_('If the destination of the existing alias domain fqdn '
+ 'should be switched to another destination use this '
+ 'subcommand.'))
+ ads.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ads.add_argument('destination',
+ help=_('the fqdn of the destination domain'))
+ ads.set_defaults(func=aliasdomain_switch, scmd='aliasdomainswitch')
+
+ add = a('aliasdomaindelete', aliases=('add',),
+ help=_('delete the specified alias domain'),
+ epilog=_('Use this subcommand if the alias domain fqdn should be '
+ 'removed.'))
+ add.add_argument('fqdn', help=_('a fully qualified domain name'))
+ add.set_defaults(func=aliasdomain_delete, scmd='aliasdomaindelete')
+
+ ###
+ # account subcommands
+ ###
+ ua = a('useradd', aliases=('ua',),
+ help=_('create a new e-mail user with the given address'),
+ epilog=fill(_('Use this subcommand to create a new e-mail account '
+ 'for the given address.\n\nIf the password is not provided, '
+ 'vmm will prompt for it interactively. When no password is '
+ 'provided and account.random_password is set to true, vmm '
+ 'will generate a random password and print it to stdout '
+ 'after the account has been created.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ ua.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ua.add_argument('-p', metavar='PASSWORD', dest='password',
+ help=_("the new user's password"))
+ ua.set_defaults(func=user_add, scmd='useradd')
+
+ details = ('aliases', 'du', 'full')
+ ui = a('userinfo', aliases=('ui',),
+ help=_('display information about the given address'),
+ epilog=fill(_('This subcommand displays some information about '
+ 'the account specified by the given address.\n\nIf the '
+ 'optional argument details is given some more information '
+ 'will be displayed.\nPossible values for details are:\n') +
+ mklst(details)),
+ formatter_class=RawDescriptionHelpFormatter)
+ ui.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ui.add_argument('-d', choices=details, metavar='DETAILS', dest='details',
+ help=_('additionally details to display'))
+ ui.set_defaults(func=user_info, scmd='userinfo')
+
+ un = a('username', aliases=('un',),
+ help=_('set, update or delete the real name for an address'),
+ epilog=fill(_("The user's real name can be set/updated with this "
+ "subcommand.\n\nIf no name is given, the value stored for the "
+ "account is erased.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ un.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ un.add_argument('-n', help=_("a user's real name"), metavar='NAME',
+ dest='name')
+ un.set_defaults(func=user_name, scmd='username')
+
+ uo = a('usernote', aliases=('uo',),
+ help=_('set, update or delete the note of the given address'),
+ epilog=_('With this subcommand, it is possible to attach a note to '
+ 'the specified account. Without the note argument, an '
+ 'existing note is removed.'))
+ uo.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ uo.add_argument('-n', metavar='NOTE', dest='note',
+ help=_('the note that should be set'))
+ uo.set_defaults(func=user_note, scmd='usernote')
+
+ up = a('userpassword', aliases=('up',),
+ help=_('update the password for the given address'),
+ epilog=fill(_("The password of an account can be updated with this "
+ "subcommand.\n\nIf no password was provided, vmm will prompt "
+ "for it interactively.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ up.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ up.add_argument('-p', metavar='PASSWORD', dest='password',
+ help=_("the user's new password"))
+ up.set_defaults(func=user_password, scmd='userpassword')
+
+ uq = a('userquota', aliases=('uq',),
+ help=_('update the quota limit for the given address'),
+ epilog=fill(_("This subcommand is used to set a new quota limit "
+ "for the given account.\n\nWhen the argument messages was "
+ "omitted the default number of messages 0 (zero) will be "
+ "applied.\n\nInstead of a storage limit pass the keyword "
+ "'domain' to remove the account-specific override, causing "
+ "the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ uq.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ uq.add_argument('storage', type=quota_storage,
+ help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+ 'or 2048M'))
+ uq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+ dest='messages',
+ help=_('quota limit in number of messages (default: 0)'))
+ uq.set_defaults(func=user_quota, scmd='userquota')
+
+ us = a('userservices', aliases=('us',),
+ help=_('enable the specified services and disables all not '
+ 'specified services'),
+ epilog=fill(_("To grant a user access to the specified service(s), "
+ "use this command.\n\nAll omitted services will be "
+ "deactivated/unusable for the user with the given "
+ "address.\n\nInstead of any service pass the keyword "
+ "'domain' to remove the account-specific override, causing "
+ "the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ us.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ us.add_argument('-s', choices=SERVICES + ('domain',),
+ help=_('services which should be usable'),
+ metavar='SERVICE', nargs='+', dest='services')
+ us.set_defaults(func=user_services, scmd='userservices')
+
+ ut = a('usertransport', aliases=('ut',),
+ help=_('update the transport of the given address'),
+ epilog=fill(_("A different transport for an account can be "
+ "specified with this subcommand.\n\nInstead of a transport "
+ "pass the keyword 'domain' to remove the account-specific "
+ "override, causing the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ut.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ut.add_argument('transport', help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ ut.set_defaults(func=user_transport, scmd='usertransport')
+
+ ud = a('userdelete', aliases=('ud',),
+ help=_('delete the specified user'),
+ epilog=fill(_('Use this subcommand to delete the account with the '
+ 'given address.\n\nIf there are one or more aliases with an '
+ 'identical destination address, vmm will abort the requested '
+ 'operation and show an error message. To prevent this, '
+ 'give the optional argument --force.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ ud.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ud.add_argument('--force', action='store_true',
+ help=_('also delete assigned alias addresses'))
+ ud.set_defaults(func=user_delete, scmd='userdelete')
+
+ ###
+ # alias subcommands
+ ###
+ aa = a('aliasadd', aliases=('aa',),
+ help=_('create a new alias e-mail address with one or more '
+ 'destinations'),
+ epilog=fill(_("This subcommand is used to create a new alias "
+ "address with one or more destination addresses.\n\nWithin "
+ "the destination address, the placeholders %n, %d, and %= "
+ "will be replaced by the local part, the domain, or the "
+ "email address with '@' replaced by '=' respectively. In "
+ "combination with alias domains, this enables "
+ "domain-specific destinations.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ aa.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ aa.add_argument('destination', nargs='+',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ aa.set_defaults(func=alias_add, scmd='aliasadd')
+
+ ai = a('aliasinfo', aliases=('ai',),
+ help=_('show the destination(s) of the specified alias'),
+ epilog=_('Information about the alias with the given address can '
+ 'be displayed with this subcommand.'))
+ ai.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ ai.set_defaults(func=alias_info, scmd='aliasinfo')
+
+ ad = a('aliasdelete', aliases=('ad',),
+ help=_('delete the specified alias e-mail address or one of its '
+ 'destinations'),
+ epilog=fill(_("This subcommand is used to delete one or multiple "
+ "destinations from the alias with the given address.\n\nWhen "
+ "no destination address was specified the alias with all its "
+ "destinations will be deleted.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ad.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ ad.add_argument('destination', nargs='*',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ ad.set_defaults(func=alias_delete, scmd='aliasdelete')
+
+ ###
+ # catch-all subcommands
+ ###
+ caa = a('catchalladd', aliases=('caa',),
+ help=_('add one or more catch-all destinations for a domain'),
+ epilog=fill(_('This subcommand allows to specify destination '
+ 'addresses for a domain, which shall receive mail addressed '
+ 'to unknown local parts within that domain. Those catch-all '
+ 'aliases hence "catch all" mail to any address in the domain '
+ '(unless a more specific alias, mailbox or relocated entry '
+ 'exists).\n\nWARNING: Catch-all addresses can cause mail '
+ 'server flooding because spammers like to deliver mail to '
+ 'all possible combinations of names, e.g. to all addresses '
+ 'between abba@example.org and zztop@example.org.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ caa.add_argument('fqdn', help=_('a fully qualified domain name'))
+ caa.add_argument('destination', nargs='+',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ caa.set_defaults(func=catchall_add, scmd='catchalladd')
+
+ cai = a('catchallinfo', aliases=('cai',),
+ help=_('show the catch-all destination(s) of the specified '
+ 'domain'),
+ epilog=_('This subcommand displays information about catch-all '
+ 'aliases defined for a domain.'))
+ cai.add_argument('fqdn', help=_('a fully qualified domain name'))
+ cai.set_defaults(func=catchall_info, scmd='catchallinfo')
+
+ cad = a('catchalldelete', aliases=('cad',),
+ help=_("delete the specified catch-all destination or all of a "
+ "domain's destinations"),
+ epilog=_('With this subcommand, catch-all aliases defined for a '
+ 'domain can be removed, either all of them, or those '
+ 'destinations which were specified explicitly.'))
+ cad.add_argument('fqdn', help=_('a fully qualified domain name'))
+ cad.add_argument('destination', nargs='*',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ cad.set_defaults(func=catchall_delete, scmd='catchalldelete')
+
+ ###
+ # relocated subcommands
+ ###
+ ra = a('relocatedadd', aliases=('ra',),
+ help=_('create a new record for a relocated user'),
+ epilog=_("A new relocated user can be created with this "
+ "subcommand."))
+ ra.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ ra.add_argument('newaddress',
+ help=_('e-mail address where the user can be reached now'))
+ ra.set_defaults(func=relocated_add, scmd='relocatedadd')
+
+ ri = a('relocatedinfo', aliases=('ri',),
+ help=_('print information about a relocated user'),
+ epilog=_('This subcommand shows the new address of the relocated '
+ 'user with the given address.'))
+ ri.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ ri.set_defaults(func=relocated_info, scmd='relocatedinfo')
+
+ rd = a('relocateddelete', aliases=('rd',),
+ help=_('delete the record of the relocated user'),
+ epilog=_('Use this subcommand in order to delete the relocated '
+ 'user with the given address.'))
+ rd.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ rd.set_defaults(func=relocated_delete, scmd='relocateddelete')
+
+ txt_wrpr.replace_whitespace = old_rw
+ return parser
def _get_order(ctx):
"""returns a tuple with (key, 1||0) tuples. Used by functions, which
get a dict from the handler."""
order = ()
- if ctx.scmd == 'domaininfo':
- order = ((u'domain name', 0), (u'gid', 1), (u'domain directory', 0),
- (u'quota limit/user', 0), (u'active services', 0),
- (u'transport', 0), (u'alias domains', 0), (u'accounts', 0),
- (u'aliases', 0), (u'relocated', 0), (u'catch-all dests', 0))
- elif ctx.scmd == 'userinfo':
- if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
+ if ctx.args.scmd == 'domaininfo':
+ order = (('domain name', 0), ('gid', 1), ('domain directory', 0),
+ ('quota limit/user', 0), ('active services', 0),
+ ('transport', 0), ('alias domains', 0), ('accounts', 0),
+ ('aliases', 0), ('relocated', 0), ('catch-all dests', 0))
+ elif ctx.args.scmd == 'userinfo':
+ if ctx.args.details in ('du', 'full') or \
ctx.cget('account.disk_usage'):
- order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
- (u'home', 0), (u'mail_location', 0),
- (u'quota storage', 0), (u'quota messages', 0),
- (u'disk usage', 0), (u'transport', 0), (u'smtp', 1),
- (u'pop3', 1), (u'imap', 1), (u'sieve', 1))
+ order = (('address', 0), ('name', 0), ('uid', 1), ('gid', 1),
+ ('home', 0), ('mail_location', 0),
+ ('quota storage', 0), ('quota messages', 0),
+ ('disk usage', 0), ('transport', 0), ('smtp', 1),
+ ('pop3', 1), ('imap', 1), ('sieve', 1))
else:
- order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
- (u'home', 0), (u'mail_location', 0),
- (u'quota storage', 0), (u'quota messages', 0),
- (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
- (u'imap', 1), (u'sieve', 1))
- elif ctx.scmd == 'getuser':
- order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
+ order = (('address', 0), ('name', 0), ('uid', 1), ('gid', 1),
+ ('home', 0), ('mail_location', 0),
+ ('quota storage', 0), ('quota messages', 0),
+ ('transport', 0), ('smtp', 1), ('pop3', 1),
+ ('imap', 1), ('sieve', 1))
+ elif ctx.args.scmd == 'getuser':
+ order = (('uid', 1), ('gid', 1), ('address', 0))
return order
@@ -950,43 +1042,37 @@
}
else:
q_usage = {
- 'used': locale.format('%d', used, True).decode(ENCODING,
- 'replace'),
- 'limit': locale.format('%d', limit, True).decode(ENCODING,
- 'replace'),
+ 'used': locale.format('%d', used, True),
+ 'limit': locale.format('%d', limit, True),
}
if limit:
q_usage['percent'] = locale.format('%6.2f', 100. / limit * used, True)
else:
q_usage['percent'] = locale.format('%6.2f', 0, True)
- # Py25: fmt = format_domain_default if domaindefault else lambda s: s
- if domaindefault:
- fmt = format_domain_default
- else:
- fmt = lambda s: s
+ fmt = format_domain_default if domaindefault else lambda s: s
# TP: e.g.: [ 0.00%] 21.09 KiB/1.00 GiB
- return fmt(_(u'[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
+ return fmt(_('[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
def _print_info(ctx, info, title):
"""Print info dicts."""
# TP: used in e.g. 'Domain information' or 'Account information'
- msg = u'%s %s' % (title, _(u'information'))
- w_std(msg, u'-' * len(msg))
+ msg = '%s %s' % (title, _('information'))
+ w_std(msg, '-' * len(msg))
for key, upper in _get_order(ctx):
if upper:
- w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key]))
+ w_std('\t%s: %s' % (key.upper().ljust(17, '.'), info[key]))
else:
- w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key]))
- print
+ w_std('\t%s: %s' % (key.title().ljust(17, '.'), info[key]))
+ print()
note = info.get('note')
if note:
_print_note(note + '\n')
def _print_note(note):
- msg = _(u'Note')
- w_std(msg, u'-' * len(msg))
+ msg = _('Note')
+ w_std(msg, '-' * len(msg))
old_ii = txt_wrpr.initial_indent
old_si = txt_wrpr.subsequent_indent
txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
@@ -1001,63 +1087,61 @@
def _print_list(alist, title):
"""Print a list."""
# TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
- msg = u'%s %s' % (_(u'Existing'), title)
- w_std(msg, u'-' * len(msg))
+ msg = '%s %s' % (_('Existing'), title)
+ w_std(msg, '-' * len(msg))
if alist:
- if title != _(u'alias domains'):
- w_std(*(u'\t%s' % item for item in alist))
+ if title != _('alias domains'):
+ w_std(*('\t%s' % item for item in alist))
else:
for domain in alist:
if not domain.startswith('xn--'):
- w_std(u'\t%s' % domain)
+ w_std('\t%s' % domain)
else:
- w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
- print
+ w_std('\t%s (%s)' % (domain,
+ domain.encode('utf-8').decode('idna')))
+ print()
else:
- w_std(_(u'\tNone'), '')
+ w_std(_('\tNone'), '')
def _print_aliase_info(alias, destinations):
"""Print the alias address and all its destinations"""
- title = _(u'Alias information')
- w_std(title, u'-' * len(title))
- w_std(_(u'\tMail for %s will be redirected to:') % alias)
- w_std(*(u'\t * %s' % dest for dest in destinations))
- print
+ title = _('Alias information')
+ w_std(title, '-' * len(title))
+ w_std(_('\tMail for %s will be redirected to:') % alias)
+ w_std(*('\t * %s' % dest for dest in destinations))
+ print()
def _print_catchall_info(domain, destinations):
"""Print the catchall destinations of a domain"""
- title = _(u'Catch-all information')
- w_std(title, u'-' * len(title))
- w_std(_(u'\tMail to unknown local-parts in domain %s will be sent to:')
+ title = _('Catch-all information')
+ w_std(title, '-' * len(title))
+ w_std(_('\tMail to unknown local-parts in domain %s will be sent to:')
% domain)
- w_std(*(u'\t * %s' % dest for dest in destinations))
- print
+ w_std(*('\t * %s' % dest for dest in destinations))
+ print()
def _print_relocated_info(**kwargs):
"""Print the old and new addresses of a relocated user."""
- title = _(u'Relocated information')
- w_std(title, u'-' * len(title))
- w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
+ title = _('Relocated information')
+ w_std(title, '-' * len(title))
+ w_std(_("\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
def _format_domain(domain, main=True):
"""format (prefix/convert) the domain name."""
if domain.startswith('xn--'):
- domain = u'%s (%s)' % (domain, domain.decode('idna'))
+ domain = '%s (%s)' % (domain, domain.encode('utf-8').decode('idna'))
if main:
- return u'\t[+] %s' % domain
- return u'\t[-] %s' % domain
+ return '\t[+] %s' % domain
+ return '\t[-] %s' % domain
def _print_domain_list(dids, domains, matching):
"""Print a list of (matching) domains/alias domains."""
- if matching:
- title = _(u'Matching domains')
- else:
- title = _(u'Existing domains')
+ title = _('Matching domains') if matching else _('Existing domains')
w_std(title, '-' * len(title))
if domains:
for did in dids:
@@ -1067,7 +1151,7 @@
w_std(*(_format_domain(a, False) for a in domains[did][1:]))
else:
w_std(_('\tNone'))
- print
+ print()
def _print_address_list(which, dids, addresses, matching):
@@ -1083,9 +1167,9 @@
}
try:
if matching:
- title = _(u'Matching %s') % _trans[which]
+ title = _('Matching %s') % _trans[which]
else:
- title = _(u'Existing %s') % _trans[which]
+ title = _('Existing %s') % _trans[which]
w_std(title, '-' * len(title))
except KeyError:
raise VMMError(_("Invalid address type for list: '%s'") % which,
@@ -1111,15 +1195,16 @@
w_std('\t%s %s' % (leader, addr))
else:
w_std(_('\tNone'))
- print
+ print()
def _print_aliasdomain_info(info):
"""Print alias domain information."""
- title = _(u'Alias domain information')
+ title = _('Alias domain information')
for key in ('alias', 'domain'):
if info[key].startswith('xn--'):
- info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
+ info[key] = '%s (%s)' % (info[key],
+ info[key].encode(ENCODING).decode('idna'))
w_std(title, '-' * len(title),
_('\tThe alias domain %(alias)s belongs to:\n\t * %(domain)s') %
info, '')
--- a/VirtualMailManager/common.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/common.py Sun Jan 06 00:09:47 2013 +0000
@@ -36,9 +36,9 @@
def get_unicode(string):
"""Converts `string` to `unicode`, if necessary."""
- if isinstance(string, unicode):
+ if isinstance(string, str):
return string
- return unicode(string, ENCODING, 'replace')
+ return str(string, ENCODING, 'replace')
def lisdir(path):
@@ -60,44 +60,44 @@
"""
binary = expand_path(binary)
if not os.path.isfile(binary):
- raise VMMError(_(u"No such file: '%s'") % get_unicode(binary),
+ raise VMMError(_("No such file: '%s'") % get_unicode(binary),
NO_SUCH_BINARY)
if not os.access(binary, os.X_OK):
- raise VMMError(_(u"File is not executable: '%s'") %
+ raise VMMError(_("File is not executable: '%s'") %
get_unicode(binary), NOT_EXECUTABLE)
return binary
def human_size(size):
"""Converts the `size` in bytes in human readable format."""
- if not isinstance(size, (long, int)):
+ if not isinstance(size, int):
try:
- size = long(size)
+ size = int(size)
except ValueError:
- raise TypeError("'size' must be a positive long or int.")
+ raise TypeError("'size' must be a positive integer.")
if size < 0:
- raise ValueError("'size' must be a positive long or int.")
+ raise ValueError("'size' must be a positive integer.")
if size < 1024:
return str(size)
# TP: abbreviations of gibibyte, tebibyte kibibyte and mebibyte
- prefix_multiply = ((_(u'TiB'), 1 << 40), (_(u'GiB'), 1 << 30),
- (_(u'MiB'), 1 << 20), (_(u'KiB'), 1 << 10))
+ prefix_multiply = ((_('TiB'), 1 << 40), (_('GiB'), 1 << 30),
+ (_('MiB'), 1 << 20), (_('KiB'), 1 << 10))
for prefix, multiply in prefix_multiply:
if size >= multiply:
# TP: e.g.: '%(size)s %(prefix)s' -> '118.30 MiB'
- return _(u'%(size)s %(prefix)s') % {
+ return _('%(size)s %(prefix)s') % {
'size': locale.format('%.2f', float(size) / multiply,
- True).decode(ENCODING, 'replace'),
+ True),
'prefix': prefix}
def size_in_bytes(size):
- """Converts the string `size` to a long (size in bytes).
+ """Converts the string `size` to an integer (size in bytes).
The string `size` can be suffixed with *b* (bytes), *k* (kilobytes),
*M* (megabytes) or *G* (gigabytes).
"""
- if not isinstance(size, basestring) or not size:
+ if not isinstance(size, str) or not size:
raise TypeError('size must be a non empty string.')
if size[-1].upper() in ('B', 'K', 'M', 'G'):
try:
@@ -108,11 +108,11 @@
if unit == 'B':
return num
elif unit == 'K':
- return num << 10L
+ return num << 10
elif unit == 'M':
- return num << 20L
+ return num << 20
else:
- return num << 30L
+ return num << 30
else:
try:
num = int(size)
@@ -136,8 +136,8 @@
"""
if transport.transport in ('virtual', 'virtual:') and \
not maillocation.postfix:
- raise VMMError(_(u"Invalid transport '%(transport)s' for mailbox "
- u"format '%(mbfmt)s'.") %
+ raise VMMError(_("Invalid transport '%(transport)s' for mailbox "
+ "format '%(mbfmt)s'.") %
{'transport': transport.transport,
'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
@@ -183,21 +183,22 @@
def version_str(version):
"""Converts a Dovecot version previously converted with version_hex back to
a string.
- Raises a `TypeError` if *version* is not an int/long.
+ Raises a `TypeError` if *version* is not an integer.
Raises a `ValueError` if *version* is an incorrect int version.
"""
global _version_cache
if version in _version_cache:
return _version_cache[version]
- if not isinstance(version, (int, long)):
- raise TypeError('Argument is not a int/long: %r', version)
+ if not isinstance(version, int):
+ raise TypeError('Argument is not a integer: %r', version)
major = (version >> 28) & 0xFF
minor = (version >> 20) & 0xFF
patch = (version >> 12) & 0xFF
level = (version >> 8) & 0x0F
serial = version & 0xFF
- levels = dict(zip(_version_level.values(), _version_level.keys()))
+ levels = dict(list(zip(list(_version_level.values()),
+ list(_version_level.keys()))))
if level == 0xF and not serial:
version_string = '%u.%u.%u' % (major, minor, patch)
elif level in levels and not patch:
@@ -214,7 +215,7 @@
# TP: [domain default] indicates that a user's setting is the same as
# configured in the user's domain.
# e.g.: [ 0.84%] 42/5,000 [domain default]
- return _(u'%s [domain default]') % domaindata
+ return _('%s [domain default]') % domaindata
def search_addresses(dbh, typelimit=None, lpattern=None, llike=False,
--- a/VirtualMailManager/config.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/config.py Sun Jan 06 00:09:47 2013 +0000
@@ -8,10 +8,12 @@
VMM's configuration module for simplified configuration access.
"""
-from ConfigParser import \
+import collections
+
+from configparser import \
Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \
ParsingError, RawConfigParser
-from cStringIO import StringIO
+from io import StringIO
from VirtualMailManager.common import VERSION_RE, \
exec_ok, expand_path, get_unicode, lisdir, size_in_bytes, version_hex
@@ -83,10 +85,10 @@
"""
if isinstance(value, bool):
return value
- if value.lower() in self._boolean_states:
- return self._boolean_states[value.lower()]
+ if value.lower() in self.BOOLEAN_STATES:
+ return self.BOOLEAN_STATES[value.lower()]
else:
- raise ConfigValueError(_(u"Not a boolean: '%s'") %
+ raise ConfigValueError(_("Not a boolean: '%s'") %
get_unicode(value))
def getboolean(self, section, option):
@@ -103,9 +105,9 @@
tmp = self.get(section, option)
if isinstance(tmp, bool):
return tmp
- if not tmp.lower() in self._boolean_states:
+ if not tmp.lower() in self.BOOLEAN_STATES:
raise ValueError('Not a boolean: %s' % tmp)
- return self._boolean_states[tmp.lower()]
+ return self.BOOLEAN_STATES[tmp.lower()]
def _get_section_option(self, section_option):
"""splits ``section_option`` (section.option) in two parts and
@@ -124,8 +126,8 @@
sect_opt = section_option.lower().split('.')
# TODO: cache it
if len(sect_opt) != 2 or not sect_opt[0] or not sect_opt[1]:
- raise BadOptionError(_(u"Bad format: '%s' - expected: "
- u"section.option") %
+ raise BadOptionError(_("Bad format: '%s' - expected: "
+ "section.option") %
get_unicode(section_option))
if not sect_opt[0] in self._cfg:
raise NoSectionError(sect_opt[0])
@@ -143,14 +145,14 @@
raise NoSectionError(section)
else:
return ((k, self._cfg[section][k].default)
- for k in self._cfg[section].iterkeys())
+ for k in self._cfg[section].keys())
# still here? Get defaults and merge defaults with configured setting
defaults = dict((k, self._cfg[section][k].default)
- for k in self._cfg[section].iterkeys())
+ for k in self._cfg[section].keys())
defaults.update(sect)
if '__name__' in defaults:
del defaults['__name__']
- return defaults.iteritems()
+ return iter(defaults.items())
def dget(self, option):
"""Returns the value of the `option`.
@@ -216,7 +218,7 @@
def sections(self):
"""Returns an iterator object for all configuration sections."""
- return self._cfg.iterkeys()
+ return iter(self._cfg.keys())
class LazyConfigOption(object):
@@ -246,15 +248,12 @@
check the value, when `LazyConfig.set()` is called.
"""
self.__cls = cls
- if not default is None: # enforce the type of the default value
- self.__default = self.__cls(default)
- else:
- self.__default = default
- if not callable(getter):
+ self.__default = default if default is None else self.__cls(default)
+ if not isinstance(getter, collections.Callable):
raise TypeError('getter has to be a callable, got a %r' %
getter.__class__.__name__)
self.__getter = getter
- if validate and not callable(validate):
+ if validate and not isinstance(validate, collections.Callable):
raise TypeError('validate has to be callable or None, got a %r' %
validate.__class__.__name__)
self.__validate = validate
@@ -337,9 +336,9 @@
},
'mailbox': {
'folders': LCO(str, 'Drafts:Sent:Templates:Trash',
- self.unicode),
+ self.str),
'format': LCO(str, 'maildir', self.get, check_mailbox_format),
- 'root': LCO(str, 'Maildir', self.unicode),
+ 'root': LCO(str, 'Maildir', self.str),
'subscribe': LCO(bool_t, True, self.getboolean),
},
'misc': {
@@ -360,12 +359,11 @@
Raises a ConfigError if the configuration syntax is
invalid.
"""
- self._cfg_file = open(self._cfg_filename, 'r')
- try:
- self.readfp(self._cfg_file)
- except (MissingSectionHeaderError, ParsingError), err:
- raise ConfigError(str(err), CONF_ERROR)
- self._cfg_file.close()
+ with open(self._cfg_filename, 'r', encoding='utf-8') as self._cfg_file:
+ try:
+ self.readfp(self._cfg_file)
+ except (MissingSectionHeaderError, ParsingError) as err:
+ raise ConfigError(str(err), CONF_ERROR)
def check(self):
"""Performs a configuration check.
@@ -374,9 +372,9 @@
Or some settings have a invalid value.
"""
def iter_dict():
- for section, options in self._missing.iteritems():
- errmsg.write(_(u'* Section: %s\n') % section)
- errmsg.writelines(u' %s\n' % option for option in options)
+ for section, options in self._missing.items():
+ errmsg.write(_('* Section: %s\n') % section)
+ errmsg.writelines(' %s\n' % option for option in options)
self._missing.clear()
errmsg = None
@@ -385,19 +383,19 @@
'dovecot_version' in self._missing['misc']
if self._missing:
errmsg = StringIO()
- errmsg.write(_(u'Check of configuration file %s failed.\n') %
+ errmsg.write(_('Check of configuration file %s failed.\n') %
self._cfg_filename)
- errmsg.write(_(u'Missing options, which have no default value.\n'))
+ errmsg.write(_('Missing options, which have no default value.\n'))
iter_dict()
self._chk_possible_values(miss_vers)
if self._missing:
if not errmsg:
errmsg = StringIO()
- errmsg.write(_(u'Check of configuration file %s failed.\n') %
+ errmsg.write(_('Check of configuration file %s failed.\n') %
self._cfg_filename)
- errmsg.write(_(u'Invalid configuration values.\n'))
+ errmsg.write(_('Invalid configuration values.\n'))
else:
- errmsg.write('\n' + _(u'Invalid configuration values.\n'))
+ errmsg.write('\n' + _('Invalid configuration values.\n'))
iter_dict()
if errmsg:
raise ConfigError(errmsg.getvalue(), CONF_ERROR)
@@ -409,10 +407,10 @@
def get_in_bytes(self, section, option):
"""Converts the size value (e.g.: 1024k) from the *option*'s
- value to a long"""
+ value to a integer"""
return size_in_bytes(self.get(section, option))
- def unicode(self, section, option):
+ def str(self, section, option):
"""Returns the value of the `option` from `section`, converted
to Unicode."""
return get_unicode(self.get(section, option))
@@ -421,9 +419,9 @@
"""Checks all section's options for settings w/o a default
value. Missing items will be stored in _missing.
"""
- for section in self._cfg.iterkeys():
+ for section in self._cfg.keys():
missing = []
- for option, value in self._cfg[section].iteritems():
+ for option, value in self._cfg[section].items():
if (value.default is None and
not RawConfigParser.has_option(self, section, option)):
missing.append(option)
@@ -436,30 +434,30 @@
value = self.get('misc', 'dovecot_version')
if not VERSION_RE.match(value):
self._missing['misc'] = ['version: ' +
- _(u"Not a valid Dovecot version: '%s'") % value]
+ _("Not a valid Dovecot version: '%s'") % value]
# section database
db_err = []
value = self.dget('database.module').lower()
if value not in DB_MODULES:
db_err.append('module: ' +
- _(u"Unsupported database module: '%s'") % value)
+ _("Unsupported database module: '%s'") % value)
if value == 'psycopg2':
value = self.dget('database.sslmode')
if value not in DB_SSL_MODES:
db_err.append('sslmode: ' +
- _(u"Unknown pgsql SSL mode: '%s'") % value)
+ _("Unknown pgsql SSL mode: '%s'") % value)
if db_err:
self._missing['database'] = db_err
# section mailbox
value = self.dget('mailbox.format')
if not known_format(value):
self._missing['mailbox'] = ['format: ' +
- _(u"Unsupported mailbox format: '%s'") % value]
+ _("Unsupported mailbox format: '%s'") % value]
# section domain
try:
value = self.dget('domain.quota_bytes')
- except (ValueError, TypeError), err:
- self._missing['domain'] = [u'quota_bytes: ' + str(err)]
+ except (ValueError, TypeError) as err:
+ self._missing['domain'] = ['quota_bytes: ' + str(err)]
def is_dir(path):
@@ -470,14 +468,14 @@
path = expand_path(path)
if lisdir(path):
return path
- raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path))
+ raise ConfigValueError(_("No such directory: %s") % get_unicode(path))
def check_db_module(module):
"""Check if the *module* is a supported pgsql module."""
if module.lower() in DB_MODULES:
return module
- raise ConfigValueError(_(u"Unsupported database module: '%s'") %
+ raise ConfigValueError(_("Unsupported database module: '%s'") %
get_unicode(module))
@@ -485,7 +483,7 @@
"""Check if the *ssl_mode* is one of the SSL modes, known by pgsql."""
if ssl_mode in DB_SSL_MODES:
return ssl_mode
- raise ConfigValueError(_(u"Unknown pgsql SSL mode: '%s'") %
+ raise ConfigValueError(_("Unknown pgsql SSL mode: '%s'") %
get_unicode(ssl_mode))
@@ -498,7 +496,7 @@
format = format.lower()
if known_format(format):
return format
- raise ConfigValueError(_(u"Unsupported mailbox format: '%s'") %
+ raise ConfigValueError(_("Unsupported mailbox format: '%s'") %
get_unicode(format))
@@ -508,8 +506,8 @@
Otherwise a `ConfigValueError` will be raised."""
try:
tmp = size_in_bytes(value)
- except (TypeError, ValueError), err:
- raise ConfigValueError(_(u"Not a valid size value: '%s'") %
+ except (TypeError, ValueError) as err:
+ raise ConfigValueError(_("Not a valid size value: '%s'") %
get_unicode(value))
return value
@@ -520,7 +518,7 @@
Otherwise a `ConfigValueError` will be raised.
"""
if not VERSION_RE.match(version_string):
- raise ConfigValueError(_(u"Not a valid Dovecot version: '%s'") %
+ raise ConfigValueError(_("Not a valid Dovecot version: '%s'") %
get_unicode(version_string))
return version_string
@@ -531,7 +529,7 @@
"""
try:
scheme, encoding = _verify_scheme(scheme)
- except VMMError, err: # 'cast' it
+ except VMMError as err: # 'cast' it
raise ConfigValueError(err.msg)
if not encoding:
return scheme
--- a/VirtualMailManager/constants.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/constants.py Sun Jan 06 00:09:47 2013 +0000
@@ -32,8 +32,6 @@
# exit codes
EX_SUCCESS = 0
-EX_MISSING_ARGS = 1
-EX_UNKNOWN_COMMAND = 2
EX_USER_INTERRUPT = 3
--- a/VirtualMailManager/domain.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/domain.py Sun Jan 06 00:09:47 2013 +0000
@@ -18,7 +18,6 @@
from VirtualMailManager.common import validate_transport
from VirtualMailManager.errors import VMMError, DomainError as DomErr
from VirtualMailManager.maillocation import MailLocation
-from VirtualMailManager.pycompat import all, any
from VirtualMailManager.quotalimit import QuotaLimit
from VirtualMailManager.serviceset import ServiceSet
from VirtualMailManager.transport import Transport
@@ -79,7 +78,7 @@
dbc.close()
if result:
if not result[5]:
- raise DomErr(_(u"The domain '%s' is an alias domain.") %
+ raise DomErr(_("The domain '%s' is an alias domain.") %
self._name, DOMAIN_ALIAS_EXISTS)
self._gid, self._directory = result[0], result[4]
self._qlimit = QuotaLimit(self._dbh, qid=result[1])
@@ -114,9 +113,9 @@
result = result[0]
if any(result):
keys = ('account_count', 'alias_count', 'relocated_count')
- raise DomErr(_(u'There are %(account_count)u accounts, '
- u'%(alias_count)u aliases and %(relocated_count)u '
- u'relocated users.') % dict(zip(keys, result)),
+ raise DomErr(_('There are %(account_count)u accounts, '
+ '%(alias_count)u aliases and %(relocated_count)u '
+ 'relocated users.') % dict(list(zip(keys, result))),
ACCOUNT_AND_ALIAS_PRESENT)
def _chk_state(self, must_exist=True):
@@ -126,10 +125,10 @@
- or *must_exist* is `False` and the domain exists
"""
if must_exist and self._new:
- raise DomErr(_(u"The domain '%s' does not exist.") % self._name,
+ raise DomErr(_("The domain '%s' does not exist.") % self._name,
NO_SUCH_DOMAIN)
elif not must_exist and not self._new:
- raise DomErr(_(u"The domain '%s' already exists.") % self._name,
+ raise DomErr(_("The domain '%s' already exists.") % self._name,
DOMAIN_EXISTS)
def _update_tables(self, column, value):
@@ -150,7 +149,7 @@
`column` : basestring
Name of the table column. Currently: qid, ssid and tid
- `value` : long
+ `value` : int
The referenced key
`force` : bool
reset existing users. Default: `False`
@@ -264,7 +263,7 @@
The note, or None to remove
"""
self._chk_state(False)
- assert note is None or isinstance(note, basestring)
+ assert note is None or isinstance(note, str)
self._note = note
def save(self):
@@ -326,8 +325,8 @@
enforce new quota limit for all accounts, default `False`
"""
if cfg_dget('misc.dovecot_version') < 0x10102f00:
- raise VMMError(_(u'PostgreSQL-based dictionary quota requires '
- u'Dovecot >= v1.1.2.'), VMM_ERROR)
+ raise VMMError(_('PostgreSQL-based dictionary quota requires '
+ 'Dovecot >= v1.1.2.'), VMM_ERROR)
self._chk_state()
assert isinstance(quotalimit, QuotaLimit)
if not force and quotalimit == self._qlimit:
@@ -389,7 +388,7 @@
the new note
"""
self._chk_state()
- assert note is None or isinstance(note, basestring)
+ assert note is None or isinstance(note, str)
if note == self._note:
return
self._update_tables('note', note)
@@ -406,7 +405,7 @@
dbc.close()
keys = ('alias domains', 'accounts', 'aliases', 'relocated',
'catch-all dests')
- info = dict(zip(keys, info))
+ info = dict(list(zip(keys, info)))
info['gid'] = self._gid
info['domain name'] = self._name
info['transport'] = self._transport.transport
@@ -433,7 +432,7 @@
dbc.close()
accounts = []
if users:
- addr = u'@'.join
+ addr = '@'.join
_dom = self._name
accounts = [addr((account[0], _dom)) for account in users]
return accounts
@@ -448,7 +447,7 @@
dbc.close()
aliases = []
if addresses:
- addr = u'@'.join
+ addr = '@'.join
_dom = self._name
aliases = [addr((alias[0], _dom)) for alias in addresses]
return aliases
@@ -463,7 +462,7 @@
dbc.close()
relocated = []
if addresses:
- addr = u'@'.join
+ addr = '@'.join
_dom = self._name
relocated = [addr((address[0], _dom)) for address in addresses]
return relocated
@@ -500,11 +499,11 @@
"""
if not RE_DOMAIN.match(domainname):
- domainname = domainname.encode('idna')
+ domainname = domainname.encode('idna').decode()
if len(domainname) > 255:
- raise DomErr(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
+ raise DomErr(_('The domain name is too long'), DOMAIN_TOO_LONG)
if not RE_DOMAIN.match(domainname):
- raise DomErr(_(u"The domain name '%s' is invalid") % domainname,
+ raise DomErr(_("The domain name '%s' is invalid") % domainname,
DOMAIN_INVALID)
return domainname
--- a/VirtualMailManager/emailaddress.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/emailaddress.py Sun Jan 06 00:09:47 2013 +0000
@@ -16,7 +16,7 @@
from VirtualMailManager.errors import DomainError, EmailAddressError as EAErr
-RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]", re.ASCII)
_ = lambda msg: msg
@@ -26,7 +26,7 @@
def __init__(self, address, _validate=True):
"""Creates a new instance from the string/unicode ``address``."""
- assert isinstance(address, basestring)
+ assert isinstance(address, str)
self._localpart = None
self._domainname = None
if _validate:
@@ -70,16 +70,16 @@
parts = address.split('@')
p_len = len(parts)
if p_len < 2:
- raise EAErr(_(u"Missing the '@' sign in address: '%s'") % address,
+ raise EAErr(_("Missing the '@' sign in address: '%s'") % address,
INVALID_ADDRESS)
elif p_len > 2:
- raise EAErr(_(u"Too many '@' signs in address: '%s'") % address,
+ raise EAErr(_("Too many '@' signs in address: '%s'") % address,
INVALID_ADDRESS)
if not parts[0]:
- raise EAErr(_(u"Missing local-part in address: '%s'") % address,
+ raise EAErr(_("Missing local-part in address: '%s'") % address,
LOCALPART_INVALID)
if not parts[1]:
- raise EAErr(_(u"Missing domain name in address: '%s'") % address,
+ raise EAErr(_("Missing domain name in address: '%s'") % address,
DOMAIN_NO_NAME)
self._localpart = check_localpart(parts[0])
self._domainname = check_domainname(parts[1])
@@ -105,7 +105,7 @@
if not _validate:
try:
self._chk_address(address)
- except DomainError, err:
+ except DomainError as err:
if err.code is DOMAIN_INVALID and \
address.split('@')[1] == 'localhost':
self._localhost = True
@@ -142,13 +142,13 @@
invalid characters.
"""
if len(localpart) > 64:
- raise EAErr(_(u"The local-part '%s' is too long.") % localpart,
+ raise EAErr(_("The local-part '%s' is too long.") % localpart,
LOCALPART_TOO_LONG)
invalid_chars = set(RE_LOCALPART.findall(localpart))
if invalid_chars:
- i_chars = u''.join((u'"%s" ' % c for c in invalid_chars))
- raise EAErr(_(u"The local-part '%(l_part)s' contains invalid "
- u"characters: %(i_chars)s") % {'l_part': localpart,
+ i_chars = ''.join(('"%s" ' % c for c in invalid_chars))
+ raise EAErr(_("The local-part '%(l_part)s' contains invalid "
+ "characters: %(i_chars)s") % {'l_part': localpart,
'i_chars': i_chars}, LOCALPART_INVALID)
return localpart
--- a/VirtualMailManager/ext/postconf.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/ext/postconf.py Sun Jan 06 00:09:47 2013 +0000
@@ -25,8 +25,8 @@
class Postconf(object):
"""Wrapper class for Postfix's postconf."""
__slots__ = ('_bin', '_val')
- _parameter_re = re.compile(r'^\w+$')
- _variables_re = re.compile(r'\$\b\w+\b')
+ _parameter_re = re.compile(r'^\w+$', re.ASCII)
+ _variables_re = re.compile(r'\$\b\w+\b', re.ASCII)
def __init__(self, postconf_bin):
"""Creates a new Postconf instance.
@@ -53,7 +53,7 @@
stderr = Popen((self._bin, '-e', parameter + '=' + str(value)),
stderr=PIPE).communicate()[1]
if stderr:
- raise VMMError(stderr.strip(), VMM_ERROR)
+ raise VMMError(stderr.strip().decode(), VMM_ERROR)
def read(self, parameter, expand_vars=True):
"""Returns the parameters value.
@@ -81,8 +81,8 @@
"""Check that the `parameter` looks like a configuration parameter.
If not, a VMMError will be raised."""
if not self.__class__._parameter_re.match(parameter):
- raise VMMError(_(u"The value '%s' does not look like a valid "
- u"Postfix configuration parameter name.") %
+ raise VMMError(_("The value '%s' does not look like a valid "
+ "Postfix configuration parameter name.") %
parameter, VMM_ERROR)
def _expand_vars(self):
@@ -99,7 +99,7 @@
def _expand_multi_vars(self, old_new):
"""Replace all $vars in self._val with their values."""
- for old, new in old_new.iteritems():
+ for old, new in old_new.items():
self._val = self._val.replace('$' + old, new)
def _read(self, parameter):
@@ -107,8 +107,8 @@
stdout, stderr = Popen([self._bin, '-h', parameter], stdout=PIPE,
stderr=PIPE).communicate()
if stderr:
- raise VMMError(stderr.strip(), VMM_ERROR)
- return stdout.strip()
+ raise VMMError(stderr.strip().decode(), VMM_ERROR)
+ return stdout.strip().decode()
def _read_multi(self, parameters):
"""Ask postconf for multiple configuration parameters. Returns a dict
@@ -117,9 +117,9 @@
cmd.extend(parameter[1:] for parameter in parameters)
stdout, stderr = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
if stderr:
- raise VMMError(stderr.strip(), VMM_ERROR)
+ raise VMMError(stderr.strip().decode(), VMM_ERROR)
par_val = {}
- for line in stdout.splitlines():
+ for line in stdout.decode().splitlines():
par, val = line.split(' = ')
par_val[par] = val
return par_val
--- a/VirtualMailManager/handler.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/handler.py Sun Jan 06 00:09:47 2013 +0000
@@ -16,6 +16,7 @@
import re
from shutil import rmtree
+from stat import S_IRGRP, S_IROTH, S_IWGRP, S_IWOTH
from subprocess import Popen, PIPE
from VirtualMailManager.account import Account
@@ -37,7 +38,6 @@
from VirtualMailManager.errors import \
DomainError, NotRootError, PermissionError, VMMError
from VirtualMailManager.mailbox import new as new_mailbox
-from VirtualMailManager.pycompat import all, any
from VirtualMailManager.quotalimit import QuotaLimit
from VirtualMailManager.relocated import Relocated
from VirtualMailManager.serviceset import ServiceSet, SERVICES
@@ -51,9 +51,9 @@
CFG_PATH = '/root:/usr/local/etc:/etc'
RE_DOMAIN_SEARCH = """^[a-z0-9-\.]+$"""
OTHER_TYPES = {
- TYPE_ACCOUNT: (_(u'an account'), ACCOUNT_EXISTS),
- TYPE_ALIAS: (_(u'an alias'), ALIAS_EXISTS),
- TYPE_RELOCATED: (_(u'a relocated user'), RELOCATED_EXISTS),
+ TYPE_ACCOUNT: (_('an account'), ACCOUNT_EXISTS),
+ TYPE_ALIAS: (_('an alias'), ALIAS_EXISTS),
+ TYPE_RELOCATED: (_('a relocated user'), RELOCATED_EXISTS),
}
@@ -79,7 +79,7 @@
self._db_connect = None
if os.geteuid():
- raise NotRootError(_(u"You are not root.\n\tGood bye!\n"),
+ raise NotRootError(_("You are not root.\n\tGood bye!\n"),
CONF_NOPERM)
if self._check_cfg_file():
self._cfg = Cfg(self._cfg_fname)
@@ -99,22 +99,24 @@
self._cfg_fname = tmp
break
if not self._cfg_fname:
- raise VMMError(_(u"Could not find '%(cfg_file)s' in: "
- u"'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
+ raise VMMError(_("Could not find '%(cfg_file)s' in: "
+ "'%(cfg_path)s'") % {'cfg_file': CFG_FILE,
'cfg_path': CFG_PATH}, CONF_NOFILE)
def _check_cfg_file(self):
"""Checks the configuration file, returns bool"""
+ GRPRW = S_IRGRP | S_IWGRP
+ OTHRW = S_IROTH | S_IWOTH
self._find_cfg_file()
fstat = os.stat(self._cfg_fname)
- fmode = int(oct(fstat.st_mode & 0777))
- if fmode % 100 and fstat.st_uid != fstat.st_gid or \
- fmode % 10 and fstat.st_uid == fstat.st_gid:
+ if (fstat.st_uid == fstat.st_gid and fstat.st_mode & OTHRW) or \
+ (fstat.st_uid != fstat.st_gid and fstat.st_mode & (GRPRW | OTHRW)):
# TP: Please keep the backticks around the command. `chmod 0600 …`
- raise PermissionError(_(u"wrong permissions for '%(file)s': "
- u"%(perms)s\n`chmod 0600 %(file)s` would "
- u"be great.") % {'file': self._cfg_fname,
- 'perms': fmode}, CONF_WRONGPERM)
+ raise PermissionError(_("wrong permissions for '%(file)s': "
+ "%(perms)s\n`chmod 0600 %(file)s` would "
+ "be great.") % {'file': self._cfg_fname,
+ 'perms': oct(fstat.st_mode)[-4:]},
+ CONF_WRONGPERM)
else:
return True
@@ -125,23 +127,23 @@
dir_created = False
basedir = self._cfg.dget('misc.base_directory')
if not os.path.exists(basedir):
- old_umask = os.umask(0006)
- os.makedirs(basedir, 0771)
+ old_umask = os.umask(0o006)
+ os.makedirs(basedir, 0o771)
os.chown(basedir, 0, 0)
os.umask(old_umask)
dir_created = True
if not dir_created and not lisdir(basedir):
- raise VMMError(_(u"'%(path)s' is not a directory.\n(%(cfg_file)s: "
- u"section 'misc', option 'base_directory')") %
+ raise VMMError(_("'%(path)s' is not a directory.\n(%(cfg_file)s: "
+ "section 'misc', option 'base_directory')") %
{'path': basedir, 'cfg_file': self._cfg_fname},
NO_SUCH_DIRECTORY)
for opt, val in self._cfg.items('bin'):
try:
exec_ok(val)
- except VMMError, err:
+ except VMMError as err:
if err.code in (NO_SUCH_BINARY, NOT_EXECUTABLE):
- raise VMMError(err.msg + _(u"\n(%(cfg_file)s: section "
- u"'bin', option '%(option)s')") %
+ raise VMMError(err.msg + _("\n(%(cfg_file)s: section "
+ "'bin', option '%(option)s')") %
{'cfg_file': self._cfg_fname,
'option': opt}, err.code)
else:
@@ -154,14 +156,14 @@
try:
_db_mod = __import__('psycopg2')
except ImportError:
- raise VMMError(_(u"Unable to import database module '%s'.") %
+ raise VMMError(_("Unable to import database module '%s'.") %
'psycopg2', VMM_ERROR)
self._db_connect = self._psycopg2_connect
else:
try:
tmp = __import__('pyPgSQL', globals(), locals(), ['PgSQL'])
except ImportError:
- raise VMMError(_(u"Unable to import database module '%s'.") %
+ raise VMMError(_("Unable to import database module '%s'.") %
'pyPgSQL', VMM_ERROR)
_db_mod = tmp.PgSQL
self._db_connect = self._pypgsql_connect
@@ -181,7 +183,7 @@
dbc = self._dbh.cursor()
dbc.execute("SET NAMES 'UTF8'")
dbc.close()
- except _db_mod.libpq.DatabaseError, err:
+ except _db_mod.libpq.DatabaseError as err:
raise VMMError(str(err), DATABASE_ERROR)
def _psycopg2_connect(self):
@@ -198,11 +200,10 @@
user=self._cfg.pget('database.user'),
password=self._cfg.pget('database.pass'))
self._dbh.set_client_encoding('utf8')
- _db_mod.extensions.register_type(_db_mod.extensions.UNICODE)
dbc = self._dbh.cursor()
dbc.execute("SET NAMES 'UTF8'")
dbc.close()
- except _db_mod.DatabaseError, err:
+ except _db_mod.DatabaseError as err:
raise VMMError(str(err), DATABASE_ERROR)
def _chk_other_address_types(self, address, exclude):
@@ -240,7 +241,7 @@
return False
# TP: %(a_type)s will be one of: 'an account', 'an alias' or
# 'a relocated user'
- msg = _(u"There is already %(a_type)s with the address '%(address)s'.")
+ msg = _("There is already %(a_type)s with the address '%(address)s'.")
raise VMMError(msg % {'a_type': OTHER_TYPES[other][0],
'address': address}, OTHER_TYPES[other][1])
@@ -282,7 +283,7 @@
"""
if lisdir(directory):
return Popen([self._cfg.dget('bin.du'), "-hs", directory],
- stdout=PIPE).communicate()[0].split('\t')[0]
+ stdout=PIPE).communicate()[0].decode().split('\t')[0]
else:
self._warnings.append(_('No such directory: %s') % directory)
return 0
@@ -293,16 +294,16 @@
hashdir, domdir = domain.directory.split(os.path.sep)[-2:]
dir_created = False
os.chdir(self._cfg.dget('misc.base_directory'))
- old_umask = os.umask(0022)
+ old_umask = os.umask(0o022)
if not os.path.exists(hashdir):
- os.mkdir(hashdir, 0711)
+ os.mkdir(hashdir, 0o711)
os.chown(hashdir, 0, 0)
dir_created = True
if not dir_created and not lisdir(hashdir):
- raise VMMError(_(u"'%s' is not a directory.") % hashdir,
+ raise VMMError(_("'%s' is not a directory.") % hashdir,
NO_SUCH_DIRECTORY)
if os.path.exists(domain.directory):
- raise VMMError(_(u"The file/directory '%s' already exists.") %
+ raise VMMError(_("The file/directory '%s' already exists.") %
domain.directory, VMM_ERROR)
os.mkdir(os.path.join(hashdir, domdir),
self._cfg.dget('domain.directory_mode'))
@@ -315,7 +316,7 @@
domdir = account.domain.directory
if not lisdir(domdir):
self._make_domain_dir(account.domain)
- os.umask(0007)
+ os.umask(0o007)
uid = account.uid
os.chdir(domdir)
os.mkdir('%s' % uid, self._cfg.dget('account.directory_mode'))
@@ -332,7 +333,7 @@
bad = mailbox.add_boxes(folders,
self._cfg.dget('mailbox.subscribe'))
if bad:
- self._warnings.append(_(u"Skipped mailbox folders:") +
+ self._warnings.append(_("Skipped mailbox folders:") +
'\n\t- ' + '\n\t- '.join(bad))
os.chdir(oldpwd)
@@ -344,34 +345,34 @@
`domdir` : basestring
The directory of the domain the user belongs to
(commonly AccountObj.domain.directory)
- `uid` : int/long
+ `uid` : int
The user's UID (commonly AccountObj.uid)
- `gid` : int/long
+ `gid` : int
The user's GID (commonly AccountObj.gid)
"""
- assert all(isinstance(xid, (long, int)) for xid in (uid, gid)) and \
- isinstance(domdir, basestring)
+ assert all(isinstance(xid, int) for xid in (uid, gid)) and \
+ isinstance(domdir, str)
if uid < MIN_UID or gid < MIN_GID:
- raise VMMError(_(u"UID '%(uid)u' and/or GID '%(gid)u' are less "
- u"than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
+ raise VMMError(_("UID '%(uid)u' and/or GID '%(gid)u' are less "
+ "than %(min_uid)u/%(min_gid)u.") % {'uid': uid,
'gid': gid, 'min_gid': MIN_GID, 'min_uid': MIN_UID},
MAILDIR_PERM_MISMATCH)
if domdir.count('..'):
- raise VMMError(_(u'Found ".." in domain directory path: %s') %
+ raise VMMError(_('Found ".." in domain directory path: %s') %
domdir, FOUND_DOTS_IN_PATH)
if not lisdir(domdir):
- raise VMMError(_(u"No such directory: %s") % domdir,
+ raise VMMError(_("No such directory: %s") % domdir,
NO_SUCH_DIRECTORY)
os.chdir(domdir)
userdir = '%s' % uid
if not lisdir(userdir):
- self._warnings.append(_(u"No such directory: %s") %
+ self._warnings.append(_("No such directory: %s") %
os.path.join(domdir, userdir))
return
mdstat = os.lstat(userdir)
if (mdstat.st_uid, mdstat.st_gid) != (uid, gid):
- raise VMMError(_(u'Detected owner/group mismatch in home '
- u'directory.'), MAILDIR_PERM_MISMATCH)
+ raise VMMError(_('Detected owner/group mismatch in home '
+ 'directory.'), MAILDIR_PERM_MISMATCH)
rmtree(userdir, ignore_errors=True)
def _delete_domain_dir(self, domdir, gid):
@@ -381,24 +382,24 @@
`domdir` : basestring
The domain's directory (commonly DomainObj.directory)
- `gid` : int/long
+ `gid` : int
The domain's GID (commonly DomainObj.gid)
"""
- assert isinstance(domdir, basestring) and isinstance(gid, (long, int))
+ assert isinstance(domdir, str) and isinstance(gid, int)
if gid < MIN_GID:
- raise VMMError(_(u"GID '%(gid)u' is less than '%(min_gid)u'.") %
+ raise VMMError(_("GID '%(gid)u' is less than '%(min_gid)u'.") %
{'gid': gid, 'min_gid': MIN_GID},
DOMAINDIR_GROUP_MISMATCH)
if domdir.count('..'):
- raise VMMError(_(u'Found ".." in domain directory path: %s') %
+ raise VMMError(_('Found ".." in domain directory path: %s') %
domdir, FOUND_DOTS_IN_PATH)
if not lisdir(domdir):
self._warnings.append(_('No such directory: %s') % domdir)
return
dirst = os.lstat(domdir)
if dirst.st_gid != gid:
- raise VMMError(_(u'Detected group mismatch in domain directory: '
- u'%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
+ raise VMMError(_('Detected group mismatch in domain directory: '
+ '%s') % domdir, DOMAINDIR_GROUP_MISMATCH)
rmtree(domdir, ignore_errors=True)
def has_warnings(self):
@@ -426,9 +427,9 @@
def cfg_install(self):
"""Installs the cfg_dget method as ``cfg_dget`` into the built-in
namespace."""
- import __builtin__
- assert 'cfg_dget' not in __builtin__.__dict__
- __builtin__.__dict__['cfg_dget'] = self._cfg.dget
+ import builtins
+ assert 'cfg_dget' not in builtins.__dict__
+ builtins.__dict__['cfg_dget'] = self._cfg.dget
def domain_add(self, domainname, transport=None):
"""Wrapper around Domain's set_quotalimit, set_transport and save."""
@@ -439,7 +440,7 @@
else:
dom.set_transport(Transport(self._dbh, transport=transport))
dom.set_quotalimit(QuotaLimit(self._dbh,
- bytes=long(self._cfg.dget('domain.quota_bytes')),
+ bytes=int(self._cfg.dget('domain.quota_bytes')),
messages=self._cfg.dget('domain.quota_messages')))
dom.set_serviceset(ServiceSet(self._dbh,
imap=self._cfg.dget('domain.imap'),
@@ -452,11 +453,11 @@
def domain_quotalimit(self, domainname, bytes_, messages=0, force=None):
"""Wrapper around Domain.update_quotalimit()."""
- if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
+ if not all(isinstance(i, int) for i in (bytes_, messages)):
raise TypeError("'bytes_' and 'messages' have to be "
"integers or longs.")
if force is not None and force != 'force':
- raise DomainError(_(u"Invalid argument: '%s'") % force,
+ raise DomainError(_("Invalid argument: '%s'") % force,
INVALID_ARGUMENT)
dom = self._get_domain(domainname)
quotalimit = QuotaLimit(self._dbh, bytes=bytes_, messages=messages)
@@ -469,11 +470,11 @@
"""Wrapper around Domain.update_serviceset()."""
kwargs = dict.fromkeys(SERVICES, False)
if force is not None and force != 'force':
- raise DomainError(_(u"Invalid argument: '%s'") % force,
+ raise DomainError(_("Invalid argument: '%s'") % force,
INVALID_ARGUMENT)
for service in set(services):
if service not in SERVICES:
- raise DomainError(_(u"Unknown service: '%s'") % service,
+ raise DomainError(_("Unknown service: '%s'") % service,
UNKNOWN_SERVICE)
kwargs[service] = True
@@ -484,7 +485,7 @@
def domain_transport(self, domainname, transport, force=None):
"""Wrapper around Domain.update_transport()"""
if force is not None and force != 'force':
- raise DomainError(_(u"Invalid argument: '%s'") % force,
+ raise DomainError(_("Invalid argument: '%s'") % force,
INVALID_ARGUMENT)
dom = self._get_domain(domainname)
trsp = Transport(self._dbh, transport=transport)
@@ -518,13 +519,13 @@
Domain.get_relocated."""
if details not in [None, 'accounts', 'aliasdomains', 'aliases', 'full',
'relocated', 'catchall']:
- raise VMMError(_(u"Invalid argument: '%s'") % details,
+ raise VMMError(_("Invalid argument: '%s'") % details,
INVALID_ARGUMENT)
dom = self._get_domain(domainname)
dominfo = dom.get_info()
if dominfo['domain name'].startswith('xn--'):
dominfo['domain name'] += ' (%s)' % \
- dominfo['domain name'].decode('idna')
+ dominfo['domain name'].encode('utf-8').decode('idna')
if details is None:
return dominfo
elif details == 'accounts':
@@ -597,8 +598,8 @@
if pattern and (pattern.startswith('%') or pattern.endswith('%')):
like = True
if not re.match(RE_DOMAIN_SEARCH, pattern.strip('%')):
- raise VMMError(_(u"The pattern '%s' contains invalid "
- u"characters.") % pattern, DOMAIN_INVALID)
+ raise VMMError(_("The pattern '%s' contains invalid "
+ "characters.") % pattern, DOMAIN_INVALID)
self._db_connect()
return search(self._dbh, pattern=pattern, like=like)
@@ -616,13 +617,10 @@
dpattern = parts[1]
dlike = dpattern.startswith('%') or dpattern.endswith('%')
- if llike:
- checkp = lpattern.strip('%')
- else:
- checkp = lpattern
+ checkp = lpattern.strip('%') if llike else lpattern
if len(checkp) > 0 and re.search(RE_LOCALPART, checkp):
- raise VMMError(_(u"The pattern '%s' contains invalid "
- u"characters.") % pattern,
+ raise VMMError(_("The pattern '%s' contains invalid "
+ "characters.") % pattern,
LOCALPART_INVALID)
else:
# else just match on domains
@@ -630,13 +628,10 @@
dpattern = parts[0]
dlike = dpattern.startswith('%') or dpattern.endswith('%')
- if dlike:
- checkp = dpattern.strip('%')
- else:
- checkp = dpattern
+ checkp = dpattern.strip('%') if dlike else dpattern
if len(checkp) > 0 and not re.match(RE_DOMAIN_SEARCH, checkp):
- raise VMMError(_(u"The pattern '%s' contains invalid "
- u"characters.") % pattern, DOMAIN_INVALID)
+ raise VMMError(_("The pattern '%s' contains invalid "
+ "characters.") % pattern, DOMAIN_INVALID)
self._db_connect()
from VirtualMailManager.common import search_addresses
return search_addresses(self._dbh, typelimit=typelimit,
@@ -647,7 +642,7 @@
"""Wrapper around Account.set_password() and Account.save()."""
acc = self._get_account(emailaddress)
if acc:
- raise VMMError(_(u"The account '%s' already exists.") %
+ raise VMMError(_("The account '%s' already exists.") %
acc.address, ACCOUNT_EXISTS)
self._is_other_address(acc.address, TYPE_ACCOUNT)
acc.set_password(password)
@@ -670,8 +665,8 @@
for destination in destinations:
if destination.gid and \
not self._chk_other_address_types(destination, TYPE_RELOCATED):
- self._warnings.append(_(u"The destination account/alias '%s' "
- u"does not exist.") % destination)
+ self._warnings.append(_("The destination account/alias '%s' "
+ "does not exist.") % destination)
def user_delete(self, emailaddress, force=False):
"""Wrapper around Account.delete(...)"""
@@ -679,7 +674,7 @@
raise TypeError('force must be a bool')
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
uid = acc.uid
gid = acc.gid
@@ -689,10 +684,10 @@
if self._cfg.dget('account.delete_directory'):
try:
self._delete_home(dom_dir, uid, gid)
- except VMMError, err:
+ except VMMError as err:
if err.code in (FOUND_DOTS_IN_PATH, MAILDIR_PERM_MISMATCH,
NO_SUCH_DIRECTORY):
- warning = _(u"""\
+ warning = _("""\
The account has been successfully deleted from the database.
But an error occurred while deleting the following directory:
'%(directory)s'
@@ -708,7 +703,7 @@
if alias:
return alias.get_destinations()
if not self._is_other_address(alias.address, TYPE_ALIAS):
- raise VMMError(_(u"The alias '%s' does not exist.") %
+ raise VMMError(_("The alias '%s' does not exist.") %
alias.address, NO_SUCH_ALIAS)
def alias_delete(self, aliasaddress, targetaddresses=None):
@@ -725,7 +720,7 @@
warnings = []
try:
alias.del_destinations(destinations, warnings)
- except VMMError, err:
+ except VMMError as err:
error = err
if warnings:
self._warnings.append(_('Ignored destination addresses:'))
@@ -747,8 +742,8 @@
for destination in destinations:
if destination.gid and \
not self._chk_other_address_types(destination, TYPE_RELOCATED):
- self._warnings.append(_(u"The destination account/alias '%s' "
- u"does not exist.") % destination)
+ self._warnings.append(_("The destination account/alias '%s' "
+ "does not exist.") % destination)
def catchall_info(self, domain):
"""Returns an iterator object for all destinations (`EmailAddress`
@@ -769,7 +764,7 @@
warnings = []
try:
catchall.del_destinations(destinations, warnings)
- except VMMError, err:
+ except VMMError as err:
error = err
if warnings:
self._warnings.append(_('Ignored destination addresses:'))
@@ -780,12 +775,12 @@
def user_info(self, emailaddress, details=None):
"""Wrapper around Account.get_info(...)"""
if details not in (None, 'du', 'aliases', 'full'):
- raise VMMError(_(u"Invalid argument: '%s'") % details,
+ raise VMMError(_("Invalid argument: '%s'") % details,
INVALID_ARGUMENT)
acc = self._get_account(emailaddress)
if not acc:
if not self._is_other_address(acc.address, TYPE_ACCOUNT):
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
info = acc.get_info()
if self._cfg.dget('account.disk_usage') or details in ('du', 'full'):
@@ -806,12 +801,12 @@
def user_password(self, emailaddress, password):
"""Wrapper for Account.modify('password' ...)."""
- if not isinstance(password, basestring) or not password:
- raise VMMError(_(u"Could not accept password: '%s'") % password,
+ if not isinstance(password, str) or not password:
+ raise VMMError(_("Could not accept password: '%s'") % password,
INVALID_ARGUMENT)
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
acc.modify('password', password)
@@ -819,7 +814,7 @@
"""Wrapper for Account.modify('name', ...)."""
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
acc.modify('name', name)
@@ -827,7 +822,7 @@
"""Wrapper for Account.modify('note', ...)."""
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
acc.modify('note', note)
@@ -835,12 +830,12 @@
"""Wrapper for Account.update_quotalimit(QuotaLimit)."""
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
if bytes_ == 'domain':
quotalimit = None
else:
- if not all(isinstance(i, (int, long)) for i in (bytes_, messages)):
+ if not all(isinstance(i, int) for i in (bytes_, messages)):
raise TypeError("'bytes_' and 'messages' have to be "
"integers or longs.")
quotalimit = QuotaLimit(self._dbh, bytes=bytes_,
@@ -849,24 +844,22 @@
def user_transport(self, emailaddress, transport):
"""Wrapper for Account.update_transport(Transport)."""
- if not isinstance(transport, basestring) or not transport:
- raise VMMError(_(u"Could not accept transport: '%s'") % transport,
+ if not isinstance(transport, str) or not transport:
+ raise VMMError(_("Could not accept transport: '%s'") % transport,
INVALID_ARGUMENT)
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
- if transport == 'domain':
- transport = None
- else:
- transport = Transport(self._dbh, transport=transport)
+ transport = None if transport == 'domain' \
+ else Transport(self._dbh, transport=transport)
acc.update_transport(transport)
def user_services(self, emailaddress, *services):
"""Wrapper around Account.update_serviceset()."""
acc = self._get_account(emailaddress)
if not acc:
- raise VMMError(_(u"The account '%s' does not exist.") %
+ raise VMMError(_("The account '%s' does not exist.") %
acc.address, NO_SUCH_ACCOUNT)
if len(services) == 1 and services[0] == 'domain':
serviceset = None
@@ -874,7 +867,7 @@
kwargs = dict.fromkeys(SERVICES, False)
for service in set(services):
if service not in SERVICES:
- raise VMMError(_(u"Unknown service: '%s'") % service,
+ raise VMMError(_("Unknown service: '%s'") % service,
UNKNOWN_SERVICE)
kwargs[service] = True
serviceset = ServiceSet(self._dbh, **kwargs)
@@ -891,8 +884,8 @@
relocated.set_destination(destination)
if destination.gid and \
not self._chk_other_address_types(destination, TYPE_RELOCATED):
- self._warnings.append(_(u"The destination account/alias '%s' "
- u"does not exist.") % destination)
+ self._warnings.append(_("The destination account/alias '%s' "
+ "does not exist.") % destination)
def relocated_info(self, emailaddress):
"""Returns the target address of the relocated user with the given
@@ -901,7 +894,7 @@
if relocated:
return relocated.get_info()
if not self._is_other_address(relocated.address, TYPE_RELOCATED):
- raise VMMError(_(u"The relocated user '%s' does not exist.") %
+ raise VMMError(_("The relocated user '%s' does not exist.") %
relocated.address, NO_SUCH_RELOCATED)
def relocated_delete(self, emailaddress):
--- a/VirtualMailManager/mailbox.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/mailbox.py Sun Jan 06 00:09:47 2013 +0000
@@ -14,6 +14,7 @@
from binascii import a2b_base64, b2a_base64
from subprocess import Popen, PIPE
+from VirtualMailManager import ENCODING
from VirtualMailManager.account import Account
from VirtualMailManager.common import lisdir
from VirtualMailManager.errors import VMMError
@@ -29,13 +30,14 @@
def _mbase64_encode(inp, dest):
if inp:
- mb64 = b2a_base64(''.join(inp).encode('utf-16be'))
+ mb64 = b2a_base64(''.join(inp).encode('utf-16be')).decode()
dest.append('&%s-' % mb64.rstrip('\n=').replace('/', ','))
del inp[:]
def _mbase64_to_unicode(mb64):
- return unicode(a2b_base64(mb64.replace(',', '/') + '==='), 'utf-16be')
+ return str(a2b_base64(mb64.replace(',', '/').encode() + b'==='),
+ 'utf-16be')
def utf8_to_mutf7(src):
@@ -86,7 +88,7 @@
class Mailbox(object):
"""Base class of all mailbox classes."""
__slots__ = ('_boxes', '_root', '_sep', '_user')
- FILE_MODE = 0600
+ FILE_MODE = 0o600
_ctrl_chr_re = re.compile('[\x00-\x1F\x7F-\x9F]')
_box_name_re = re.compile('^[\x20-\x25\x27-\x7E]+$')
@@ -206,11 +208,9 @@
"""Writes all created mailboxes to the subscriptions file."""
if not self._boxes:
return
- subscriptions = open('subscriptions', 'w')
- subscriptions.write('\n'.join(self._boxes))
- subscriptions.write('\n')
- subscriptions.flush()
- subscriptions.close()
+ with open('subscriptions', 'w') as subscriptions:
+ subscriptions.write('\n'.join(self._boxes))
+ subscriptions.write('\n')
os.chown('subscriptions', self._user.uid, self._user.gid)
os.chmod('subscriptions', self.__class__.FILE_MODE)
del self._boxes[:]
@@ -257,8 +257,8 @@
process = Popen(cmd_args, stderr=PIPE)
stderr = process.communicate()[1]
if process.returncode:
- e_msg = _(u'Failed to create mailboxes: %r\n') % mailboxes
- raise VMMError(e_msg + stderr.strip(), VMM_ERROR)
+ e_msg = _('Failed to create mailboxes: %r\n') % mailboxes
+ raise VMMError(e_msg + stderr.strip().decode(ENCODING), VMM_ERROR)
def create(self):
"""Create a dbox INBOX"""
--- a/VirtualMailManager/maillocation.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/maillocation.py Sun Jan 06 00:09:47 2013 +0000
@@ -12,7 +12,6 @@
from VirtualMailManager.constants import MAILLOCATION_INIT
from VirtualMailManager.errors import MailLocationError as MLErr
-from VirtualMailManager.pycompat import all
__all__ = ('MailLocation', 'known_format')
@@ -56,29 +55,29 @@
self._mbfmt = None
self._mid = 0
- for key in kwargs.iterkeys():
+ for key in kwargs.keys():
if key not in self.__class__._kwargs:
raise ValueError('unrecognized keyword: %r' % key)
mid = kwargs.get('mid')
if mid:
- assert isinstance(mid, (int, long))
+ assert isinstance(mid, int)
self._load_by_mid(mid)
else:
args = kwargs.get('mbfmt'), kwargs.get('directory')
- assert all(isinstance(arg, basestring) for arg in args)
+ assert all(isinstance(arg, str) for arg in args)
if args[0].lower() not in _format_info:
- raise MLErr(_(u"Unsupported mailbox format: '%s'") % args[0],
+ raise MLErr(_("Unsupported mailbox format: '%s'") % args[0],
MAILLOCATION_INIT)
directory = args[1].strip()
if not directory:
- raise MLErr(_(u"Empty directory name"), MAILLOCATION_INIT)
+ raise MLErr(_("Empty directory name"), MAILLOCATION_INIT)
if len(directory) > 20:
- raise MLErr(_(u"Directory name is too long: '%s'") % directory,
+ raise MLErr(_("Directory name is too long: '%s'") % directory,
MAILLOCATION_INIT)
self._load_by_names(args[0].lower(), directory)
def __str__(self):
- return u'%s:~/%s' % (self._mbfmt, self._directory)
+ return '%s:~/%s' % (self._mbfmt, self._directory)
@property
def directory(self):
--- a/VirtualMailManager/network.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/network.py Sun Jan 06 00:09:47 2013 +0000
@@ -10,6 +10,8 @@
import socket
+from binascii import b2a_hex
+
class NetInfo(object):
"""Simple class for CIDR network addresses an IP addresses."""
@@ -81,7 +83,7 @@
`(address_family, address_as_long)` will be returned. The
`address_family`will be either `socket.AF_INET` or `socket.AF_INET6`.
"""
- if not isinstance(ip_address, basestring) or not ip_address:
+ if not isinstance(ip_address, str) or not ip_address:
raise TypeError('ip_address must be a non empty string.')
if not ip_address.count(':'):
family = socket.AF_INET
@@ -97,4 +99,4 @@
address = socket.inet_pton(family, ip_address)
except socket.error:
raise ValueError('Not a valid IPv6 address: %r' % ip_address)
- return (family, long(address.encode('hex'), 16))
+ return (family, int(b2a_hex(address), 16))
--- a/VirtualMailManager/password.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/password.py Sun Jan 06 00:09:47 2013 +0000
@@ -15,22 +15,20 @@
schemes, encodings = list_schemes()
"""
+import hashlib
+
+from base64 import b64encode
+from binascii import b2a_hex
from crypt import crypt
from random import SystemRandom
from subprocess import Popen, PIPE
-try:
- import hashlib
-except ImportError:
- from VirtualMailManager.pycompat import hashlib
-
from VirtualMailManager import ENCODING
from VirtualMailManager.emailaddress import EmailAddress
from VirtualMailManager.common import get_unicode, version_str
from VirtualMailManager.constants import VMM_ERROR
from VirtualMailManager.errors import VMMError
-COMPAT = hasattr(hashlib, 'compat')
SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
DEFAULT_B64 = (None, 'B64', 'BASE64')
@@ -55,7 +53,7 @@
cfg_dget = lambda option: None
_sys_rand = SystemRandom()
_choice = _sys_rand.choice
-_get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in xrange(s_len))
+_get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in range(s_len))
def _dovecotpw(password, scheme, encoding):
@@ -71,8 +69,8 @@
process = Popen(cmd_args, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate()
if process.returncode:
- raise VMMError(stderr.strip(), VMM_ERROR)
- hashed = stdout.strip()
+ raise VMMError(stderr.strip().decode(ENCODING), VMM_ERROR)
+ hashed = stdout.strip().decode(ENCODING)
if not hashed.startswith('{%s}' % scheme):
raise VMMError('Unexpected result from %s: %s' %
(cfg_dget('bin.dovecotpw'), hashed), VMM_ERROR)
@@ -80,33 +78,13 @@
def _md4_new():
- """Returns an new MD4-hash object if supported by the hashlib or
- provided by PyCrypto - other `None`.
+ """Returns an new MD4-hash object if supported by the hashlib -
+ otherwise `None`.
"""
try:
return hashlib.new('md4')
- except ValueError, err:
- if str(err) == 'unsupported hash type':
- if not COMPAT:
- try:
- from Crypto.Hash import MD4
- return MD4.new()
- except ImportError:
- return None
- else:
- raise
-
-
-def _sha256_new(data=''):
- """Returns a new sha256 object from the hashlib.
-
- Returns `None` if the PyCrypto in pycompat.hashlib is too old."""
- if not COMPAT:
- return hashlib.sha256(data)
- try:
- return hashlib.new('sha256', data)
- except ValueError, err:
- if str(err) == 'unsupported hash type':
+ except ValueError as err:
+ if err.args[0].startswith('unsupported hash type'):
return None
else:
raise
@@ -121,13 +99,14 @@
def _clear_hash(password, scheme, encoding):
"""Generates a (encoded) CLEARTEXT/PLAIN 'hash'."""
+ password = password.decode(ENCODING)
if encoding:
if encoding == 'HEX':
- password = password.encode('hex')
+ password = b2a_hex(password.encode()).decode()
else:
- password = password.encode('base64').replace('\n', '')
+ password = b64encode(password.encode()).decode()
return _format_digest(password, scheme, encoding)
- return get_unicode('{%s}%s' % (scheme, password))
+ return '{%s}%s' % (scheme, password)
def _get_crypt_blowfish_salt():
@@ -174,12 +153,12 @@
salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
else:
salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
- encrypted = crypt(password, salt)
+ encrypted = crypt(password.decode(ENCODING), salt)
if encoding:
if encoding == 'HEX':
- encrypted = encrypted.encode('hex')
+ encrypted = b2a_hex(encrypted.encode()).decode()
else:
- encrypted = encrypted.encode('base64').replace('\n', '')
+ encrypted = b64encode(encrypted.encode()).decode()
if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
cfg_dget('misc.dovecot_version') < 0x20000b06:
scheme = 'CRYPT'
@@ -194,7 +173,7 @@
if encoding in DEFAULT_HEX:
digest = md4.hexdigest()
else:
- digest = md4.digest().encode('base64').rstrip()
+ digest = b64encode(md4.digest()).decode()
return _format_digest(digest, scheme, encoding)
return _dovecotpw(password, scheme, encoding)
@@ -210,15 +189,17 @@
# http://dovecot.org/list/dovecot-news/2009-March/000103.html
# http://hg.dovecot.org/dovecot-1.1/rev/2b0043ba89ae
if cfg_dget('misc.dovecot_version') >= 0x1010cf00:
- md5.update('%s:%s:' % (user.localpart, user.domainname))
+ md5.update(user.localpart.encode() + b':' +
+ user.domainname.encode() + b':')
else:
- md5.update('%s::' % user)
+ raise VMMError('You will need Dovecot >= v1.2.0 for proper '
+ 'functioning digest-md5 authentication.', VMM_ERROR)
md5.update(password)
if (scheme in ('PLAIN-MD5', 'DIGEST-MD5') and encoding in DEFAULT_HEX) or \
(scheme == 'LDAP-MD5' and encoding == 'HEX'):
digest = md5.hexdigest()
else:
- digest = md5.digest().encode('base64').rstrip()
+ digest = b64encode(md5.digest()).decode()
return _format_digest(digest, scheme, encoding)
@@ -226,12 +207,13 @@
"""Generates NTLM hashes."""
md4 = _md4_new()
if md4:
- password = ''.join('%s\x00' % c for c in password)
+ password = b''.join(bytes(x)
+ for x in zip(password, bytes(len(password))))
md4.update(password)
if encoding in DEFAULT_HEX:
digest = md4.hexdigest()
else:
- digest = md4.digest().encode('base64').rstrip()
+ digest = b64encode(md4.digest()).decode()
return _format_digest(digest, scheme, encoding)
return _dovecotpw(password, scheme, encoding)
@@ -240,7 +222,7 @@
"""Generates SHA1 aka SHA hashes."""
sha1 = hashlib.sha1(password)
if encoding in DEFAULT_B64:
- digest = sha1.digest().encode('base64').rstrip()
+ digest = b64encode(sha1.digest()).decode()
else:
digest = sha1.hexdigest()
return _format_digest(digest, scheme, encoding)
@@ -248,80 +230,72 @@
def _sha256_hash(password, scheme, encoding):
"""Generates SHA256 hashes."""
- sha256 = _sha256_new(password)
- if sha256:
- if encoding in DEFAULT_B64:
- digest = sha256.digest().encode('base64').rstrip()
- else:
- digest = sha256.hexdigest()
- return _format_digest(digest, scheme, encoding)
- return _dovecotpw(password, scheme, encoding)
+ sha256 = hashlib.sha256(password)
+ if encoding in DEFAULT_B64:
+ digest = b64encode(sha256.digest()).decode()
+ else:
+ digest = sha256.hexdigest()
+ return _format_digest(digest, scheme, encoding)
def _sha512_hash(password, scheme, encoding):
"""Generates SHA512 hashes."""
- if not COMPAT:
- sha512 = hashlib.sha512(password)
- if encoding in DEFAULT_B64:
- digest = sha512.digest().encode('base64').replace('\n', '')
- else:
- digest = sha512.hexdigest()
- return _format_digest(digest, scheme, encoding)
- return _dovecotpw(password, scheme, encoding)
+ sha512 = hashlib.sha512(password)
+ if encoding in DEFAULT_B64:
+ digest = b64encode(sha512.digest()).decode()
+ else:
+ digest = sha512.hexdigest()
+ return _format_digest(digest, scheme, encoding)
def _smd5_hash(password, scheme, encoding):
"""Generates SMD5 (salted PLAIN-MD5) hashes."""
md5 = hashlib.md5(password)
- salt = _get_salt(SALTED_ALGO_SALT_LEN)
+ salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
md5.update(salt)
if encoding in DEFAULT_B64:
- digest = (md5.digest() + salt).encode('base64').rstrip()
+ digest = b64encode(md5.digest() + salt).decode()
else:
- digest = md5.hexdigest() + salt.encode('hex')
+ digest = md5.hexdigest() + b2a_hex(salt).decode()
return _format_digest(digest, scheme, encoding)
def _ssha1_hash(password, scheme, encoding):
"""Generates SSHA (salted SHA/SHA1) hashes."""
sha1 = hashlib.sha1(password)
- salt = _get_salt(SALTED_ALGO_SALT_LEN)
+ salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
sha1.update(salt)
if encoding in DEFAULT_B64:
- digest = (sha1.digest() + salt).encode('base64').rstrip()
+ digest = b64encode(sha1.digest() + salt).decode()
else:
- digest = sha1.hexdigest() + salt.encode('hex')
+ digest = sha1.hexdigest() + b2a_hex(salt).decode()
return _format_digest(digest, scheme, encoding)
def _ssha256_hash(password, scheme, encoding):
"""Generates SSHA256 (salted SHA256) hashes."""
- sha256 = _sha256_new(password)
- if sha256:
- salt = _get_salt(SALTED_ALGO_SALT_LEN)
- sha256.update(salt)
- if encoding in DEFAULT_B64:
- digest = (sha256.digest() + salt).encode('base64').rstrip()
- else:
- digest = sha256.hexdigest() + salt.encode('hex')
- return _format_digest(digest, scheme, encoding)
- return _dovecotpw(password, scheme, encoding)
+ sha256 = hashlib.sha256(password)
+ salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
+ sha256.update(salt)
+ if encoding in DEFAULT_B64:
+ digest = b64encode(sha256.digest() + salt).decode()
+ else:
+ digest = sha256.hexdigest() + b2a_hex(salt).decode()
+ return _format_digest(digest, scheme, encoding)
def _ssha512_hash(password, scheme, encoding):
"""Generates SSHA512 (salted SHA512) hashes."""
- if not COMPAT:
- salt = _get_salt(SALTED_ALGO_SALT_LEN)
- sha512 = hashlib.sha512(password + salt)
- if encoding in DEFAULT_B64:
- digest = (sha512.digest() + salt).encode('base64').replace('\n',
- '')
- else:
- digest = sha512.hexdigest() + salt.encode('hex')
- return _format_digest(digest, scheme, encoding)
- return _dovecotpw(password, scheme, encoding)
+ salt = _get_salt(SALTED_ALGO_SALT_LEN).encode()
+ sha512 = hashlib.sha512(password + salt)
+ if encoding in DEFAULT_B64:
+ digest = b64encode(sha512.digest() + salt).decode()
+ else:
+ digest = sha512.hexdigest() + b2a_hex(salt).decode()
+ return _format_digest(digest, scheme, encoding)
_scheme_info = {
+ 'CLEAR': (_clear_hash, 0x2010df00),
'CLEARTEXT': (_clear_hash, 0x10000f00),
'CRAM-MD5': (_dovecotpw, 0x10000f00),
'CRYPT': (_crypt_hash, 0x10000f00),
@@ -359,11 +333,8 @@
be empty.
"""
dcv = cfg_dget('misc.dovecot_version')
- schemes = (k for (k, v) in _scheme_info.iteritems() if v[1] <= dcv)
- if dcv >= 0x10100a01:
- encodings = ('.B64', '.BASE64', '.HEX')
- else:
- encodings = ()
+ schemes = (k for (k, v) in _scheme_info.items() if v[1] <= dcv)
+ encodings = ('.B64', '.BASE64', '.HEX') if dcv >= 0x10100a01 else ()
return schemes, encodings
@@ -382,23 +353,23 @@
* depends on a newer Dovecot version
* has a unknown encoding suffix
"""
- assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme
+ assert isinstance(scheme, str), 'Not a str: {!r}'.format(scheme)
scheme_encoding = scheme.upper().split('.')
scheme = scheme_encoding[0]
if scheme not in _scheme_info:
- raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme,
+ raise VMMError(_("Unsupported password scheme: '%s'") % scheme,
VMM_ERROR)
if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]:
- raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot "
- u">= v%(version)s.") % {'scheme': scheme,
+ raise VMMError(_("The password scheme '%(scheme)s' requires Dovecot "
+ ">= v%(version)s.") % {'scheme': scheme,
'version': version_str(_scheme_info[scheme][1])},
VMM_ERROR)
if len(scheme_encoding) > 1:
if cfg_dget('misc.dovecot_version') < 0x10100a01:
- raise VMMError(_(u'Encoding suffixes for password schemes require '
- u'Dovecot >= v1.1.alpha1.'), VMM_ERROR)
+ raise VMMError(_('Encoding suffixes for password schemes require '
+ 'Dovecot >= v1.1.alpha1.'), VMM_ERROR)
if scheme_encoding[1] not in ('B64', 'BASE64', 'HEX'):
- raise VMMError(_(u"Unsupported password encoding: '%s'") %
+ raise VMMError(_("Unsupported password encoding: '%s'") %
scheme_encoding[1], VMM_ERROR)
encoding = scheme_encoding[1]
else:
@@ -413,11 +384,9 @@
be used for the hash generation. When 'DIGEST-MD5' is used as scheme,
also an EmailAddress instance must be given as *user* argument.
"""
- if not isinstance(password, basestring):
+ if not isinstance(password, str):
raise TypeError('Password is not a string: %r' % password)
- if isinstance(password, unicode):
- password = password.encode(ENCODING)
- password = password.strip()
+ password = password.encode(ENCODING).strip()
if not password:
raise ValueError("Could not accept empty password.")
if scheme is None:
--- a/VirtualMailManager/pycompat/__init__.py Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2013, Pascal Volk
-# See COPYING for distribution information.
-
-"""
- VirtualMailManager.pycompat
-
- VirtualMailManager's compatibility stuff for Python 2.4
-"""
-
-# http://docs.python.org/library/functions.html#all
-try:
- all = all
-except NameError:
- def all(iterable):
- """Return True if all elements of the *iterable* are true
- (or if the iterable is empty).
-
- """
- for element in iterable:
- if not element:
- return False
- return True
-
-
-# http://docs.python.org/library/functions.html#any
-try:
- any = any
-except NameError:
- def any(iterable):
- """Return True if any element of the *iterable* is true. If the
- iterable is empty, return False.
-
- """
- for element in iterable:
- if element:
- return True
- return False
--- a/VirtualMailManager/pycompat/hashlib.py Sat Jan 05 23:49:42 2013 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-# -*- coding: UTF-8 -*-
-# Copyright (c) 2010 - 2013, Pascal Volk
-# See COPYING for distribution information.
-
-"""
- VirtualMailManager.pycompat.hashlib
-
- VirtualMailManager's minimal hashlib emulation for Python 2.4
-
- hashlib.md5(...), hashlib.sha1(...), hashlib.new('md5', ...) and
- hashlib.new('sha1', ...) will work always.
-
- When the PyCrypto module <http://www.pycrypto.org/> could be found in
- sys.path hashlib.new('md4', ...) will also work.
-
- With PyCrypto >= 2.1.0alpha1 hashlib.new('sha256', ...) and
- hashlib.sha256(...) becomes functional.
-"""
-
-
-import md5 as _md5
-import sha as _sha1
-
-try:
- import Crypto
-except ImportError:
- _md4 = None
- SHA256 = None
-else:
- from Crypto.Hash import MD4 as _md4
- if hasattr(Crypto, 'version_info'): # <- Available since v2.1.0alpha1
- from Crypto.Hash import SHA256 # SHA256 works since v2.1.0alpha1
- sha256 = SHA256.new
- else:
- SHA256 = None
- del Crypto
-
-
-compat = 0x01
-md5 = _md5.new
-sha1 = _sha1.new
-
-
-def new(name, string=''):
- """Return a new hashing object using the named algorithm, optionally
- initialized with the provided string.
- """
- if name in ('md5', 'MD5'):
- return _md5.new(string)
- if name in ('sha1', 'SHA1'):
- return _sha1.new(string)
- if not _md4:
- raise ValueError('unsupported hash type')
- if name in ('md4', 'MD4'):
- return _md4.new(string)
- if name in ('sha256', 'SHA256') and SHA256:
- return SHA256.new(string)
- raise ValueError('unsupported hash type')
--- a/VirtualMailManager/quotalimit.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/quotalimit.py Sun Jan 06 00:09:47 2013 +0000
@@ -9,8 +9,6 @@
for domains and accounts.
"""
-from VirtualMailManager.pycompat import all
-
_ = lambda msg: msg
@@ -34,7 +32,7 @@
`qid` : int
The id of a quota limit
- `bytes` : long
+ `bytes` : int
The quota limit in bytes.
`messages` : int
The quota limit in number of messages
@@ -44,24 +42,18 @@
self._bytes = 0
self._messages = 0
- for key in kwargs.iterkeys():
+ for key in kwargs.keys():
if key not in self.__class__._kwargs:
raise ValueError('unrecognized keyword: %r' % key)
qid = kwargs.get('qid')
if qid is not None:
- assert isinstance(qid, (int, long))
+ assert isinstance(qid, int)
self._load_by_qid(qid)
else:
bytes_, msgs = kwargs.get('bytes'), kwargs.get('messages')
- assert all(isinstance(i, (int, long)) for i in (bytes_, msgs))
- if bytes_ < 0:
- self._bytes = -bytes_
- else:
- self._bytes = bytes_
- if msgs < 0:
- self._messages = -msgs
- else:
- self._messages = msgs
+ assert all(isinstance(i, int) for i in (bytes_, msgs))
+ self._bytes = -bytes_ if bytes_ < 0 else bytes_
+ self._messages = -msgs if msgs < 0 else msgs
self._load_by_limit()
@property
--- a/VirtualMailManager/relocated.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/relocated.py Sun Jan 06 00:09:47 2013 +0000
@@ -36,13 +36,13 @@
self._dbh = dbh
self._gid = get_gid(self._dbh, self._addr.domainname)
if not self._gid:
- raise RErr(_(u"The domain '%s' does not exist.") %
+ raise RErr(_("The domain '%s' does not exist.") %
self._addr.domainname, NO_SUCH_DOMAIN)
self._dest = None
self._load()
- def __nonzero__(self):
+ def __bool__(self):
"""Returns `True` if the Relocated is known, `False` if it's new."""
return self._dest is not None
@@ -59,8 +59,8 @@
if destination:
destination = DestinationEmailAddress(destination[0], self._dbh)
if destination.at_localhost:
- raise RErr(_(u"The destination address' domain name must not "
- u"be localhost."), DOMAIN_INVALID)
+ raise RErr(_("The destination address' domain name must not "
+ "be localhost."), DOMAIN_INVALID)
self._dest = destination
@property
@@ -73,14 +73,14 @@
update = False
assert isinstance(destination, DestinationEmailAddress)
if destination.at_localhost:
- raise RErr(_(u"The destination address' domain name must not be "
- u"localhost."), DOMAIN_INVALID)
+ raise RErr(_("The destination address' domain name must not be "
+ "localhost."), DOMAIN_INVALID)
if self._addr == destination:
- raise RErr(_(u'Address and destination are identical.'),
+ raise RErr(_('Address and destination are identical.'),
RELOCATED_ADDR_DEST_IDENTICAL)
if self._dest:
if self._dest == destination:
- raise RErr(_(u"The relocated user '%s' already exists.") %
+ raise RErr(_("The relocated user '%s' already exists.") %
self._addr, RELOCATED_EXISTS)
else:
self._dest = destination
@@ -103,14 +103,14 @@
def get_info(self):
"""Returns the address to which mails should be sent."""
if not self._dest:
- raise RErr(_(u"The relocated user '%s' does not exist.") %
+ raise RErr(_("The relocated user '%s' does not exist.") %
self._addr, NO_SUCH_RELOCATED)
return self._dest
def delete(self):
"""Deletes the relocated entry from the database."""
if not self._dest:
- raise RErr(_(u"The relocated user '%s' does not exist.") %
+ raise RErr(_("The relocated user '%s' does not exist.") %
self._addr, NO_SUCH_RELOCATED)
dbc = self._dbh.cursor()
dbc.execute('DELETE FROM relocated WHERE gid = %s AND address = %s',
--- a/VirtualMailManager/serviceset.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/serviceset.py Sun Jan 06 00:09:47 2013 +0000
@@ -19,7 +19,7 @@
Each ServiceSet object provides following - read only - attributes:
- `ssid` : long
+ `ssid` : int
The id of the service set
`smtp` : bool
Boolean flag for service smtp
@@ -65,12 +65,12 @@
else:
self._sieve_col = 'sieve'
- for key in kwargs.iterkeys():
+ for key in kwargs.keys():
if key not in self.__class__._kwargs:
raise ValueError('unrecognized keyword: %r' % key)
if key == 'ssid':
assert not isinstance(kwargs[key], bool) and \
- isinstance(kwargs[key], (int, long)) and kwargs[key] > 0
+ isinstance(kwargs[key], int) and kwargs[key] > 0
self._load_by_ssid(kwargs[key])
break
else:
@@ -101,13 +101,13 @@
def __repr__(self):
return '%s(%s, %s)' % (self.__class__.__name__, self._dbh,
- ', '.join('%s=%r' % s for s in self._services.iteritems()))
+ ', '.join('%s=%r' % s for s in self._services.items()))
def _load_by_services(self):
"""Try to load the service_set by it's service combination."""
sql = ('SELECT ssid FROM service_set WHERE %s' %
' AND '.join('%s = %s' %
- (k, str(v).upper()) for k, v in self._services.iteritems()))
+ (k, str(v).upper()) for k, v in self._services.items()))
if self._sieve_col == 'managesieve':
sql = sql.replace('sieve', self._sieve_col)
dbc = self._dbh.cursor()
@@ -131,10 +131,7 @@
self._ssid = result[0]
#self._services.update(zip(SERVICES, result[1:]))
for key, value in zip(SERVICES, result[1:]): # pyPgSQL compatible
- if value:
- self._services[key] = True
- else:
- self._services[key] = False
+ self._services[key] = True if value else False
def _save(self):
"""Store a new service_set in the database."""
--- a/VirtualMailManager/transport.py Sat Jan 05 23:49:42 2013 +0000
+++ b/VirtualMailManager/transport.py Sun Jan 06 00:09:47 2013 +0000
@@ -9,8 +9,6 @@
domains and accounts.
"""
-from VirtualMailManager.pycompat import any
-
_ = lambda msg: msg
@@ -26,7 +24,7 @@
Keyword arguments:
dbh -- a pyPgSQL.PgSQL.connection
- tid -- the id of a transport (int/long)
+ tid -- the id of a transport (int)
transport -- the value of the transport (str)
"""
@@ -34,10 +32,10 @@
self._tid = 0
assert any((tid, transport))
if tid:
- assert not isinstance(tid, bool) and isinstance(tid, (int, long))
+ assert not isinstance(tid, bool) and isinstance(tid, int)
self._load_by_id(tid)
else:
- assert isinstance(transport, basestring)
+ assert isinstance(transport, str)
self._transport = transport
self._load_by_name()
--- a/pgsql/set-permissions.py Sat Jan 05 23:49:42 2013 +0000
+++ b/pgsql/set-permissions.py Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding: utf-8
# Copyright 2012, Pascal Volk
# See COPYING for distribution information.
@@ -119,10 +119,10 @@
(dc_rw_tbls, db['dovecot']))
dbc.execute('GRANT SELECT ON %s TO %s' % (dc_ro_tbls, db['dovecot']))
dbc.execute('GRANT SELECT ON %s TO %s' % (pf_ro_tbls, db['postfix']))
- for table, columns in db['dovecot_tbls'].iteritems():
+ for table, columns in db['dovecot_tbls'].items():
dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
db['dovecot']))
- for table, columns in db['postfix_tbls'].iteritems():
+ for table, columns in db['postfix_tbls'].items():
dbc.execute('GRANT SELECT (%s) ON %s TO %s' % (columns, table,
db['postfix']))
dbc.close()
--- a/setup.py Sat Jan 05 23:49:42 2013 +0000
+++ b/setup.py Sun Jan 06 00:09:47 2013 +0000
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2007 - 2013, Pascal Volk
# See COPYING for distribution information.
import os
from distutils.core import setup
-from distutils.dist import DistributionMetadata
VERSION = '0.6.1'
@@ -20,7 +19,6 @@
'VirtualMailManager',
'VirtualMailManager.cli',
'VirtualMailManager.ext',
- 'VirtualMailManager.pycompat',
]
# http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers = ['Development Status :: 5 - Production/Stable',
@@ -44,7 +42,7 @@
'Topic :: Utilities']
# sucessfuly tested on:
-platforms = ['freebsd7', 'linux2', 'openbsd4']
+platforms = ['freebsd7', 'linux2', 'openbsd5']
# remove existing MANIFEST
if os.path.exists('MANIFEST'):
@@ -58,12 +56,10 @@
'author': 'Pascal Volk',
'author_email': 'user+vmm@localhost.localdomain.org',
'license': 'BSD License',
+ 'requires': ['psycopg2 (>=2.0)'],
'url': 'http://vmm.localdomain.org/',
- 'download_url':'http://sf.net/projects/vmm/files/',
+ 'download_url': 'http://sf.net/projects/vmm/files/',
'platforms': platforms,
'classifiers': classifiers}
-if 'requires' in DistributionMetadata._METHOD_BASENAMES:
- setup_args['requires'] = ['psycopg2 (>=2.0)', 'pyPgSQL (>=2.5.1)']
-
setup(**setup_args)
--- a/update_config.py Sat Jan 05 23:49:42 2013 +0000
+++ b/update_config.py Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# Copyright (c) 2008 - 2013, Pascal Volk
# See COPYING for distribution information.
@@ -6,7 +6,7 @@
import os
os.sys.path.remove(os.sys.path[0])
from time import time
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
from shutil import copy2
pre_060 = False
@@ -161,18 +161,20 @@
if len(sect_opt):
update_cfg_file(cp, cf)
sect_opt.sort()
- print 'Please have a look at your configuration: %s' % cf
- print 'This are your Modified/Renamed/New settings:'
+ print('Please have a look at your configuration: %s' % cf)
+ print('This are your Modified/Renamed/New settings:')
for s_o, st in sect_opt:
- print '%s %s = %s' % (st, s_o, get_option(cp, s_o))
+ print('%s %s = %s' % (st, s_o, get_option(cp, s_o)))
if had_config:
- print '\nRemoved section "config" with option "done" (obsolte)'
+ print('\nRemoved section "config" with option "done" (obsolte)')
if had_gid_mail:
- print '\nRemoved option "gid_mail" from section "misc" (obsolte)\n'
+ print('\nRemoved option "gid_mail" from section "misc"',
+ '(obsolte)\n')
os.sys.exit(0)
if had_config or had_gid_mail:
update_cfg_file(cp, cf)
if had_config:
- print '\nRemoved section "config" with option "done" (obsolte)'
+ print('\nRemoved section "config" with option "done" (obsolte)')
if had_gid_mail:
- print '\nRemoved option "gid_mail" from section "misc" (obsolte)\n'
+ print('\nRemoved option "gid_mail" from section "misc"',
+ '(obsolte)\n')
--- a/vmm Sat Jan 05 23:49:42 2013 +0000
+++ b/vmm Sun Jan 06 00:09:47 2013 +0000
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# Copyright 2007 - 2013, Pascal Volk
# See COPYING for distribution information.
@@ -15,4 +15,4 @@
# Otherwise just remove /usr/local/sbin from sys.path
sys.path.remove(sys.path[0])
from VirtualMailManager.cli.main import run
- sys.exit(run(sys.argv))
+ sys.exit(run())