# HG changeset patch # User Pascal Volk <user@localhost.localdomain.org> # Date 1357430987 0 # Node ID 2bc11dada2965c633c2a40fbcd47239aa9c0a332 # Parent 995203a101e1e3f2c4aa800024ae5a72498cc414# Parent d24f094d1cb5438385b7c7594061690add16d3a3 merged changes from default(d24f094d1cb5) diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/__init__.py --- 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') diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/account.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/alias.py --- 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[:] diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/aliasdomain.py --- 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 ' diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/catchall.py --- 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[:] diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/__init__.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/clihelp.py --- 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 _ diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/config.py --- 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 _ diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/handler.py --- 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) diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/main.py --- 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 _ diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/cli/subcommands.py --- 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, '') diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/common.py --- 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, diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/config.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/constants.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/domain.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/emailaddress.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/ext/postconf.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/handler.py --- 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): diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/mailbox.py --- 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""" diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/maillocation.py --- 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): diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/network.py --- 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)) diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/password.py --- 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: diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/pycompat/__init__.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/pycompat/hashlib.py --- 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') diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/quotalimit.py --- 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 diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/relocated.py --- 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', diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/serviceset.py --- 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.""" diff -r d24f094d1cb5 -r 2bc11dada296 VirtualMailManager/transport.py --- 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() diff -r d24f094d1cb5 -r 2bc11dada296 pgsql/set-permissions.py --- 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() diff -r d24f094d1cb5 -r 2bc11dada296 setup.py --- 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) diff -r d24f094d1cb5 -r 2bc11dada296 update_config.py --- 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') diff -r d24f094d1cb5 -r 2bc11dada296 vmm --- 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())