VirtualMailManager/cli/subcommands.py
changeset 571 a4aead244f75
parent 568 14abdd04ddf5
child 593 3dc1764c23d2
equal deleted inserted replaced
465:c0e1fb1b0145 571:a4aead244f75
       
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright (c) 2007 - 2012, Pascal Volk
       
     3 # See COPYING for distribution information.
       
     4 """
       
     5     VirtualMailManager.cli.subcommands
       
     6     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
     7 
       
     8     VirtualMailManager's cli subcommands.
       
     9 """
       
    10 
       
    11 import locale
       
    12 import os
       
    13 
       
    14 from textwrap import TextWrapper
       
    15 from time import strftime, strptime
       
    16 
       
    17 from VirtualMailManager import ENCODING
       
    18 from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
       
    19 from VirtualMailManager.common import human_size, size_in_bytes, \
       
    20      version_str, format_domain_default
       
    21 from VirtualMailManager.constants import __copyright__, __date__, \
       
    22      __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
       
    23      DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \
       
    24      RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
       
    25 from VirtualMailManager.errors import VMMError
       
    26 from VirtualMailManager.password import list_schemes
       
    27 from VirtualMailManager.serviceset import SERVICES
       
    28 
       
    29 __all__ = (
       
    30     'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
       
    31     'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
       
    32     'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete',
       
    33     'config_get', 'config_set', 'configure',
       
    34     'domain_add', 'domain_delete',  'domain_info', 'domain_quota',
       
    35     'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_',
       
    36     'list_domains', 'list_pwschemes', 'list_users', 'list_aliases',
       
    37     'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete',
       
    38     'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name',
       
    39     'user_password', 'user_quota', 'user_services', 'user_transport',
       
    40     'user_note', 'version',
       
    41 )
       
    42 
       
    43 _ = lambda msg: msg
       
    44 txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
       
    45 cmd_map = {}
       
    46 
       
    47 
       
    48 class Command(object):
       
    49     """Container class for command information."""
       
    50     __slots__ = ('name', 'alias', 'func', 'args', 'descr')
       
    51 
       
    52     def __init__(self, name, alias, func, args, descr):
       
    53         """Create a new Command instance.
       
    54 
       
    55         Arguments:
       
    56 
       
    57         `name` : str
       
    58           the command name, e.g. ``addalias``
       
    59         `alias` : str
       
    60           the command's short alias, e.g. ``aa``
       
    61         `func` : callable
       
    62           the function to handle the command
       
    63         `args` : str
       
    64           argument placeholders, e.g. ``aliasaddress``
       
    65         `descr` : str
       
    66           short description of the command
       
    67         """
       
    68         self.name = name
       
    69         self.alias = alias
       
    70         self.func = func
       
    71         self.args = args
       
    72         self.descr = descr
       
    73 
       
    74     @property
       
    75     def usage(self):
       
    76         """the command's usage info."""
       
    77         return u'%s %s %s' % (prog, self.name, self.args)
       
    78 
       
    79 
       
    80 class RunContext(object):
       
    81     """Contains all information necessary to run a subcommand."""
       
    82     __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
       
    83     plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
       
    84 
       
    85     def __init__(self, argv, handler, command):
       
    86         """Create a new RunContext"""
       
    87         self.argc = len(argv)
       
    88         self.args = [unicode(arg, ENCODING) for arg in argv]
       
    89         self.cget = handler.cfg_dget
       
    90         self.hdlr = handler
       
    91         self.scmd = command
       
    92 
       
    93 
       
    94 def alias_add(ctx):
       
    95     """create a new alias e-mail address"""
       
    96     if ctx.argc < 3:
       
    97         usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
       
    98               ctx.scmd)
       
    99     elif ctx.argc < 4:
       
   100         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   101     ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
       
   102 
       
   103 
       
   104 def alias_delete(ctx):
       
   105     """delete the specified alias e-mail address or one of its destinations"""
       
   106     if ctx.argc < 3:
       
   107         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
   108     elif ctx.argc < 4:
       
   109         ctx.hdlr.alias_delete(ctx.args[2].lower())
       
   110     else:
       
   111         ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3])
       
   112 
       
   113 
       
   114 def alias_info(ctx):
       
   115     """show the destination(s) of the specified alias"""
       
   116     if ctx.argc < 3:
       
   117         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
   118     address = ctx.args[2].lower()
       
   119     try:
       
   120         _print_aliase_info(address, ctx.hdlr.alias_info(address))
       
   121     except VMMError, err:
       
   122         if err.code is ACCOUNT_EXISTS:
       
   123             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   124                   'object': address})
       
   125             ctx.scmd = ctx.args[1] = 'userinfo'
       
   126             user_info(ctx)
       
   127         elif err.code is RELOCATED_EXISTS:
       
   128             w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   129                   'object': address})
       
   130             ctx.scmd = ctx.args[1] = 'relocatedinfo'
       
   131             relocated_info(ctx)
       
   132         else:
       
   133             raise
       
   134 
       
   135 
       
   136 def aliasdomain_add(ctx):
       
   137     """create a new alias for an existing domain"""
       
   138     if ctx.argc < 3:
       
   139         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   140                                  u'domain name.'), ctx.scmd)
       
   141     elif ctx.argc < 4:
       
   142         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   143               ctx.scmd)
       
   144     ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
       
   145 
       
   146 
       
   147 def aliasdomain_delete(ctx):
       
   148     """delete the specified alias domain"""
       
   149     if ctx.argc < 3:
       
   150         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   151     ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
       
   152 
       
   153 
       
   154 def aliasdomain_info(ctx):
       
   155     """show the destination of the given alias domain"""
       
   156     if ctx.argc < 3:
       
   157         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   158     try:
       
   159         _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
       
   160     except VMMError, err:
       
   161         if err.code is ALIASDOMAIN_ISDOMAIN:
       
   162             w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
       
   163                   'object': ctx.args[2].lower()})
       
   164             ctx.scmd = ctx.args[1] = 'domaininfo'
       
   165             domain_info(ctx)
       
   166         else:
       
   167             raise
       
   168 
       
   169 
       
   170 def aliasdomain_switch(ctx):
       
   171     """assign the given alias domain to an other domain"""
       
   172     if ctx.argc < 3:
       
   173         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   174                                  u'domain name.'), ctx.scmd)
       
   175     elif ctx.argc < 4:
       
   176         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   177               ctx.scmd)
       
   178     ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
       
   179 
       
   180 
       
   181 def catchall_add(ctx):
       
   182     """create a new catchall alias e-mail address"""
       
   183     if ctx.argc < 3:
       
   184         usage(EX_MISSING_ARGS, _(u'Missing domain and destination.'),
       
   185               ctx.scmd)
       
   186     elif ctx.argc < 4:
       
   187         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   188     ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:])
       
   189 
       
   190 
       
   191 def catchall_delete(ctx):
       
   192     """delete the specified destination or all of the catchall destination"""
       
   193     if ctx.argc < 3:
       
   194         usage(EX_MISSING_ARGS, _(u'Missing domain.'), ctx.scmd)
       
   195     elif ctx.argc < 4:
       
   196         ctx.hdlr.catchall_delete(ctx.args[2].lower())
       
   197     else:
       
   198         ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3])
       
   199 
       
   200 
       
   201 def catchall_info(ctx):
       
   202     """show the catchall destination(s) of the specified domain"""
       
   203     if ctx.argc < 3:
       
   204         usage(EX_MISSING_ARGS, _(u'Missing domain.'), ctx.scmd)
       
   205     address = ctx.args[2].lower()
       
   206     _print_catchall_info(address, ctx.hdlr.catchall_info(address))
       
   207 
       
   208 
       
   209 def config_get(ctx):
       
   210     """show the actual value of the configuration option"""
       
   211     if ctx.argc < 3:
       
   212         usage(EX_MISSING_ARGS, _(u"Missing option name."), ctx.scmd)
       
   213 
       
   214     noop = lambda option: option
       
   215     opt_formater = {
       
   216         'misc.dovecot_version': version_str,
       
   217         'domain.quota_bytes': human_size,
       
   218     }
       
   219 
       
   220     option = ctx.args[2].lower()
       
   221     w_std('%s = %s' % (option, opt_formater.get(option,
       
   222                        noop)(ctx.cget(option))))
       
   223 
       
   224 
       
   225 def config_set(ctx):
       
   226     """set a new value for the configuration option"""
       
   227     if ctx.argc < 3:
       
   228         usage(EX_MISSING_ARGS, _(u'Missing option and new value.'), ctx.scmd)
       
   229     if ctx.argc < 4:
       
   230         usage(EX_MISSING_ARGS, _(u'Missing new configuration value.'),
       
   231               ctx.scmd)
       
   232     ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3])
       
   233 
       
   234 
       
   235 def configure(ctx):
       
   236     """start interactive configuration modus"""
       
   237     if ctx.argc < 3:
       
   238         ctx.hdlr.configure()
       
   239     else:
       
   240         ctx.hdlr.configure(ctx.args[2].lower())
       
   241 
       
   242 
       
   243 def domain_add(ctx):
       
   244     """create a new domain"""
       
   245     if ctx.argc < 3:
       
   246         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   247     elif ctx.argc < 4:
       
   248         ctx.hdlr.domain_add(ctx.args[2].lower())
       
   249     else:
       
   250         ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
       
   251     if ctx.cget('domain.auto_postmaster'):
       
   252         w_std(_(u'Creating account for postmaster@%s') % ctx.args[2].lower())
       
   253         ctx.scmd = 'useradd'
       
   254         ctx.args = [prog, ctx.scmd, u'postmaster@' + ctx.args[2].lower()]
       
   255         ctx.argc = 3
       
   256         user_add(ctx)
       
   257 
       
   258 
       
   259 def domain_delete(ctx):
       
   260     """delete the given domain and all its alias domains"""
       
   261     if ctx.argc < 3:
       
   262         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   263     elif ctx.argc < 4:
       
   264         ctx.hdlr.domain_delete(ctx.args[2].lower())
       
   265     elif ctx.args[3].lower() == 'force':
       
   266         ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
       
   267     else:
       
   268         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   269               ctx.scmd)
       
   270 
       
   271 
       
   272 def domain_info(ctx):
       
   273     """display information about the given domain"""
       
   274     if ctx.argc < 3:
       
   275         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   276     if ctx.argc < 4:
       
   277         details = None
       
   278     else:
       
   279         details = ctx.args[3].lower()
       
   280         if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
       
   281                            'relocated', 'catchall'):
       
   282             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   283                   ctx.scmd)
       
   284     try:
       
   285         info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
       
   286     except VMMError, err:
       
   287         if err.code is DOMAIN_ALIAS_EXISTS:
       
   288             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
       
   289                   'object': ctx.args[2].lower()})
       
   290             ctx.scmd = ctx.args[1] = 'aliasdomaininfo'
       
   291             aliasdomain_info(ctx)
       
   292         else:
       
   293             raise
       
   294     else:
       
   295         q_limit = u'Storage: %(bytes)s; Messages: %(messages)s'
       
   296         if not details:
       
   297             info['bytes'] = human_size(info['bytes'])
       
   298             info['messages'] = locale.format('%d', info['messages'], True)
       
   299             info['quota limit/user'] = q_limit % info
       
   300             _print_info(ctx, info, _(u'Domain'))
       
   301         else:
       
   302             info[0]['bytes'] = human_size(info[0]['bytes'])
       
   303             info[0]['messages'] = locale.format('%d', info[0]['messages'],
       
   304                                                 True)
       
   305             info[0]['quota limit/user'] = q_limit % info[0]
       
   306             _print_info(ctx, info[0], _(u'Domain'))
       
   307             if details == u'accounts':
       
   308                 _print_list(info[1], _(u'accounts'))
       
   309             elif details == u'aliasdomains':
       
   310                 _print_list(info[1], _(u'alias domains'))
       
   311             elif details == u'aliases':
       
   312                 _print_list(info[1], _(u'aliases'))
       
   313             elif details == u'relocated':
       
   314                 _print_list(info[1], _(u'relocated users'))
       
   315             elif details == u'catchall':
       
   316                 _print_list(info[1], _(u'catch-all destinations'))
       
   317             else:
       
   318                 _print_list(info[1], _(u'alias domains'))
       
   319                 _print_list(info[2], _(u'accounts'))
       
   320                 _print_list(info[3], _(u'aliases'))
       
   321                 _print_list(info[4], _(u'relocated users'))
       
   322                 _print_list(info[5], _(u'catch-all destinations'))
       
   323 
       
   324 
       
   325 def domain_quota(ctx):
       
   326     """update the quota limit of the specified domain"""
       
   327     if ctx.argc < 3:
       
   328         usage(EX_MISSING_ARGS, _(u'Missing domain name and storage value.'),
       
   329               ctx.scmd)
       
   330     if ctx.argc < 4:
       
   331         usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
       
   332     messages = 0
       
   333     force = None
       
   334     try:
       
   335         bytes_ = size_in_bytes(ctx.args[3])
       
   336     except (ValueError, TypeError):
       
   337         usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
       
   338               ctx.args[3], ctx.scmd)
       
   339     if ctx.argc < 5:
       
   340         pass
       
   341     elif ctx.argc < 6:
       
   342         try:
       
   343             messages = int(ctx.args[4])
       
   344         except ValueError:
       
   345             if ctx.args[4].lower() != 'force':
       
   346                 usage(INVALID_ARGUMENT,
       
   347                       _(u"Neither a valid number of messages nor the keyword "
       
   348                         u"'force': '%s'") % ctx.args[4], ctx.scmd)
       
   349             force = 'force'
       
   350     else:
       
   351         try:
       
   352             messages = int(ctx.args[4])
       
   353         except ValueError:
       
   354             usage(INVALID_ARGUMENT,
       
   355                   _(u"Not a valid number of messages: '%s'") % ctx.args[4],
       
   356                   ctx.scmd)
       
   357         if ctx.args[5].lower() != 'force':
       
   358             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[5],
       
   359                   ctx.scmd)
       
   360         force = 'force'
       
   361     ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force)
       
   362 
       
   363 
       
   364 def domain_services(ctx):
       
   365     """allow all named service and block the uncredited."""
       
   366     if ctx.argc < 3:
       
   367         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   368     services = []
       
   369     force = False
       
   370     if ctx.argc is 3:
       
   371         pass
       
   372     elif ctx.argc is 4:
       
   373         arg = ctx.args[3].lower()
       
   374         if arg in SERVICES:
       
   375             services.append(arg)
       
   376         elif arg == 'force':
       
   377             force = True
       
   378         else:
       
   379             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % arg,
       
   380                   ctx.scmd)
       
   381     else:
       
   382         services.extend([service.lower() for service in ctx.args[3:-1]])
       
   383         arg = ctx.args[-1].lower()
       
   384         if arg == 'force':
       
   385             force = True
       
   386         else:
       
   387             services.append(arg)
       
   388         unknown = [service for service in services if service not in SERVICES]
       
   389         if unknown:
       
   390             usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
       
   391                   ' '.join(unknown), ctx.scmd)
       
   392     ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force],
       
   393                              *services)
       
   394 
       
   395 
       
   396 def domain_transport(ctx):
       
   397     """update the transport of the specified domain"""
       
   398     if ctx.argc < 3:
       
   399         usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
       
   400               ctx.scmd)
       
   401     if ctx.argc < 4:
       
   402         usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
       
   403     if ctx.argc < 5:
       
   404         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
       
   405     else:
       
   406         force = ctx.args[4].lower()
       
   407         if force != 'force':
       
   408             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
       
   409                   ctx.scmd)
       
   410         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
       
   411 
       
   412 
       
   413 def domain_note(ctx):
       
   414     """update the note of the given domain"""
       
   415     if ctx.argc < 3:
       
   416         usage(EX_MISSING_ARGS, _(u'Missing domain name.'),
       
   417               ctx.scmd)
       
   418     elif ctx.argc < 4:
       
   419         note = None
       
   420     else:
       
   421         note = ' '.join(ctx.args[3:])
       
   422     ctx.hdlr.domain_note(ctx.args[2].lower(), note)
       
   423 
       
   424 
       
   425 def get_user(ctx):
       
   426     """get the address of the user with the given UID"""
       
   427     if ctx.argc < 3:
       
   428         usage(EX_MISSING_ARGS, _(u'Missing UID.'), ctx.scmd)
       
   429     _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
       
   430 
       
   431 
       
   432 def help_(ctx):
       
   433     """print help messages."""
       
   434     if ctx.argc > 2:
       
   435         hlptpc = ctx.args[2].lower()
       
   436         if hlptpc in cmd_map:
       
   437             topic = hlptpc
       
   438         else:
       
   439             for scmd in cmd_map.itervalues():
       
   440                 if scmd.alias == hlptpc:
       
   441                     topic = scmd.name
       
   442                     break
       
   443             else:
       
   444                 usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
       
   445                       ctx.args[2], ctx.scmd)
       
   446         # FIXME
       
   447         w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)')
       
   448 
       
   449     old_ii = txt_wrpr.initial_indent
       
   450     old_si = txt_wrpr.subsequent_indent
       
   451     txt_wrpr.initial_indent = ' '
       
   452     # len(max(_overview.iterkeys(), key=len)) #Py25
       
   453     txt_wrpr.subsequent_indent = 20 * ' '
       
   454     order = cmd_map.keys()
       
   455     order.sort()
       
   456 
       
   457     w_std(_(u'List of available subcommands:') + '\n')
       
   458     for key in order:
       
   459         w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
       
   460 
       
   461     txt_wrpr.initial_indent = old_ii
       
   462     txt_wrpr.subsequent_indent = old_si
       
   463     txt_wrpr.initial_indent = ''
       
   464 
       
   465 
       
   466 def list_domains(ctx):
       
   467     """list all domains / search domains by pattern"""
       
   468     matching = ctx.argc > 2
       
   469     if matching:
       
   470         gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
       
   471     else:
       
   472         gids, domains = ctx.hdlr.domain_list()
       
   473     _print_domain_list(gids, domains, matching)
       
   474 
       
   475 
       
   476 def list_pwschemes(ctx_unused):
       
   477     """Prints all usable password schemes and password encoding suffixes."""
       
   478     # TODO: Remove trailing colons from keys.
       
   479     # For now it is to late, the translators has stared their work
       
   480     keys = (_(u'Usable password schemes:'), _(u'Usable encoding suffixes:'))
       
   481     old_ii, old_si = txt_wrpr.initial_indent, txt_wrpr.subsequent_indent
       
   482     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
       
   483     txt_wrpr.width = txt_wrpr.width - 8
       
   484 
       
   485     for key, value in zip(keys, list_schemes()):
       
   486         if key.endswith(':'):  # who knows … (see TODO above)
       
   487             #key = key.rpartition(':')[0]
       
   488             key = key[:-1]  # This one is for Py24
       
   489         w_std(key, len(key) * '-')
       
   490         w_std('\n'.join(txt_wrpr.wrap(' '.join(value))), '')
       
   491 
       
   492     txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si
       
   493     txt_wrpr.width = txt_wrpr.width + 8
       
   494 
       
   495 
       
   496 def list_addresses(ctx, limit=None):
       
   497     """List all addresses / search addresses by pattern. The output can be
       
   498     limited with TYPE_ACCOUNT, TYPE_ALIAS and TYPE_RELOCATED, which can be
       
   499     bitwise ORed as a combination. Not specifying a limit is the same as
       
   500     combining all three."""
       
   501     if limit is None:
       
   502         limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
       
   503     matching = ctx.argc > 2
       
   504     if matching:
       
   505         gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower())
       
   506     else:
       
   507         gids, addresses = ctx.hdlr.address_list(limit)
       
   508     _print_address_list(limit, gids, addresses, matching)
       
   509 
       
   510 
       
   511 def list_users(ctx):
       
   512     """list all user accounts / search user accounts by pattern"""
       
   513     return list_addresses(ctx, TYPE_ACCOUNT)
       
   514 
       
   515 def list_aliases(ctx):
       
   516     """list all aliases / search aliases by pattern"""
       
   517     return list_addresses(ctx, TYPE_ALIAS)
       
   518 
       
   519 def list_relocated(ctx):
       
   520     """list all relocated records / search relocated records by pattern"""
       
   521     return list_addresses(ctx, TYPE_RELOCATED)
       
   522 
       
   523 
       
   524 def relocated_add(ctx):
       
   525     """create a new record for a relocated user"""
       
   526     if ctx.argc < 3:
       
   527         usage(EX_MISSING_ARGS,
       
   528               _(u'Missing relocated address and destination.'), ctx.scmd)
       
   529     elif ctx.argc < 4:
       
   530         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   531     ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
       
   532 
       
   533 
       
   534 def relocated_delete(ctx):
       
   535     """delete the record of the relocated user"""
       
   536     if ctx.argc < 3:
       
   537         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   538     ctx.hdlr.relocated_delete(ctx.args[2].lower())
       
   539 
       
   540 
       
   541 def relocated_info(ctx):
       
   542     """print information about a relocated user"""
       
   543     if ctx.argc < 3:
       
   544         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   545     relocated = ctx.args[2].lower()
       
   546     try:
       
   547         _print_relocated_info(addr=relocated,
       
   548                               dest=ctx.hdlr.relocated_info(relocated))
       
   549     except VMMError, err:
       
   550         if err.code is ACCOUNT_EXISTS:
       
   551             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   552                   'object': relocated})
       
   553             ctx.scmd = ctx.args[1] = 'userinfoi'
       
   554             user_info(ctx)
       
   555         elif err.code is ALIAS_EXISTS:
       
   556             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   557                   'object': relocated})
       
   558             ctx.scmd = ctx.args[1] = 'aliasinfo'
       
   559             alias_info(ctx)
       
   560         else:
       
   561             raise
       
   562 
       
   563 
       
   564 def user_add(ctx):
       
   565     """create a new e-mail user with the given address"""
       
   566     if ctx.argc < 3:
       
   567         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   568     elif ctx.argc < 4:
       
   569         password = None
       
   570     else:
       
   571         password = ctx.args[3]
       
   572     gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password)
       
   573     if ctx.argc < 4 and gen_pass:
       
   574         w_std(_(u"Generated password: %s") % gen_pass)
       
   575 
       
   576 
       
   577 def user_delete(ctx):
       
   578     """delete the specified user"""
       
   579     if ctx.argc < 3:
       
   580         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   581     elif ctx.argc < 4:
       
   582         ctx.hdlr.user_delete(ctx.args[2].lower())
       
   583     elif ctx.args[3].lower() == 'force':
       
   584         ctx.hdlr.user_delete(ctx.args[2].lower(), True)
       
   585     else:
       
   586         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   587               ctx.scmd)
       
   588 
       
   589 
       
   590 def user_info(ctx):
       
   591     """display information about the given address"""
       
   592     if ctx.argc < 3:
       
   593         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   594     if ctx.argc < 4:
       
   595         details = None
       
   596     else:
       
   597         details = ctx.args[3].lower()
       
   598         if details not in ('aliases', 'du', 'full'):
       
   599             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   600                   ctx.scmd)
       
   601     try:
       
   602         info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
       
   603     except VMMError, err:
       
   604         if err.code is ALIAS_EXISTS:
       
   605             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   606                   'object': ctx.args[2].lower()})
       
   607             ctx.scmd = ctx.args[1] = 'aliasinfo'
       
   608             alias_info(ctx)
       
   609         elif err.code is RELOCATED_EXISTS:
       
   610             w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   611                   'object': ctx.args[2].lower()})
       
   612             ctx.scmd = ctx.args[1] = 'relocatedinfo'
       
   613             relocated_info(ctx)
       
   614         else:
       
   615             raise
       
   616     else:
       
   617         if details in (None, 'du'):
       
   618             info['quota storage'] = _format_quota_usage(info['ql_bytes'],
       
   619                     info['uq_bytes'], True, info['ql_domaindefault'])
       
   620             info['quota messages'] = _format_quota_usage(info['ql_messages'],
       
   621                     info['uq_messages'], domaindefault=info['ql_domaindefault'])
       
   622             _print_info(ctx, info, _(u'Account'))
       
   623         else:
       
   624             info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'],
       
   625                     info[0]['uq_bytes'], True, info[0]['ql_domaindefault'])
       
   626             info[0]['quota messages'] = \
       
   627                 _format_quota_usage(info[0]['ql_messages'],
       
   628                                     info[0]['uq_messages'],
       
   629                                     domaindefault=info[0]['ql_domaindefault'])
       
   630             _print_info(ctx, info[0], _(u'Account'))
       
   631             _print_list(info[1], _(u'alias addresses'))
       
   632 
       
   633 
       
   634 def user_name(ctx):
       
   635     """set or update the real name for an address"""
       
   636     if ctx.argc < 3:
       
   637         usage(EX_MISSING_ARGS, _(u"Missing e-mail address and user's name."),
       
   638               ctx.scmd)
       
   639     elif ctx.argc < 4:
       
   640         name = None
       
   641     else:
       
   642         name = ctx.args[3]
       
   643     ctx.hdlr.user_name(ctx.args[2].lower(), name)
       
   644 
       
   645 
       
   646 def user_password(ctx):
       
   647     """update the password for the given address"""
       
   648     if ctx.argc < 3:
       
   649         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   650     elif ctx.argc < 4:
       
   651         password = None
       
   652     else:
       
   653         password = ctx.args[3]
       
   654     ctx.hdlr.user_password(ctx.args[2].lower(), password)
       
   655 
       
   656 
       
   657 def user_note(ctx):
       
   658     """update the note of the given address"""
       
   659     if ctx.argc < 3:
       
   660         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'),
       
   661               ctx.scmd)
       
   662     elif ctx.argc < 4:
       
   663         note = None
       
   664     else:
       
   665         note = ' '.join(ctx.args[3:])
       
   666     ctx.hdlr.user_note(ctx.args[2].lower(), note)
       
   667 
       
   668 
       
   669 def user_quota(ctx):
       
   670     """update the quota limit for the given address"""
       
   671     if ctx.argc < 3:
       
   672         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and storage value.'),
       
   673               ctx.scmd)
       
   674     elif ctx.argc < 4:
       
   675         usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
       
   676     if ctx.args[3] != 'domain':
       
   677         try:
       
   678             bytes_ = size_in_bytes(ctx.args[3])
       
   679         except (ValueError, TypeError):
       
   680             usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
       
   681                   ctx.args[3], ctx.scmd)
       
   682     else:
       
   683         bytes_ = ctx.args[3]
       
   684     if ctx.argc < 5:
       
   685         messages = 0
       
   686     else:
       
   687         try:
       
   688             messages = int(ctx.args[4])
       
   689         except ValueError:
       
   690             usage(INVALID_ARGUMENT,
       
   691                   _(u"Not a valid number of messages: '%s'") % ctx.args[4],
       
   692                   ctx.scmd)
       
   693     ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages)
       
   694 
       
   695 
       
   696 def user_services(ctx):
       
   697     """allow all named service and block the uncredited."""
       
   698     if ctx.argc < 3:
       
   699         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   700     services = []
       
   701     if ctx.argc >= 4:
       
   702         services.extend([service.lower() for service in ctx.args[3:]])
       
   703         unknown = [service for service in services if service not in SERVICES]
       
   704         if unknown and ctx.args[3] != 'domain':
       
   705             usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
       
   706                   ' '.join(unknown), ctx.scmd)
       
   707     ctx.hdlr.user_services(ctx.args[2].lower(), *services)
       
   708 
       
   709 
       
   710 def user_transport(ctx):
       
   711     """update the transport of the given address"""
       
   712     if ctx.argc < 3:
       
   713         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
       
   714               ctx.scmd)
       
   715     if ctx.argc < 4:
       
   716         usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
       
   717     ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
       
   718 
       
   719 
       
   720 def usage(errno, errmsg, subcommand=None):
       
   721     """print usage message for the given command or all commands.
       
   722     When errno > 0, sys,exit(errno) will interrupt the program.
       
   723     """
       
   724     if subcommand and subcommand in cmd_map:
       
   725         w_err(errno, _(u"Error: %s") % errmsg,
       
   726               _(u"usage: ") + cmd_map[subcommand].usage)
       
   727 
       
   728     # TP: Please adjust translated words like the original text.
       
   729     # (It's a table header.) Extract from usage text:
       
   730     # usage: vmm subcommand arguments
       
   731     #   short long
       
   732     #   subcommand                arguments
       
   733     #
       
   734     #   da    domainadd           fqdn [transport]
       
   735     #   dd    domaindelete        fqdn [force]
       
   736     u_head = _(u"""usage: %s subcommand arguments
       
   737   short long
       
   738   subcommand                arguments\n""") % prog
       
   739     order = cmd_map.keys()
       
   740     order.sort()
       
   741     w_err(0, u_head)
       
   742     for key in order:
       
   743         scmd = cmd_map[key]
       
   744         w_err(0, '  %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
       
   745     w_err(errno, '', _(u"Error: %s") % errmsg)
       
   746 
       
   747 
       
   748 def version(ctx_unused):
       
   749     """Write version and copyright information to stdout."""
       
   750     w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
       
   751     # TP: The words 'from', 'version' and 'on' are used in
       
   752     # the version information, e.g.:
       
   753     # vmm, version 0.5.2 (from 09/09/09)
       
   754     # Python 2.5.4 on FreeBSD
       
   755         _(u'version'), __version__, _(u'from'),
       
   756         strftime(locale.nl_langinfo(locale.D_FMT),
       
   757             strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
       
   758         os.sys.version.split()[0], _(u'on'), os.uname()[0],
       
   759         __copyright__, prog,
       
   760         _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
       
   761 
       
   762 
       
   763 def update_cmd_map():
       
   764     """Update the cmd_map, after gettext's _ was installed."""
       
   765     cmd = Command
       
   766     cmd_map.update({
       
   767     # Account commands
       
   768     'getuser': cmd('getuser', 'gu', get_user, 'uid',
       
   769                    _(u'get the address of the user with the given UID')),
       
   770     'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
       
   771                    _(u'create a new e-mail user with the given address')),
       
   772     'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
       
   773                       _(u'delete the specified user')),
       
   774     'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
       
   775                     _(u'display information about the given address')),
       
   776     'username': cmd('username', 'un', user_name, 'address name',
       
   777                     _(u'set or update the real name for an address')),
       
   778     'userpassword': cmd('userpassword', 'up', user_password,
       
   779                         'address [password]',
       
   780                         _(u'update the password for the given address')),
       
   781     'userquota': cmd('userquota', 'uq', user_quota,
       
   782                      'address storage [messages] | address domain',
       
   783                      _(u'update the quota limit for the given address')),
       
   784     'userservices': cmd('userservices', 'us', user_services,
       
   785                         'address [service ...] | address domain',
       
   786                         _(u'enables the specified services and disables all '
       
   787                           u'not specified services')),
       
   788     'usertransport': cmd('usertransport', 'ut', user_transport,
       
   789                          'address transport | address domain',
       
   790                          _(u'update the transport of the given address')),
       
   791     'usernote': cmd('usernote', 'uo', user_note,
       
   792                     'address note',
       
   793                     _(u'update the note of the given address')),
       
   794     # Alias commands
       
   795     'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
       
   796                     _(u'create a new alias e-mail address with one or more '
       
   797                       u'destinations')),
       
   798     'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
       
   799                        'address [destination]',
       
   800                        _(u'delete the specified alias e-mail address or one '
       
   801                          u'of its destinations')),
       
   802     'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
       
   803                      _(u'show the destination(s) of the specified alias')),
       
   804     # AliasDomain commands
       
   805     'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
       
   806                           'fqdn destination',
       
   807                           _(u'create a new alias for an existing domain')),
       
   808     'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
       
   809                              'fqdn', _(u'delete the specified alias domain')),
       
   810     'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
       
   811                          _(u'show the destination of the given alias domain')),
       
   812     'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
       
   813                              'fqdn destination', _(u'assign the given alias '
       
   814                              'domain to an other domain')),
       
   815     # CatchallAlias commands
       
   816     'catchalladd': cmd('catchalladd', 'caa', catchall_add,
       
   817                        'fqdn destination ...',
       
   818                        _(u'add one or more catch-all destinations for a '
       
   819                          u'domain')),
       
   820     'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete,
       
   821                        'fqdn [destination]',
       
   822                        _(u'delete the specified catch-all destination or all '
       
   823                          u'of a domain\'s destinations')),
       
   824     'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn',
       
   825                      _(u'show the catch-all destination(s) of the specified domain')),
       
   826     # Domain commands
       
   827     'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
       
   828                      _(u'create a new domain')),
       
   829     'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
       
   830                       _(u'delete the given domain and all its alias domains')),
       
   831     'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
       
   832                       _(u'display information about the given domain')),
       
   833     'domainquota': cmd('domainquota', 'dq', domain_quota,
       
   834                        'fqdn storage [messages] [force]',
       
   835                        _(u'update the quota limit of the specified domain')),
       
   836     'domainservices': cmd('domainservices', 'ds', domain_services,
       
   837                           'fqdn [service ...] [force]',
       
   838                           _(u'enables the specified services and disables all '
       
   839                             u'not specified services of the given domain')),
       
   840     'domaintransport': cmd('domaintransport', 'dt', domain_transport,
       
   841                            'fqdn transport [force]',
       
   842                            _(u'update the transport of the specified domain')),
       
   843     'domainnote': cmd('domainnote', 'do', domain_note,
       
   844                       'fqdn note',
       
   845                       _(u'update the note of the given domain')),
       
   846     # List commands
       
   847     'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
       
   848                       _(u'list all domains or search for domains by pattern')),
       
   849     'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]',
       
   850                       _(u'list all addresses or search for addresses by pattern')),
       
   851     'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
       
   852                       _(u'list all user accounts or search for accounts by pattern')),
       
   853     'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]',
       
   854                       _(u'list all aliases or search for aliases by pattern')),
       
   855     'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]',
       
   856                       _(u'list all relocated entries or search for entries by pattern')),
       
   857     # Relocated commands
       
   858     'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
       
   859                         'address newaddress',
       
   860                         _(u'create a new record for a relocated user')),
       
   861     'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
       
   862                            'address',
       
   863                            _(u'delete the record of the relocated user')),
       
   864     'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
       
   865                          _(u'print information about a relocated user')),
       
   866     # cli commands
       
   867     'configget': cmd('configget', 'cg', config_get, 'option',
       
   868                      _('show the actual value of the configuration option')),
       
   869     'configset': cmd('configset', 'cs', config_set, 'option value',
       
   870                       _('set a new value for the configuration option')),
       
   871     'configure': cmd('configure', 'cf', configure, '[section]',
       
   872                      _(u'start interactive configuration modus')),
       
   873     'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '',
       
   874                          _(u'lists all usable password schemes and password '
       
   875                            u'encoding suffixes')),
       
   876     'help': cmd('help', 'h', help_, '[subcommand]',
       
   877                 _(u'show a help overview or help for the given subcommand')),
       
   878     'version': cmd('version', 'v', version, '',
       
   879                    _(u'show version and copyright information')),
       
   880     })
       
   881 
       
   882 
       
   883 def _get_order(ctx):
       
   884     """returns a tuple with (key, 1||0) tuples. Used by functions, which
       
   885     get a dict from the handler."""
       
   886     order = ()
       
   887     if ctx.scmd == 'domaininfo':
       
   888         order = ((u'domain name', 0), (u'gid', 1), (u'domain directory', 0),
       
   889                  (u'quota limit/user', 0), (u'active services', 0),
       
   890                  (u'transport', 0), (u'alias domains', 0), (u'accounts', 0),
       
   891                  (u'aliases', 0), (u'relocated', 0), (u'catch-all dests', 0))
       
   892     elif ctx.scmd == 'userinfo':
       
   893         if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
       
   894            ctx.cget('account.disk_usage'):
       
   895             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   896                      (u'home', 0), (u'mail_location', 0),
       
   897                      (u'quota storage', 0), (u'quota messages', 0),
       
   898                      (u'disk usage', 0), (u'transport', 0), (u'smtp', 1),
       
   899                      (u'pop3', 1), (u'imap', 1), (u'sieve', 1))
       
   900         else:
       
   901             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   902                      (u'home', 0), (u'mail_location', 0),
       
   903                      (u'quota storage', 0), (u'quota messages', 0),
       
   904                      (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
       
   905                      (u'imap', 1), (u'sieve', 1))
       
   906     elif ctx.scmd == 'getuser':
       
   907         order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
       
   908     return order
       
   909 
       
   910 
       
   911 def _format_quota_usage(limit, used, human=False, domaindefault=False):
       
   912     """Put quota's limit / usage / percentage in a formatted string."""
       
   913     if human:
       
   914         q_usage = {
       
   915             'used': human_size(used),
       
   916             'limit': human_size(limit),
       
   917         }
       
   918     else:
       
   919         q_usage = {
       
   920             'used': locale.format('%d', used, True),
       
   921             'limit': locale.format('%d', limit, True),
       
   922         }
       
   923     if limit:
       
   924         q_usage['percent'] = locale.format('%6.2f', 100. / limit * used, True)
       
   925     else:
       
   926         q_usage['percent'] = locale.format('%6.2f', 0, True)
       
   927     #  Py25: fmt = format_domain_default if domaindefault else lambda s: s
       
   928     if domaindefault:
       
   929         fmt = format_domain_default
       
   930     else:
       
   931         fmt = lambda s: s
       
   932     return fmt(_(u'[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
       
   933 
       
   934 
       
   935 def _print_info(ctx, info, title):
       
   936     """Print info dicts."""
       
   937     # TP: used in e.g. 'Domain information' or 'Account information'
       
   938     msg = u'%s %s' % (title, _(u'information'))
       
   939     w_std(msg, u'-' * len(msg))
       
   940     for key, upper in _get_order(ctx):
       
   941         if upper:
       
   942             w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key]))
       
   943         else:
       
   944             w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key]))
       
   945     print
       
   946     note = info.get('note', None)
       
   947     if note is not None:
       
   948         _print_note(note)
       
   949 
       
   950 def _print_note(note):
       
   951     msg = _(u'Note')
       
   952     w_std(msg, u'-' * len(msg))
       
   953     old_ii = txt_wrpr.initial_indent
       
   954     old_si = txt_wrpr.subsequent_indent
       
   955     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
       
   956     txt_wrpr.width -= 8
       
   957     for para in note.split('\n'):
       
   958         w_std(txt_wrpr.fill(para))
       
   959     txt_wrpr.width += 8
       
   960     txt_wrpr.subsequent_indent = old_si
       
   961     txt_wrpr.initial_indent = old_ii
       
   962 
       
   963 def _print_list(alist, title):
       
   964     """Print a list."""
       
   965     # TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
       
   966     msg = u'%s %s' % (_(u'Existing'), title)
       
   967     w_std(msg, u'-' * len(msg))
       
   968     if alist:
       
   969         if title != _(u'alias domains'):
       
   970             w_std(*(u'\t%s' % item for item in alist))
       
   971         else:
       
   972             for domain in alist:
       
   973                 if not domain.startswith('xn--'):
       
   974                     w_std(u'\t%s' % domain)
       
   975                 else:
       
   976                     w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
       
   977         print
       
   978     else:
       
   979         w_std(_(u'\tNone'), '')
       
   980 
       
   981 
       
   982 def _print_aliase_info(alias, destinations):
       
   983     """Print the alias address and all its destinations"""
       
   984     title = _(u'Alias information')
       
   985     w_std(title, u'-' * len(title))
       
   986     w_std(_(u'\tMail for %s will be redirected to:') % alias)
       
   987     w_std(*(u'\t     * %s' % dest for dest in destinations))
       
   988     print
       
   989 
       
   990 
       
   991 def _print_catchall_info(domain, destinations):
       
   992     """Print the catchall destinations of a domain"""
       
   993     title = _(u'Catch-all information')
       
   994     w_std(title, u'-' * len(title))
       
   995     w_std(_(u'\tMail to unknown localparts in domain %s will be sent to:')
       
   996           % domain)
       
   997     w_std(*(u'\t     * %s' % dest for dest in destinations))
       
   998     print
       
   999 
       
  1000 
       
  1001 def _print_relocated_info(**kwargs):
       
  1002     """Print the old and new addresses of a relocated user."""
       
  1003     title = _(u'Relocated information')
       
  1004     w_std(title, u'-' * len(title))
       
  1005     w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
       
  1006 
       
  1007 
       
  1008 def _format_domain(domain, main=True):
       
  1009     """format (prefix/convert) the domain name."""
       
  1010     if domain.startswith('xn--'):
       
  1011         domain = u'%s (%s)' % (domain, domain.decode('idna'))
       
  1012     if main:
       
  1013         return u'\t[+] %s' % domain
       
  1014     return u'\t[-]     %s' % domain
       
  1015 
       
  1016 
       
  1017 def _print_domain_list(dids, domains, matching):
       
  1018     """Print a list of (matching) domains/alias domains."""
       
  1019     if matching:
       
  1020         title = _(u'Matching domains')
       
  1021     else:
       
  1022         title = _(u'Existing domains')
       
  1023     w_std(title, '-' * len(title))
       
  1024     if domains:
       
  1025         for did in dids:
       
  1026             if domains[did][0] is not None:
       
  1027                 w_std(_format_domain(domains[did][0]))
       
  1028             if len(domains[did]) > 1:
       
  1029                 w_std(*(_format_domain(a, False) for a in domains[did][1:]))
       
  1030     else:
       
  1031         w_std(_('\tNone'))
       
  1032     print
       
  1033 
       
  1034 
       
  1035 def _print_address_list(which, dids, addresses, matching):
       
  1036     """Print a list of (matching) addresses."""
       
  1037     _trans = { TYPE_ACCOUNT                  : _('user accounts')
       
  1038              , TYPE_ALIAS                    : _('aliases')
       
  1039              , TYPE_RELOCATED                : _('relocated entries')
       
  1040              , TYPE_ACCOUNT | TYPE_ALIAS
       
  1041                  : _('user accounts and aliases')
       
  1042              , TYPE_ACCOUNT | TYPE_RELOCATED
       
  1043                  : _('user accounts and relocated entries')
       
  1044              , TYPE_ALIAS | TYPE_RELOCATED
       
  1045                  : _('aliases and relocated entries')
       
  1046              , TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED : _('addresses')
       
  1047              }
       
  1048     try:
       
  1049         if matching:
       
  1050             title = _(u'Matching %s') % _trans[which]
       
  1051         else:
       
  1052             title = _(u'Existing %s') % _trans[which]
       
  1053         w_std(title, '-' * len(title))
       
  1054     except KeyError:
       
  1055         raise VMMError(_("Invalid address type for list: '%s'") % which,
       
  1056                        INVALID_ARGUMENT)
       
  1057     if addresses:
       
  1058         if which & (which - 1) == 0:
       
  1059             # only one type is requested, so no type indicator
       
  1060             _trans = { TYPE_ACCOUNT   : _('')
       
  1061                      , TYPE_ALIAS     : _('')
       
  1062                      , TYPE_RELOCATED : _('')
       
  1063                      }
       
  1064         else:
       
  1065             _trans = { TYPE_ACCOUNT   : _('u')
       
  1066                      , TYPE_ALIAS     : _('a')
       
  1067                      , TYPE_RELOCATED : _('r')
       
  1068                      }
       
  1069         for did in dids:
       
  1070             for addr, atype, aliasdomain in addresses[did]:
       
  1071                 if aliasdomain:
       
  1072                     leader = '[%s-]' % _trans[atype]
       
  1073                 else:
       
  1074                     leader = '[%s+]' % _trans[atype]
       
  1075                 w_std('\t%s %s' % (leader, addr))
       
  1076     else:
       
  1077         w_std(_('\tNone'))
       
  1078     print
       
  1079 
       
  1080 
       
  1081 def _print_aliasdomain_info(info):
       
  1082     """Print alias domain information."""
       
  1083     title = _(u'Alias domain information')
       
  1084     for key in ('alias', 'domain'):
       
  1085         if info[key].startswith('xn--'):
       
  1086             info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
       
  1087     w_std(title, '-' * len(title),
       
  1088           _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
       
  1089           info, '')
       
  1090 
       
  1091 del _