VirtualMailManager/cli/subcommands.py
branchv0.6.x
changeset 340 4515afec62e5
child 341 6709d0faf2f5
equal deleted inserted replaced
339:abff2de9eed0 340:4515afec62e5
       
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright 2007 - 2010, 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.account import SERVICES
       
    19 from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
       
    20 from VirtualMailManager.constants import __copyright__, __date__, \
       
    21      __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
       
    22      DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, RELOCATED_EXISTS
       
    23 from VirtualMailManager.errors import VMMError
       
    24 
       
    25 __all__ = (
       
    26     'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
       
    27     'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
       
    28     'aliasdomain_switch', 'configure', 'domain_add', 'domain_delete',
       
    29     'domain_info', 'domain_transport', 'get_user', 'help_', 'list_domains',
       
    30     'relocated_add', 'relocated_delete', 'relocated_info', 'user_add',
       
    31     'user_delete', 'user_disable', 'user_enable', 'user_info', 'user_name',
       
    32     'user_password', 'user_transport', 'version',
       
    33 )
       
    34 
       
    35 _ = lambda msg: msg
       
    36 txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
       
    37 
       
    38 
       
    39 class Command(object):
       
    40     """Container class for command information."""
       
    41     __slots__ = ('name', 'alias', 'func', 'args', 'descr')
       
    42 
       
    43     def __init__(self, name, alias, func, args, descr):
       
    44         """Create a new Command instance.
       
    45 
       
    46         Arguments:
       
    47 
       
    48         `name` : str
       
    49           the command name, e.g. ``addalias``
       
    50         `alias` : str
       
    51           the command's short alias, e.g. ``aa``
       
    52         `func` : callable
       
    53           the function to handle the command
       
    54         `args` : str
       
    55           argument placeholders, e.g. ``aliasaddress``
       
    56         `descr` : str
       
    57           short description of the command
       
    58         """
       
    59         self.name = name
       
    60         self.alias = alias
       
    61         self.func = func
       
    62         self.args = args
       
    63         self.descr = descr
       
    64 
       
    65     @property
       
    66     def usage(self):
       
    67         """the command's usage info."""
       
    68         return u'%s %s %s' % (prog, self.name, self.args)
       
    69 
       
    70 
       
    71 class RunContext(object):
       
    72     """Contains all information necessary to run a subcommand."""
       
    73     __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
       
    74     plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
       
    75 
       
    76     def __init__(self, argv, handler, command):
       
    77         """Create a new RunContext"""
       
    78         self.argc = len(argv)
       
    79         self.args = [unicode(arg, ENCODING) for arg in argv]
       
    80         self.cget = handler.cfg_dget
       
    81         self.hdlr = handler
       
    82         self.scmd = command
       
    83 
       
    84 
       
    85 def alias_add(ctx):
       
    86     """create a new alias e-mail address"""
       
    87     if ctx.argc < 3:
       
    88         usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
       
    89               ctx.scmd)
       
    90     elif ctx.argc < 4:
       
    91         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
    92     ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
       
    93 
       
    94 
       
    95 def alias_delete(ctx):
       
    96     """delete the specified alias e-mail address or one of its destinations"""
       
    97     if ctx.argc < 3:
       
    98         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
    99     elif ctx.argc < 4:
       
   100         ctx.hdlr.alias_delete(ctx.args[2].lower())
       
   101     else:
       
   102         ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3])
       
   103 
       
   104 
       
   105 def alias_info(ctx):
       
   106     """show the destination(s) of the specified alias"""
       
   107     if ctx.argc < 3:
       
   108         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
   109     address = ctx.args[2].lower()
       
   110     try:
       
   111         _print_aliase_info(address, ctx.hdlr.alias_info(address))
       
   112     except VMMError, err:
       
   113         if err.code is ACCOUNT_EXISTS:
       
   114             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   115                   'object': address})
       
   116             ctx.args[1] = 'userinfo'
       
   117             user_info(ctx)
       
   118         elif err.code is RELOCATED_EXISTS:
       
   119             w_std(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   120                   'object': address})
       
   121             ctx.args[1] = 'relocatedinfo'
       
   122             relocated_info(ctx)
       
   123         else:
       
   124             raise
       
   125 
       
   126 
       
   127 def aliasdomain_add(ctx):
       
   128     """create a new alias for an existing domain"""
       
   129     if ctx.argc < 3:
       
   130         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   131                                  u'domain name.'), ctx.scmd)
       
   132     elif ctx.argc < 4:
       
   133         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   134               ctx.scmd)
       
   135     ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
       
   136 
       
   137 
       
   138 def aliasdomain_delete(ctx):
       
   139     """delete the specified alias domain"""
       
   140     if ctx.argc < 3:
       
   141         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   142     ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
       
   143 
       
   144 
       
   145 def aliasdomain_info(ctx):
       
   146     """show the destination of the given alias domain"""
       
   147     if ctx.argc < 3:
       
   148         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   149     try:
       
   150         _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
       
   151     except VMMError, err:
       
   152         if err.code is ALIASDOMAIN_ISDOMAIN:
       
   153             w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
       
   154                   'object': ctx.args[2].lower()})
       
   155             ctx.args[1] = 'domaininfo'
       
   156             domain_info(ctx)
       
   157         else:
       
   158             raise
       
   159 
       
   160 
       
   161 def aliasdomain_switch(ctx):
       
   162     """assign the given alias domain to an other domain"""
       
   163     if ctx.argc < 3:
       
   164         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   165                                  u'domain name.'), ctx.scmd)
       
   166     elif ctx.argc < 4:
       
   167         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   168               ctx.scmd)
       
   169     ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
       
   170 
       
   171 
       
   172 def configure(ctx):
       
   173     """start interactive configuration modus"""
       
   174     if ctx.argc < 3:
       
   175         ctx.hdlr.configure()
       
   176     else:
       
   177         ctx.hdlr.configure(ctx.args[2].lower())
       
   178 
       
   179 
       
   180 def domain_add(ctx):
       
   181     """create a new domain"""
       
   182     if ctx.argc < 3:
       
   183         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   184     elif ctx.argc < 4:
       
   185         ctx.hdlr.domain_add(ctx.args[2].lower())
       
   186     else:
       
   187         ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
       
   188 
       
   189 
       
   190 def domain_delete(ctx):
       
   191     """delete the given domain and all its alias domains"""
       
   192     if ctx.argc < 3:
       
   193         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   194     elif ctx.argc < 4:
       
   195         ctx.hdlr.domain_delete(ctx.args[2].lower())
       
   196     elif ctx.args[3].lower() == 'force':
       
   197         ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
       
   198     else:
       
   199         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   200               ctx.scmd)
       
   201 
       
   202 
       
   203 def domain_info(ctx):
       
   204     """display information about the given domain"""
       
   205     if ctx.argc < 3:
       
   206         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   207     if ctx.argc < 4:
       
   208         details = None
       
   209     else:
       
   210         details = ctx.args[3].lower()
       
   211         if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
       
   212                            'relocated'):
       
   213             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   214                   ctx.scmd)
       
   215     try:
       
   216         info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
       
   217     except VMMError, err:
       
   218         if err.code is DOMAIN_ALIAS_EXISTS:
       
   219             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
       
   220                   'object': ctx.args[2].lower()})
       
   221             ctx.args[1] = 'aliasdomaininfo'
       
   222             aliasdomain_info(ctx)
       
   223         else:
       
   224             raise
       
   225     else:
       
   226         if not details:
       
   227             _print_info(ctx, info, _(u'Domain'))
       
   228         else:
       
   229             _print_info(ctx, info[0], _(u'Domain'))
       
   230             if details == u'accounts':
       
   231                 _print_list(info[1], _(u'accounts'))
       
   232             elif details == u'aliasdomains':
       
   233                 _print_list(info[1], _(u'alias domains'))
       
   234             elif details == u'aliases':
       
   235                 _print_list(info[1], _(u'aliases'))
       
   236             elif details == u'relocated':
       
   237                 _print_list(info[1], _(u'relocated users'))
       
   238             else:
       
   239                 _print_list(info[1], _(u'alias domains'))
       
   240                 _print_list(info[2], _(u'accounts'))
       
   241                 _print_list(info[3], _(u'aliases'))
       
   242                 _print_list(info[4], _(u'relocated users'))
       
   243 
       
   244 
       
   245 def domain_transport(ctx):
       
   246     """update the transport of the specified domain"""
       
   247     if ctx.argc < 3:
       
   248         usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
       
   249               ctx.scmd)
       
   250     if ctx.argc < 4:
       
   251         usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
       
   252     if ctx.argc < 5:
       
   253         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
       
   254     else:
       
   255         force = ctx.args[4].lower()
       
   256         if force != 'force':
       
   257             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
       
   258                   ctx.scmd)
       
   259         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
       
   260 
       
   261 
       
   262 def get_user(ctx):
       
   263     """get the address of the user with the given UID"""
       
   264     if ctx.argc < 3:
       
   265         usage(EX_MISSING_ARGS, _(u'Missing userid.'), ctx.scmd)
       
   266     _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
       
   267 
       
   268 
       
   269 def help_(ctx):
       
   270     """print help messgaes."""
       
   271     if ctx.argc > 2:
       
   272         hlptpc = ctx.args[2].lower()
       
   273         if hlptpc in cmd_map:
       
   274             topic = hlptpc
       
   275         else:
       
   276             for scmd in cmd_map.itervalues():
       
   277                 if scmd.alias == hlptpc:
       
   278                     topic = scmd.name
       
   279                     break
       
   280             else:
       
   281                 usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
       
   282                       ctx.args[2], ctx.scmd)
       
   283         # FIXME
       
   284         w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)')
       
   285 
       
   286     old_ii = txt_wrpr.initial_indent
       
   287     old_si = txt_wrpr.subsequent_indent
       
   288     txt_wrpr.initial_indent = ' '
       
   289     # len(max(_overview.iterkeys(), key=len)) #Py25
       
   290     txt_wrpr.subsequent_indent = 20 * ' '
       
   291     order = cmd_map.keys()
       
   292     order.sort()
       
   293 
       
   294     w_std(_(u'List of available subcommands:') + '\n')
       
   295     for key in order:
       
   296         w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
       
   297 
       
   298     txt_wrpr.initial_indent = old_ii
       
   299     txt_wrpr.subsequent_indent = old_si
       
   300     txt_wrpr.initial_indent = ''
       
   301 
       
   302 
       
   303 def list_domains(ctx):
       
   304     """list all domains / search domains by pattern"""
       
   305     matching = ctx.argc > 2
       
   306     if matching:
       
   307         gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
       
   308     else:
       
   309         gids, domains = ctx.hdlr.domain_list()
       
   310     _print_domain_list(gids, domains, matching)
       
   311 
       
   312 
       
   313 def relocated_add(ctx):
       
   314     """create a new record for a relocated user"""
       
   315     if ctx.argc < 3:
       
   316         usage(EX_MISSING_ARGS,
       
   317               _(u'Missing relocated address and destination.'), ctx.scmd)
       
   318     elif ctx.argc < 4:
       
   319         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   320     ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
       
   321 
       
   322 
       
   323 def relocated_delete(ctx):
       
   324     """delete the record of the relocated user"""
       
   325     if ctx.argc < 3:
       
   326         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   327     ctx.hdlr.relocated_delete(ctx.args[2].lower())
       
   328 
       
   329 
       
   330 def relocated_info(ctx):
       
   331     """print information about a relocated user"""
       
   332     if ctx.argc < 3:
       
   333         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   334     relocated = ctx.args[2].lower()
       
   335     try:
       
   336         _print_relocated_info(addr=relocated,
       
   337                               dest=ctx.hdlr.relocated_info(relocated))
       
   338     except VMMError, err:
       
   339         if err.code is ACCOUNT_EXISTS:
       
   340             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   341                   'object': relocated})
       
   342             ctx.args[1] = 'userinfoi'
       
   343             user_info(ctx)
       
   344         elif err.code is ALIAS_EXISTS:
       
   345             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   346                   'object': relocated})
       
   347             ctx.args[1] = 'aliasinfo'
       
   348             alias_info(ctx)
       
   349         else:
       
   350             raise
       
   351 
       
   352 
       
   353 def user_add(ctx):
       
   354     """create a new e-mail user with the given address"""
       
   355     if ctx.argc < 3:
       
   356         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   357     elif ctx.argc < 4:
       
   358         password = None
       
   359     else:
       
   360         password = ctx.args[3]
       
   361     ctx.hdlr.user_add(ctx.args[2].lower(), password)
       
   362 
       
   363 
       
   364 def user_delete(ctx):
       
   365     """delete the specified user"""
       
   366     if ctx.argc < 3:
       
   367         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   368     elif ctx.argc < 4:
       
   369         ctx.hdlr.user_delete(ctx.args[2].lower())
       
   370     elif ctx.args[3].lower() == 'force':
       
   371         ctx.hdlr.user_delete(ctx.args[2].lower(), True)
       
   372     else:
       
   373         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   374               ctx.scmd)
       
   375 
       
   376 
       
   377 def user_disable(ctx):
       
   378     """deactivate all/the given service(s) for a user"""
       
   379     if ctx.argc < 3:
       
   380         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   381     elif ctx.argc < 4:
       
   382         ctx.hdlr.user_disable(ctx.args[2].lower())
       
   383     else:
       
   384         services = [service.lower() for service in ctx.args[3:]]
       
   385         unknown = [service for service in services if service not in SERVICES]
       
   386         if unknown:
       
   387             usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") %
       
   388                   ' '.join(unknown), ctx.scmd)
       
   389         ctx.hdlr.user_disable(ctx.args[2].lower(), services)
       
   390 
       
   391 
       
   392 def user_enable(ctx):
       
   393     """activate all or the given service(s) for a user"""
       
   394     if ctx.argc < 3:
       
   395         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   396     elif ctx.argc < 4:
       
   397         ctx.hdlr.user_enable(ctx.args[2].lower())
       
   398     else:
       
   399         services = [service.lower() for service in ctx.args[3:]]
       
   400         unknown = [service for service in services if service not in SERVICES]
       
   401         if unknown:
       
   402             usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") %
       
   403                   ' '.join(unknown), ctx.scmd)
       
   404         ctx.hdlr.user_enable(ctx.args[2].lower(), services)
       
   405 
       
   406 
       
   407 def user_info(ctx):
       
   408     """display information about the given address"""
       
   409     if ctx.argc < 3:
       
   410         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   411     if ctx.argc < 4:
       
   412         details = None
       
   413     else:
       
   414         details = ctx.args[3].lower()
       
   415         if details not in ('aliases', 'du', 'full'):
       
   416             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   417                   ctx.scmd)
       
   418     try:
       
   419         info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
       
   420     except VMMError, err:
       
   421         if err.code is ALIAS_EXISTS:
       
   422             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   423                   'object': ctx.args[2].lower()})
       
   424             ctx.args[1] = 'aliasinfo'
       
   425             alias_info(ctx)
       
   426         elif err.code is RELOCATED_EXISTS:
       
   427             w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   428                   'object': ctx.args[2].lower()})
       
   429             ctx.args[1] = 'relocatedinfo'
       
   430             relocated_info(ctx)
       
   431         else:
       
   432             raise
       
   433     else:
       
   434         if details in (None, 'du'):
       
   435             _print_info(ctx, info, _(u'Account'))
       
   436         else:
       
   437             _print_info(ctx, info[0], _(u'Account'))
       
   438             _print_list(info[1], _(u'alias addresses'))
       
   439 
       
   440 
       
   441 def user_name(ctx):
       
   442     """set or update the real name for an address"""
       
   443     if ctx.argc < 3:
       
   444         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and user’s name.'),
       
   445               ctx.scmd)
       
   446     if ctx.argc < 4:
       
   447         usage(EX_MISSING_ARGS, _(u'Missing user’s name.'), ctx.scmd)
       
   448     ctx.hdlr.user_name(ctx.args[2].lower(), ctx.args[3])
       
   449 
       
   450 
       
   451 def user_password(ctx):
       
   452     """update the password for the given address"""
       
   453     if ctx.argc < 3:
       
   454         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   455     elif ctx.argc < 4:
       
   456         password = None
       
   457     else:
       
   458         password = ctx.args[3]
       
   459     ctx.hdlr.user_password(ctx.args[2].lower(), password)
       
   460 
       
   461 
       
   462 def user_transport(ctx):
       
   463     """update the transport of the given address"""
       
   464     if ctx.argc < 3:
       
   465         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
       
   466               ctx.scmd)
       
   467     if ctx.argc < 4:
       
   468         usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
       
   469     ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
       
   470 
       
   471 
       
   472 def usage(errno, errmsg, subcommand=None):
       
   473     """print usage message for the given command or all commands.
       
   474     When errno > 0, sys,exit(errno) will interrupt the program.
       
   475     """
       
   476     if subcommand and subcommand in cmd_map:
       
   477         w_err(errno, _(u"Error: %s") % errmsg,
       
   478               _(u"usage: ") + cmd_map[subcommand].usage)
       
   479 
       
   480     # TP: Please adjust translated words like the original text.
       
   481     # (It's a table header.) Extract from usage text:
       
   482     # usage: vmm subcommand arguments
       
   483     #   short long
       
   484     #   subcommand                arguments
       
   485     #
       
   486     #   da    domainadd           fqdn [transport]
       
   487     #   dd    domaindelete        fqdn [force]
       
   488     u_head = _(u"""usage: %s subcommand arguments
       
   489   short long
       
   490   subcommand                arguments\n""") % prog
       
   491     order = cmd_map.keys()
       
   492     order.sort()
       
   493     w_err(0, u_head)
       
   494     for key in order:
       
   495         scmd = cmd_map[key]
       
   496         w_err(0, '  %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
       
   497     w_err(errno, '', _(u"Error: %s") % errmsg)
       
   498 
       
   499 
       
   500 def version(ctx):
       
   501     """Write version and copyright information to stdout."""
       
   502     w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
       
   503     # TP: The words 'from', 'version' and 'on' are used in
       
   504     # the version information, e.g.:
       
   505     # vmm, version 0.5.2 (from 09/09/09)
       
   506     # Python 2.5.4 on FreeBSD
       
   507         _(u'version'), __version__, _(u'from'),
       
   508         strftime(locale.nl_langinfo(locale.D_FMT),
       
   509             strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
       
   510         os.sys.version.split()[0], _(u'on'), os.uname()[0],
       
   511         __copyright__, prog,
       
   512         _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
       
   513 
       
   514 cmd = Command
       
   515 cmd_map = {  # {{{
       
   516     # Account commands
       
   517     'getuser': cmd('getuser', 'gu', get_user, 'uid',
       
   518                    _(u'get the address of the user with the given UID')),
       
   519     'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
       
   520                    _(u'create a new e-mail user with the given address')),
       
   521     'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
       
   522                       _(u'delete the specified user')),
       
   523     'userdisable': cmd('userdisable', 'u0', user_disable,
       
   524                        'address [service ...]',
       
   525                        _(u'deactivate all/the given service(s) for a user')),
       
   526     'userenable': cmd('userenable', 'u1', user_enable, 'address [service ...]',
       
   527                       _(u'activate all or the given service(s) for a user')),
       
   528     'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
       
   529                     _(u'display information about the given address')),
       
   530     'username': cmd('username', 'un', user_name, 'address name',
       
   531                     _(u'set or update the real name for an address')),
       
   532     'userpassword': cmd('userpassword', 'up', user_password,
       
   533                         'address [password]',
       
   534                         _(u'update the password for the given address')),
       
   535     'usertransport': cmd('usertransport', 'ut', user_transport,
       
   536                          'address transport',
       
   537                          _(u'update the transport of the given address')),
       
   538     # Alias commands
       
   539     'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
       
   540                     _(u'create a new alias e-mail address')),
       
   541     'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
       
   542                        'address [destination]',
       
   543                        _(u'delete the specified alias e-mail address or one '
       
   544                          u'of its destinations')),
       
   545     'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
       
   546                      _(u'show the destination(s) of the specified alias')),
       
   547     # AliasDomain commands
       
   548     'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
       
   549                           'fqdn destination',
       
   550                           _(u'create a new alias for an existing domain')),
       
   551     'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
       
   552                              'fqdn', _(u'delete the specified alias domain')),
       
   553     'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
       
   554                          _(u'show the destination of the given alias domain')),
       
   555     'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
       
   556                              'fqdn destination',
       
   557                        _(u'assign the given alias domain to an other domain')),
       
   558     # Domain commands
       
   559     'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
       
   560                      _(u'create a new domain')),
       
   561     'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
       
   562                       _(u'delete the given domain and all its alias domains')),
       
   563     'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
       
   564                       _(u'display information about the given domain')),
       
   565     'domaintransport': cmd('domaintransport', 'dt', domain_transport,
       
   566                            'fqdn transport [force]',
       
   567                            _(u'update the transport of the specified domain')),
       
   568     'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
       
   569                        _(u'list all domains / search domains by pattern')),
       
   570     # Relocated commands
       
   571     'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
       
   572                         'address newaddress',
       
   573                         _(u'create a new record for a relocated user')),
       
   574     'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
       
   575                            'address',
       
   576                            _(u'delete the record of the relocated user')),
       
   577     'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
       
   578                          _(u'print information about a relocated user')),
       
   579     # cli commands
       
   580     'configure': cmd('configure', 'cf', configure, '[section]',
       
   581                      _(u'start interactive configuration modus')),
       
   582     'help': cmd('help', 'h', help_, '[subcommand]',
       
   583                 _(u'show a help overview or help for the given subcommand')),
       
   584     'version': cmd('version', 'v', version, '',
       
   585                    _(u'show version and copyright information')),
       
   586 }  # }}}
       
   587 
       
   588 
       
   589 def _get_order(ctx):
       
   590     """returns a tuple with (key, 1||0) tuples. Used by functions, which
       
   591     get a dict from the handler."""
       
   592     order = ()
       
   593     if ctx.scmd == 'domaininfo':
       
   594         order = ((u'domainname', 0), (u'gid', 1), (u'transport', 0),
       
   595                  (u'domaindir', 0), (u'aliasdomains', 0), (u'accounts', 0),
       
   596                  (u'aliases', 0), (u'relocated', 0))
       
   597     elif ctx.scmd == 'userinfo':
       
   598         dc12 = ctx.cget('misc.dovecot_version') >= 0x10200b02
       
   599         sieve = (u'managesieve', u'sieve')[dc12]
       
   600         if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
       
   601            ctx.cget('account.disk_usage'):
       
   602             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   603                      (u'home', 0), (u'mail_location', 0), (u'disk usage', 0),
       
   604                      (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
       
   605                      (u'imap', 1), (sieve, 1))
       
   606         else:
       
   607             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   608                      (u'home', 0), (u'mail_location', 0), (u'transport', 0),
       
   609                      (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve, 1))
       
   610     elif ctx.scmd == 'getuser':
       
   611         order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
       
   612     return order
       
   613 
       
   614 
       
   615 def _print_info(ctx, info, title):
       
   616     """Print info dicts."""
       
   617     # TP: used in e.g. 'Domain information' or 'Account information'
       
   618     msg = u'%s %s' % (title, _(u'information'))
       
   619     w_std(msg, u'-' * len(msg))
       
   620     for key, upper in _get_order(ctx):
       
   621         if upper:
       
   622             w_std(u'\t%s: %s' % (key.upper().ljust(15, u'.'), info[key]))
       
   623         else:
       
   624             w_std(u'\t%s: %s' % (key.title().ljust(15, u'.'), info[key]))
       
   625     print
       
   626 
       
   627 
       
   628 def _print_list(alist, title):
       
   629     """Print a list."""
       
   630     # TP: used in e.g. 'Available alias addresses' or 'Available accounts'
       
   631     msg = u'%s %s' % (_(u'Available'), title)
       
   632     w_std(msg, u'-' * len(msg))
       
   633     if alist:
       
   634         if title != _(u'alias domains'):
       
   635             w_std(*(u'\t%s' % item for item in alist))
       
   636         else:
       
   637             for domain in alist:
       
   638                 if not domain.startswith('xn--'):
       
   639                     w_std(u'\t%s' % domain)
       
   640                 else:
       
   641                     w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
       
   642         print
       
   643     else:
       
   644         w_std(_(u'\tNone'), '')
       
   645 
       
   646 
       
   647 def _print_aliase_info(alias, destinations):
       
   648     """Print the alias address and all its destinations"""
       
   649     title = _(u'Alias information')
       
   650     w_std(title, u'-' * len(title))
       
   651     w_std(_(u'\tMail for %s will be redirected to:') % alias)
       
   652     w_std(*(u'\t     * %s' % dest for dest in destinations))
       
   653     print
       
   654 
       
   655 
       
   656 def _print_relocated_info(**kwargs):
       
   657     """Print the old and new addresses of a relocated user."""
       
   658     title = _(u'Relocated information')
       
   659     w_std(title, u'-' * len(title))
       
   660     w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
       
   661 
       
   662 
       
   663 def _format_domain(domain, main=True):
       
   664     """format (prefix/convert) the domain name."""
       
   665     if domain.startswith('xn--'):
       
   666         domain = u'%s (%s)' % (domain, domain.decode('idna'))
       
   667     if main:
       
   668         return u'\t[+] %s' % domain
       
   669     return u'\t[-]     %s' % domain
       
   670 
       
   671 
       
   672 def _print_domain_list(dids, domains, matching):
       
   673     """Print a list of (matching) domains/alias domains."""
       
   674     if matching:
       
   675         title = _(u'Matching domains')
       
   676     else:
       
   677         title = _(u'Available domains')
       
   678     w_std(title, '-' * len(title))
       
   679     if domains:
       
   680         for did in dids:
       
   681             if domains[did][0] is not None:
       
   682                 w_std(_format_domain(domains[did][0]))
       
   683             if len(domains[did]) > 1:
       
   684                 w_std(*(_format_domain(a, False) for a in domains[did][1:]))
       
   685     else:
       
   686         w_std(_('\tNone'))
       
   687     print
       
   688 
       
   689 
       
   690 def _print_aliasdomain_info(info):
       
   691     """Print alias domain information."""
       
   692     title = _(u'Alias domain information')
       
   693     for key in ('alias', 'domain'):
       
   694         if info[key].startswith('xn--'):
       
   695             info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
       
   696     w_std(title, '-' * len(title),
       
   697           _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
       
   698           info, '')
       
   699 
       
   700 del _