# HG changeset patch # User Pascal Volk # Date 1280984882 0 # Node ID 4515afec62e595d6cec1099a745f03babc552393 # Parent abff2de9eed0c72ef1d3abb2c05b62807351fe32 vmm: Renamed to VirtualMailManager/cli/main.py. Splitted subcommands out to VirtualMailManager/cli/subcommands.py. vmm: New created with minimal code. diff -r abff2de9eed0 -r 4515afec62e5 VirtualMailManager/cli/__init__.py --- a/VirtualMailManager/cli/__init__.py Thu Aug 05 02:38:20 2010 +0000 +++ b/VirtualMailManager/cli/__init__.py Thu Aug 05 05:08:02 2010 +0000 @@ -19,11 +19,12 @@ from VirtualMailManager.errors import VMMError -__all__ = ('get_winsize', 'read_pass', 'w_err', 'w_std') +__all__ = ('prog', 'get_winsize', 'read_pass', 'w_err', 'w_std') _ = lambda msg: msg _std_write = os.sys.stdout.write _err_write = os.sys.stderr.write +prog = os.path.basename(os.sys.argv[0]) def w_std(*args): diff -r abff2de9eed0 -r 4515afec62e5 VirtualMailManager/cli/main.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/cli/main.py Thu Aug 05 05:08:02 2010 +0000 @@ -0,0 +1,66 @@ +# -*- coding: UTF-8 -*- +# Copyright 2007 - 2010, Pascal Volk +# See COPYING for distribution information. +""" + VirtualMailManager.cli.main + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + VirtualMailManager's command line interface. +""" + +from VirtualMailManager import ENCODING, errors +from VirtualMailManager.config import ConfigValueError +from VirtualMailManager.cli import w_err +from VirtualMailManager.cli.handler import CliHandler +from VirtualMailManager.constants import DATABASE_ERROR, EX_MISSING_ARGS, \ + EX_SUCCESS, EX_UNKNOWN_COMMAND, EX_USER_INTERRUPT +from VirtualMailManager.cli.subcommands import RunContext, cmd_map, usage + + +_ = lambda msg: msg + +def _get_handler(): + """Try to get a CliHandler. Exit the program when an error occurs.""" + try: + handler = CliHandler() + handler.cfg_install() + except (errors.NotRootError, errors.PermissionError, errors.VMMError, + errors.ConfigError, ConfigValueError), err: + w_err(err.code, _(u'Error: %s') % err.msg) + else: + return handler + + +def run(argv): + if len(argv) < 2: + usage(EX_MISSING_ARGS, _(u"You must specify a subcommand at least")) + + sub_cmd = argv[1].lower() + if sub_cmd in cmd_map: + cmd_func = cmd_map[sub_cmd].func + else: + for cmd in cmd_map.itervalues(): + if cmd.alias == sub_cmd: + cmd_func = cmd.func + sub_cmd = cmd.name + break + else: + usage(EX_UNKNOWN_COMMAND, _(u"Unknown subcommand: '%s'") % sub_cmd) + + handler = _get_handler() + run_ctx = RunContext(argv, handler, sub_cmd) + try: + cmd_func(run_ctx) + except (EOFError, KeyboardInterrupt): + # TP: We have to cry, because root has killed/interrupted vmm + # with Ctrl+C or Ctrl+D. + w_err(EX_USER_INTERRUPT, '', _(u'Ouch!'), '') + except errors.VMMError, err: + if err.code != DATABASE_ERROR: + w_err(err.code, _(u'Error: %s') % err.msg) + w_err(err.code, unicode(err.msg, ENCODING, 'replace')) + if handler.has_warnings(): + w_err(0, _(u'Warnings:'), *handler.get_warnings()) + return EX_SUCCESS + +del _ diff -r abff2de9eed0 -r 4515afec62e5 VirtualMailManager/cli/subcommands.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualMailManager/cli/subcommands.py Thu Aug 05 05:08:02 2010 +0000 @@ -0,0 +1,700 @@ +# -*- coding: UTF-8 -*- +# Copyright 2007 - 2010, Pascal Volk +# See COPYING for distribution information. +""" + VirtualMailManager.cli.subcommands + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + VirtualMailManager's cli subcommands. +""" + +import locale +import os + +from textwrap import TextWrapper +from time import strftime, strptime + +from VirtualMailManager import ENCODING +from VirtualMailManager.account import SERVICES +from VirtualMailManager.cli import get_winsize, prog, w_err, w_std +from VirtualMailManager.constants import __copyright__, __date__, \ + __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \ + DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, RELOCATED_EXISTS +from VirtualMailManager.errors import VMMError + +__all__ = ( + 'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete', + 'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info', + 'aliasdomain_switch', 'configure', 'domain_add', 'domain_delete', + 'domain_info', 'domain_transport', 'get_user', 'help_', 'list_domains', + 'relocated_add', 'relocated_delete', 'relocated_info', 'user_add', + 'user_delete', 'user_disable', 'user_enable', 'user_info', 'user_name', + 'user_password', 'user_transport', 'version', +) + +_ = lambda msg: msg +txt_wrpr = TextWrapper(width=get_winsize()[1] - 1) + + +class Command(object): + """Container class for command information.""" + __slots__ = ('name', 'alias', 'func', 'args', 'descr') + + def __init__(self, name, alias, func, args, descr): + """Create a new Command instance. + + Arguments: + + `name` : str + the command name, e.g. ``addalias`` + `alias` : str + the command's short alias, e.g. ``aa`` + `func` : callable + the function to handle the command + `args` : str + argument placeholders, e.g. ``aliasaddress`` + `descr` : str + short description of the command + """ + self.name = name + self.alias = alias + self.func = func + self.args = args + self.descr = descr + + @property + def usage(self): + """the command's usage info.""" + return u'%s %s %s' % (prog, self.name, self.args) + + +class RunContext(object): + """Contains all information necessary to run a subcommand.""" + __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd') + plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s') + + def __init__(self, argv, handler, command): + """Create a new RunContext""" + self.argc = len(argv) + self.args = [unicode(arg, ENCODING) for arg in argv] + self.cget = handler.cfg_dget + self.hdlr = handler + self.scmd = command + + +def alias_add(ctx): + """create a new alias e-mail address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'), + ctx.scmd) + elif ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd) + ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:]) + + +def alias_delete(ctx): + """delete the specified alias e-mail address or one of its destinations""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.alias_delete(ctx.args[2].lower()) + else: + ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3]) + + +def alias_info(ctx): + """show the destination(s) of the specified alias""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd) + address = ctx.args[2].lower() + try: + _print_aliase_info(address, ctx.hdlr.alias_info(address)) + except VMMError, err: + if err.code is ACCOUNT_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo', + 'object': address}) + ctx.args[1] = 'userinfo' + user_info(ctx) + elif err.code is RELOCATED_EXISTS: + w_std(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo', + 'object': address}) + ctx.args[1] = 'relocatedinfo' + relocated_info(ctx) + else: + raise + + +def aliasdomain_add(ctx): + """create a new alias for an existing domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination ' + u'domain name.'), ctx.scmd) + elif ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'), + ctx.scmd) + ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower()) + + +def aliasdomain_delete(ctx): + """delete the specified alias domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd) + ctx.hdlr.aliasdomain_delete(ctx.args[2].lower()) + + +def aliasdomain_info(ctx): + """show the destination of the given alias domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd) + try: + _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower())) + except VMMError, err: + if err.code is ALIASDOMAIN_ISDOMAIN: + w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo', + 'object': ctx.args[2].lower()}) + ctx.args[1] = 'domaininfo' + domain_info(ctx) + else: + raise + + +def aliasdomain_switch(ctx): + """assign the given alias domain to an other domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination ' + u'domain name.'), ctx.scmd) + elif ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'), + ctx.scmd) + ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower()) + + +def configure(ctx): + """start interactive configuration modus""" + if ctx.argc < 3: + ctx.hdlr.configure() + else: + ctx.hdlr.configure(ctx.args[2].lower()) + + +def domain_add(ctx): + """create a new domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.domain_add(ctx.args[2].lower()) + else: + ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3]) + + +def domain_delete(ctx): + """delete the given domain and all its alias domains""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.domain_delete(ctx.args[2].lower()) + elif ctx.args[3].lower() == 'force': + ctx.hdlr.domain_delete(ctx.args[2].lower(), True) + else: + usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3], + ctx.scmd) + + +def domain_info(ctx): + """display information about the given domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd) + if ctx.argc < 4: + details = None + else: + details = ctx.args[3].lower() + if details not in ('accounts', 'aliasdomains', 'aliases', 'full', + 'relocated'): + usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details, + ctx.scmd) + try: + info = ctx.hdlr.domain_info(ctx.args[2].lower(), details) + except VMMError, err: + if err.code is DOMAIN_ALIAS_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo', + 'object': ctx.args[2].lower()}) + ctx.args[1] = 'aliasdomaininfo' + aliasdomain_info(ctx) + else: + raise + else: + if not details: + _print_info(ctx, info, _(u'Domain')) + else: + _print_info(ctx, info[0], _(u'Domain')) + if details == u'accounts': + _print_list(info[1], _(u'accounts')) + elif details == u'aliasdomains': + _print_list(info[1], _(u'alias domains')) + elif details == u'aliases': + _print_list(info[1], _(u'aliases')) + elif details == u'relocated': + _print_list(info[1], _(u'relocated users')) + else: + _print_list(info[1], _(u'alias domains')) + _print_list(info[2], _(u'accounts')) + _print_list(info[3], _(u'aliases')) + _print_list(info[4], _(u'relocated users')) + + +def domain_transport(ctx): + """update the transport of the specified domain""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'), + ctx.scmd) + if ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd) + if ctx.argc < 5: + ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3]) + else: + force = ctx.args[4].lower() + if force != 'force': + usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force, + ctx.scmd) + ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force) + + +def get_user(ctx): + """get the address of the user with the given UID""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing userid.'), ctx.scmd) + _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account')) + + +def help_(ctx): + """print help messgaes.""" + if ctx.argc > 2: + hlptpc = ctx.args[2].lower() + if hlptpc in cmd_map: + topic = hlptpc + else: + for scmd in cmd_map.itervalues(): + if scmd.alias == hlptpc: + topic = scmd.name + break + else: + usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") % + ctx.args[2], ctx.scmd) + # FIXME + w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)') + + old_ii = txt_wrpr.initial_indent + old_si = txt_wrpr.subsequent_indent + txt_wrpr.initial_indent = ' ' + # len(max(_overview.iterkeys(), key=len)) #Py25 + txt_wrpr.subsequent_indent = 20 * ' ' + order = cmd_map.keys() + order.sort() + + w_std(_(u'List of available subcommands:') + '\n') + for key in order: + w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr)))) + + txt_wrpr.initial_indent = old_ii + txt_wrpr.subsequent_indent = old_si + txt_wrpr.initial_indent = '' + + +def list_domains(ctx): + """list all domains / search domains by pattern""" + matching = ctx.argc > 2 + if matching: + gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower()) + else: + gids, domains = ctx.hdlr.domain_list() + _print_domain_list(gids, domains, matching) + + +def relocated_add(ctx): + """create a new record for a relocated user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, + _(u'Missing relocated address and destination.'), ctx.scmd) + elif ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd) + ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3]) + + +def relocated_delete(ctx): + """delete the record of the relocated user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd) + ctx.hdlr.relocated_delete(ctx.args[2].lower()) + + +def relocated_info(ctx): + """print information about a relocated user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd) + relocated = ctx.args[2].lower() + try: + _print_relocated_info(addr=relocated, + dest=ctx.hdlr.relocated_info(relocated)) + except VMMError, err: + if err.code is ACCOUNT_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo', + 'object': relocated}) + ctx.args[1] = 'userinfoi' + user_info(ctx) + elif err.code is ALIAS_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo', + 'object': relocated}) + ctx.args[1] = 'aliasinfo' + alias_info(ctx) + else: + raise + + +def user_add(ctx): + """create a new e-mail user with the given address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + elif ctx.argc < 4: + password = None + else: + password = ctx.args[3] + ctx.hdlr.user_add(ctx.args[2].lower(), password) + + +def user_delete(ctx): + """delete the specified user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.user_delete(ctx.args[2].lower()) + elif ctx.args[3].lower() == 'force': + ctx.hdlr.user_delete(ctx.args[2].lower(), True) + else: + usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3], + ctx.scmd) + + +def user_disable(ctx): + """deactivate all/the given service(s) for a user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.user_disable(ctx.args[2].lower()) + else: + services = [service.lower() for service in ctx.args[3:]] + unknown = [service for service in services if service not in SERVICES] + if unknown: + usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") % + ' '.join(unknown), ctx.scmd) + ctx.hdlr.user_disable(ctx.args[2].lower(), services) + + +def user_enable(ctx): + """activate all or the given service(s) for a user""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + elif ctx.argc < 4: + ctx.hdlr.user_enable(ctx.args[2].lower()) + else: + services = [service.lower() for service in ctx.args[3:]] + unknown = [service for service in services if service not in SERVICES] + if unknown: + usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") % + ' '.join(unknown), ctx.scmd) + ctx.hdlr.user_enable(ctx.args[2].lower(), services) + + +def user_info(ctx): + """display information about the given address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + if ctx.argc < 4: + details = None + else: + details = ctx.args[3].lower() + if details not in ('aliases', 'du', 'full'): + usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details, + ctx.scmd) + try: + info = ctx.hdlr.user_info(ctx.args[2].lower(), details) + except VMMError, err: + if err.code is ALIAS_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo', + 'object': ctx.args[2].lower()}) + ctx.args[1] = 'aliasinfo' + alias_info(ctx) + elif err.code is RELOCATED_EXISTS: + w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo', + 'object': ctx.args[2].lower()}) + ctx.args[1] = 'relocatedinfo' + relocated_info(ctx) + else: + raise + else: + if details in (None, 'du'): + _print_info(ctx, info, _(u'Account')) + else: + _print_info(ctx, info[0], _(u'Account')) + _print_list(info[1], _(u'alias addresses')) + + +def user_name(ctx): + """set or update the real name for an address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address and user’s name.'), + ctx.scmd) + if ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing user’s name.'), ctx.scmd) + ctx.hdlr.user_name(ctx.args[2].lower(), ctx.args[3]) + + +def user_password(ctx): + """update the password for the given address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd) + elif ctx.argc < 4: + password = None + else: + password = ctx.args[3] + ctx.hdlr.user_password(ctx.args[2].lower(), password) + + +def user_transport(ctx): + """update the transport of the given address""" + if ctx.argc < 3: + usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'), + ctx.scmd) + if ctx.argc < 4: + usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd) + ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3]) + + +def usage(errno, errmsg, subcommand=None): + """print usage message for the given command or all commands. + When errno > 0, sys,exit(errno) will interrupt the program. + """ + if subcommand and subcommand in cmd_map: + w_err(errno, _(u"Error: %s") % errmsg, + _(u"usage: ") + cmd_map[subcommand].usage) + + # TP: Please adjust translated words like the original text. + # (It's a table header.) Extract from usage text: + # usage: vmm subcommand arguments + # short long + # subcommand arguments + # + # da domainadd fqdn [transport] + # dd domaindelete fqdn [force] + u_head = _(u"""usage: %s subcommand arguments + short long + subcommand arguments\n""") % prog + order = cmd_map.keys() + order.sort() + w_err(0, u_head) + for key in order: + scmd = cmd_map[key] + w_err(0, ' %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args)) + w_err(errno, '', _(u"Error: %s") % errmsg) + + +def version(ctx): + """Write version and copyright information to stdout.""" + w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog, + # TP: The words 'from', 'version' and 'on' are used in + # the version information, e.g.: + # vmm, version 0.5.2 (from 09/09/09) + # Python 2.5.4 on FreeBSD + _(u'version'), __version__, _(u'from'), + strftime(locale.nl_langinfo(locale.D_FMT), + strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'), + os.sys.version.split()[0], _(u'on'), os.uname()[0], + __copyright__, prog, + _(u'is free software and comes with ABSOLUTELY NO WARRANTY.'))) + +cmd = Command +cmd_map = { # {{{ + # Account commands + 'getuser': cmd('getuser', 'gu', get_user, 'uid', + _(u'get the address of the user with the given UID')), + 'useradd': cmd('useradd', 'ua', user_add, 'address [password]', + _(u'create a new e-mail user with the given address')), + 'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]', + _(u'delete the specified user')), + 'userdisable': cmd('userdisable', 'u0', user_disable, + 'address [service ...]', + _(u'deactivate all/the given service(s) for a user')), + 'userenable': cmd('userenable', 'u1', user_enable, 'address [service ...]', + _(u'activate all or the given service(s) for a user')), + 'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]', + _(u'display information about the given address')), + 'username': cmd('username', 'un', user_name, 'address name', + _(u'set or update the real name for an address')), + 'userpassword': cmd('userpassword', 'up', user_password, + 'address [password]', + _(u'update the password for the given address')), + 'usertransport': cmd('usertransport', 'ut', user_transport, + 'address transport', + _(u'update the transport of the given address')), + # Alias commands + 'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...', + _(u'create a new alias e-mail address')), + 'aliasdelete': cmd('aliasdelete', 'ad', alias_delete, + 'address [destination]', + _(u'delete the specified alias e-mail address or one ' + u'of its destinations')), + 'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address', + _(u'show the destination(s) of the specified alias')), + # AliasDomain commands + 'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add, + 'fqdn destination', + _(u'create a new alias for an existing domain')), + 'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete, + 'fqdn', _(u'delete the specified alias domain')), + 'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn', + _(u'show the destination of the given alias domain')), + 'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch, + 'fqdn destination', + _(u'assign the given alias domain to an other domain')), + # Domain commands + 'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]', + _(u'create a new domain')), + 'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]', + _(u'delete the given domain and all its alias domains')), + 'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]', + _(u'display information about the given domain')), + 'domaintransport': cmd('domaintransport', 'dt', domain_transport, + 'fqdn transport [force]', + _(u'update the transport of the specified domain')), + 'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]', + _(u'list all domains / search domains by pattern')), + # Relocated commands + 'relocatedadd': cmd('relocatedadd', 'ra', relocated_add, + 'address newaddress', + _(u'create a new record for a relocated user')), + 'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete, + 'address', + _(u'delete the record of the relocated user')), + 'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address', + _(u'print information about a relocated user')), + # cli commands + 'configure': cmd('configure', 'cf', configure, '[section]', + _(u'start interactive configuration modus')), + 'help': cmd('help', 'h', help_, '[subcommand]', + _(u'show a help overview or help for the given subcommand')), + 'version': cmd('version', 'v', version, '', + _(u'show version and copyright information')), +} # }}} + + +def _get_order(ctx): + """returns a tuple with (key, 1||0) tuples. Used by functions, which + get a dict from the handler.""" + order = () + if ctx.scmd == 'domaininfo': + order = ((u'domainname', 0), (u'gid', 1), (u'transport', 0), + (u'domaindir', 0), (u'aliasdomains', 0), (u'accounts', 0), + (u'aliases', 0), (u'relocated', 0)) + elif ctx.scmd == 'userinfo': + dc12 = ctx.cget('misc.dovecot_version') >= 0x10200b02 + sieve = (u'managesieve', u'sieve')[dc12] + if ctx.argc == 4 and ctx.args[3] != u'aliases' or \ + ctx.cget('account.disk_usage'): + order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), + (u'home', 0), (u'mail_location', 0), (u'disk usage', 0), + (u'transport', 0), (u'smtp', 1), (u'pop3', 1), + (u'imap', 1), (sieve, 1)) + else: + order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), + (u'home', 0), (u'mail_location', 0), (u'transport', 0), + (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve, 1)) + elif ctx.scmd == 'getuser': + order = ((u'uid', 1), (u'gid', 1), (u'address', 0)) + return order + + +def _print_info(ctx, info, title): + """Print info dicts.""" + # TP: used in e.g. 'Domain information' or 'Account information' + msg = u'%s %s' % (title, _(u'information')) + w_std(msg, u'-' * len(msg)) + for key, upper in _get_order(ctx): + if upper: + w_std(u'\t%s: %s' % (key.upper().ljust(15, u'.'), info[key])) + else: + w_std(u'\t%s: %s' % (key.title().ljust(15, u'.'), info[key])) + print + + +def _print_list(alist, title): + """Print a list.""" + # TP: used in e.g. 'Available alias addresses' or 'Available accounts' + msg = u'%s %s' % (_(u'Available'), title) + w_std(msg, u'-' * len(msg)) + if alist: + if title != _(u'alias domains'): + w_std(*(u'\t%s' % item for item in alist)) + else: + for domain in alist: + if not domain.startswith('xn--'): + w_std(u'\t%s' % domain) + else: + w_std(u'\t%s (%s)' % (domain, domain.decode('idna'))) + print + else: + w_std(_(u'\tNone'), '') + + +def _print_aliase_info(alias, destinations): + """Print the alias address and all its destinations""" + title = _(u'Alias information') + w_std(title, u'-' * len(title)) + w_std(_(u'\tMail for %s will be redirected to:') % alias) + w_std(*(u'\t * %s' % dest for dest in destinations)) + print + + +def _print_relocated_info(**kwargs): + """Print the old and new addresses of a relocated user.""" + title = _(u'Relocated information') + w_std(title, u'-' * len(title)) + w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '') + + +def _format_domain(domain, main=True): + """format (prefix/convert) the domain name.""" + if domain.startswith('xn--'): + domain = u'%s (%s)' % (domain, domain.decode('idna')) + if main: + return u'\t[+] %s' % domain + return u'\t[-] %s' % domain + + +def _print_domain_list(dids, domains, matching): + """Print a list of (matching) domains/alias domains.""" + if matching: + title = _(u'Matching domains') + else: + title = _(u'Available domains') + w_std(title, '-' * len(title)) + if domains: + for did in dids: + if domains[did][0] is not None: + w_std(_format_domain(domains[did][0])) + if len(domains[did]) > 1: + w_std(*(_format_domain(a, False) for a in domains[did][1:])) + else: + w_std(_('\tNone')) + print + + +def _print_aliasdomain_info(info): + """Print alias domain information.""" + title = _(u'Alias domain information') + for key in ('alias', 'domain'): + if info[key].startswith('xn--'): + info[key] = u'%s (%s)' % (info[key], info[key].decode('idna')) + w_std(title, '-' * len(title), + _('\tThe alias domain %(alias)s belongs to:\n\t * %(domain)s') % + info, '') + +del _ diff -r abff2de9eed0 -r 4515afec62e5 vmm --- a/vmm Thu Aug 05 02:38:20 2010 +0000 +++ b/vmm Thu Aug 05 05:08:02 2010 +0000 @@ -5,527 +5,11 @@ """This is the vmm main script.""" -from time import strftime, strptime - -from VirtualMailManager import * -from VirtualMailManager.cli import w_std, w_err - - -# TODO: FIXME -from VirtualMailManager.VirtualMailManager import VirtualMailManager -import VirtualMailManager.Exceptions as VMME -import VirtualMailManager.constants.EXIT as EXIT - - -def usage(excode=0, errMsg=None): - # TP: Please adjust translated words like the original text. - # (It's a table header.) Extract from usage text: - # Usage: vmm SUBCOMMAND OBJECT ARGS* - # short long - # subcommand object args (* = optional) - # - # da domainadd domain.tld transport* - # di domaininfo domain.tld details* - u_head = _(u"""\ -Usage: %s SUBCOMMAND OBJECT ARGS* - short long - subcommand object args (* = optional)\n""")\ - % __prog__ - - u_body = u"""\ - da domainadd domain.tld transport* - di domaininfo domain.tld details* - dt domaintransport domain.tld transport force* - dd domaindelete domain.tld delalias*|deluser*|delall* - ada aliasdomainadd aliasdomain.tld domain.tld - adi aliasdomaininfo aliasdomain.tld - ads aliasdomainswitch aliasdomain.tld domain.tld - add aliasdomaindelete aliasdomain.tld - ua useradd user@domain.tld password* - ui userinfo user@domain.tld details* - un username user@domain.tld "user’s name" - up userpassword user@domain.tld password* - ut usertransport user@domain.tld transport - u0 userdisable user@domain.tld service* - u1 userenable user@domain.tld service* - ud userdelete user@domain.tld delalias* - aa aliasadd alias@domain.tld user@domain.tld - ai aliasinfo alias@domain.tld - ad aliasdelete alias@domain.tld user@domain.tld* - ra relocatedadd exaddr@domain.tld user@domain.tld - ri relocatedinfo exaddr@domain.tld - rf relocateddelete exaddr@domain.tld - gu getuser userid - ld listdomains pattern* - cf configure section* - h help - v version -""" - if excode > 0: - if errMsg is None: - w_err(excode, u_head, u_body) - else: - w_err(excode, u_head, u_body, _(u'Error: %s\n') % errMsg) - else: - w_std(u_head, u_body) - os.sys.exit(excode) - -def get_vmm(): - try: - vmm = VirtualMailManager() - return vmm - except (VMME.VMMException, VMME.VMMNotRootException, VMME.VMMPermException, - VMME.VMMConfigException), e: - w_err(e.code(), _(u'Error: %s\n') % e.msg()) - -def _getOrder(): - order = () - if vmm.cfgDget('misc.dovecot_version') > 11: - sieve_name = u'sieve' - else: - sieve_name = u'managesieve' - if argv[1] in (u'di', u'domaininfo'): - order = ((u'domainname', 0), (u'gid', 1), (u'transport', 0), - (u'domaindir', 0), (u'aliasdomains', 0), (u'accounts', 0), - (u'aliases', 0), (u'relocated', 0)) - elif argv[1] in (u'ui', u'userinfo'): - if argc == 4 and argv[3] != u'aliases'\ - or vmm.cfgDget('account.disk_usage'): - order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), - (u'transport', 0), (u'maildir', 0), (u'disk usage', 0), - (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve_name, 1)) - else: - order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1), - (u'transport', 0), (u'maildir', 0), (u'smtp', 1), - (u'pop3', 1), (u'imap', 1), (sieve_name, 1)) - elif argv[1] in (u'gu', u'getuser'): - order = ((u'uid', 1), (u'gid', 1), (u'address', 0)) - return order - -def _printInfo(info, title): - # TP: e.g. 'Domain information' or 'Account information' - msg = u'%s %s' % (title, _(u'information')) - w_std (u'%s\n%s' % (msg, u'-'*len(msg))) - for k,u in _getOrder(): - if u: - w_std(u'\t%s: %s' % (k.upper().ljust(15, u'.'), info[k])) - else: - w_std(u'\t%s: %s' % (k.title().ljust(15, u'.'), info[k])) - print - -def _printList(alist, title): - # TP: e.g. 'Available alias addresses' or 'Available accounts' - msg = u'%s %s' % (_(u'Available'), title) - w_std(u'%s\n%s' % (msg, u'-'*len(msg))) - if len(alist) > 0: - if title != _(u'alias domains'): - for val in alist: - w_std(u'\t%s' % val) - else: - for dom in alist: - if not dom.startswith('xn--'): - w_std(u'\t%s' % dom) - else: - w_std(u'\t%s (%s)' % (dom, ace2idna(dom))) - else: - w_std(_(u'\tNone')) - print - -def _printAliases(alias, targets): - msg = _(u'Alias information') - w_std(u'%s\n%s' % (msg, u'-'*len(msg))) - w_std(_(u'\tMail for %s will be redirected to:') % alias) - if len(targets) > 0: - for target in targets: - w_std(u'\t * %s' % target) - else: - w_std(_(u'\tNone')) - print - -def _printRelocated(addr_dest): - msg = _(u'Relocated information') - w_std(u'%s\n%s' % (msg, u'-'*len(msg))) - w_std(_(u'\tUser “%(addr)s” has moved to “%(dest)s”') % addr_dest) - print - -def _formatDom(domain, main=True): - if domain.startswith('xn--'): - domain = u'%s (%s)' % (domain, ace2idna(domain)) - if main: - return u'\t[+] %s' % domain - else: - return u'\t[-] %s' % domain - -def _printDomList(dids, domains): - if argc < 3: - msg = _('Available domains') - else: - msg = _('Matching domains') - w_std('%s\n%s' % (msg, '-'*len(msg))) - if not len(domains): - w_std(_('\tNone')) - else: - for id in dids: - if domains[id][0] is not None: - w_std(_formatDom(domains[id][0])) - if len(domains[id]) > 1: - for alias in domains[id][1:]: - w_std(_formatDom(alias, main=False)) - print - -def _printAliasDomInfo(info): - msg = _('Alias domain information') - for k in ['alias', 'domain']: - if info[k].startswith('xn--'): - info[k] = "%s (%s)" % (info[k], ace2idna(info[k])) - w_std('%s\n%s' % (msg, '-'*len(msg))) - w_std( - _('\tThe alias domain %(alias)s belongs to:\n\t * %(domain)s')%info) - print - -def configure(): - if argc < 3: - vmm.configure() - else: - vmm.configure(argv[2]) - -def domain_add(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing domain name.')) - elif argc < 4: - vmm.domainAdd(argv[2].lower()) - else: - vmm.domainAdd(argv[2].lower(), argv[3]) - -def domain_delete(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing domain name.')) - elif argc < 4: - vmm.domainDelete(argv[2].lower()) - else: - vmm.domainDelete(argv[2].lower(), argv[3]) - -def domain_info(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing domain name.')) - try: - if argc < 4: - _printInfo(vmm.domainInfo(argv[2].lower()), _(u'Domain')) - else: - details = argv[3].lower() - infos = vmm.domainInfo(argv[2].lower(), details) - _printInfo(infos[0], _(u'Domain')) - if details == u'accounts': - _printList(infos[1], _(u'accounts')) - elif details == u'aliasdomains': - _printList(infos[1], _(u'alias domains')) - elif details == u'aliases': - _printList(infos[1], _(u'aliases')) - elif details == u'relocated': - _printList(infos[1], _(u'relocated users')) - else: - _printList(infos[1], _(u'alias domains')) - _printList(infos[2], _(u'accounts')) - _printList(infos[3], _(u'aliases')) - _printList(infos[4], _(u'relocated users')) - except VMME.VMMDomainException, e: - if e.code() is ERR.DOMAIN_ALIAS_EXISTS: - w_std(plan_a_b % {'subcommand': u'aliasdomaininfo', - 'object': argv[2].lower()}) - alias_domain_info() - else: - raise e - -def domain_transport(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing domain name and new transport.')) - if argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing new transport.')) - elif argc < 5: - vmm.domainTransport(argv[2].lower(), argv[3]) - else: - vmm.domainTransport(argv[2].lower(), argv[3], argv[4]) - -def alias_domain_add(): - if argc < 3: - usage(EXIT.MISSING_ARGS, - _(u'Missing alias domain name and target domain name.')) - elif argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing target domain name.')) - else: - vmm.aliasDomainAdd(argv[2].lower(), argv[3].lower()) - -def alias_domain_info(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing alias domain name.')) - try: - _printAliasDomInfo(vmm.aliasDomainInfo(argv[2].lower())) - except VMME.VMMAliasDomainException, e: - if e.code() is ERR.ALIASDOMAIN_ISDOMAIN: - w_std(plan_a_b % {'subcommand': u'domaininfo', - 'object': argv[2].lower()}) - argv[1] = u'di' # necessary manipulation to get the order - domain_info() - else: - raise e - -def alias_domain_switch(): - if argc < 3: - usage(EXIT.MISSING_ARGS, - _(u'Missing alias domain name and target domain name.')) - elif argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing target domain name.')) - else: - vmm.aliasDomainSwitch(argv[2].lower(), argv[3].lower()) - -def alias_domain_delete(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing alias domain name.')) - else: - vmm.aliasDomainDelete(argv[2].lower()) - -def user_add(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - elif argc < 4: - password = None - else: - password = argv[3] - vmm.userAdd(argv[2].lower(), password) - -def user_delete(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - elif argc < 4: - vmm.userDelete(argv[2].lower()) - else: - vmm.userDelete(argv[2].lower(), argv[3].lower()) - -def user_info(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - try: - if argc < 4: - _printInfo(vmm.userInfo(argv[2].lower()), u'Account') - else: - arg3 = argv[3].lower() - info = vmm.userInfo(argv[2].lower(), arg3) - if arg3 in ['aliases', 'full']: - _printInfo(info[0], u'Account') - _printList(info[1], _(u'alias addresses')) - else: - _printInfo(info, u'Account') - except VMME.VMMAccountException, e: - if e.code() is ERR.ALIAS_EXISTS: - w_std(plan_a_b % {'subcommand': u'aliasinfo', - 'object': argv[2].lower()}) - alias_info() - elif e.code() is ERR.RELOCATED_EXISTS: - w_std(plan_a_b % {'subcommand': u'relocatedinfo', - 'object': argv[2].lower()}) - relocated_info() - else: - raise e - -def user_name(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address and user’s name.')) - if argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing user’s name.')) - else: - vmm.userName(argv[2].lower(), argv[3]) - -def user_transport(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address and transport.')) - if argc <4: - usage(EXIT.MISSING_ARGS, _(u'Missing transport.')) - else: - vmm.userTransport(argv[2].lower(), argv[3]) - -def user_enable(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - elif argc < 4: - vmm.userEnable(argv[2].lower()) - else: - vmm.userEnable(argv[2].lower(), argv[3].lower()) - -def user_disable(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - elif argc < 4: - vmm.userDisable(argv[2].lower()) - else: - vmm.userDisable(argv[2].lower(), argv[3].lower()) - -def user_password(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing e-mail address.')) - elif argc < 4: - password = None - else: - password = argv[3] - vmm.userPassword(argv[2].lower(), password) - -def alias_add(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing alias address and destination.')) - elif argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing destination address.')) - else: - vmm.aliasAdd(argv[2].lower(), argv[3]) - -def alias_info(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing alias address')) - try: - _printAliases(argv[2].lower(), vmm.aliasInfo(argv[2].lower())) - except VMME.VMMException, e: - if e.code() is ERR.ACCOUNT_EXISTS: - w_std(plan_a_b % {'subcommand': u'userinfo', - 'object': argv[2].lower()}) - argv[1] = u'ui' # necessary manipulation to get the order - user_info() - elif e.code() is ERR.RELOCATED_EXISTS: - w_std(plan_a_b % {'subcommand': u'relocatedinfo', - 'object': argv[2].lower()}) - relocated_info() - else: - raise - -def alias_delete(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing alias address')) - elif argc < 4: - vmm.aliasDelete(argv[2].lower()) - else: - vmm.aliasDelete(argv[2].lower(), argv[3].lower()) - -def relocated_add(): - if argc < 3: - usage(EXIT.MISSING_ARGS, - _(u'Missing relocated address and destination.')) - elif argc < 4: - usage(EXIT.MISSING_ARGS, _(u'Missing destination address.')) - else: - vmm.relocatedAdd(argv[2].lower(), argv[3]) - -def relocated_info(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing relocated address')) - relocated = argv[2].lower() - try: - _printRelocated({'addr': relocated, - 'dest': vmm.relocatedInfo(relocated)}) - except VMME.VMMRelocatedException, e: - if e.code() is ERR.ACCOUNT_EXISTS: - w_std(plan_a_b % {'subcommand': u'userinfo', 'object': relocated}) - argv[1] = u'ui' # necessary manipulation to get the order - user_info() - elif e.code() is ERR.ALIAS_EXISTS: - w_std(plan_a_b % {'subcommand': u'aliasinfo', 'object': relocated}) - alias_info() - else: - raise e - -def relocated_delete(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing relocated address')) - else: - vmm.relocatedDelete(argv[2].lower()) - -def user_byID(): - if argc < 3: - usage(EXIT.MISSING_ARGS, _(u'Missing userid')) - else: - _printInfo(vmm.userByID(argv[2]), u'Account') - -def domain_list(): - if argc < 3: - order, doms = vmm.domainList() - else: - order, doms = vmm.domainList(argv[2].lower()) - _printDomList(order, doms) - -def show_warnings(): - if vmm.hasWarnings(): - w_std(_(u'Warnings:')) - for warning in vmm.getWarnings(): - w_std( " * %s" % warning) - -def show_version(): - w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s %s' % (__prog__, - # TP: The words 'from', 'version' and 'on' are used in the version - # information: - # vmm, version 0.5.2 (from 09/09/09) - # Python 2.5.4 on FreeBSD - _(u'version'), __version__, _(u'from'), - strftime(locale.nl_langinfo(locale.D_FMT), - strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'), - os.sys.version.split()[0], _(u'on'), os.uname()[0], __prog__, - _(u'is free software and comes with ABSOLUTELY NO WARRANTY.'))) - -def main(): - subcommand = os.sys.argv[1] - known_subcommand = False - try: - for s, l, f in subcmd_func.__iter__(): - if subcommand in (s, l): - known_subcommand = True - f() - if not known_subcommand: - usage(EXIT.UNKNOWN_COMMAND, _(u'Unknown subcommand: “%s”')% argv[1]) - show_warnings() - except (EOFError, KeyboardInterrupt): - # TP: We have to cry, because root has killed/interrupted vmm - # with Ctrl+C or Ctrl+D. - w_err(EXIT.USER_INTERRUPT, _(u'\nOuch!\n')) - except (VMME.VMMConfigException, VMME.VMMException), e: - if e.code() != ERR.DATABASE_ERROR: - w_err(e.code(), _(u'Error: %s') % e.msg()) - else: - w_err(e.code(), unicode(e.msg(), ENCODING, 'replace')) +import sys if __name__ == '__main__': - __prog__ = os.path.basename(os.sys.argv[0]) - argv = [unicode(arg, ENCODING) for arg in os.sys.argv] - argc = len(os.sys.argv) - plan_a_b =_(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s') - - if argc < 2: - usage(EXIT.MISSING_ARGS) - - vmm = get_vmm() - - subcmd_func = ( - #short long function - ('da', 'domainadd', domain_add), - ('di', 'domaininfo', domain_info), - ('dt', 'domaintransport', domain_transport), - ('dd', 'domaindelete', domain_delete), - ('ada', 'aliasdomainadd', alias_domain_add), - ('adi', 'aliasdomaininfo', alias_domain_info), - ('ads', 'aliasdomainswitch', alias_domain_switch), - ('add', 'aliasdomaindelete', alias_domain_delete), - ('ua', 'useradd', user_add), - ('ui', 'userinfo', user_info), - ('un', 'username', user_name), - ('up', 'userpassword', user_password), - ('ut', 'usertransport', user_transport), - ('u0', 'userdisable', user_disable), - ('u1', 'userenable', user_enable), - ('ud', 'userdelete', user_delete), - ('aa', 'aliasadd', alias_add), - ('ai', 'aliasinfo', alias_info), - ('ad', 'aliasdelete', alias_delete), - ('ra', 'relocatedadd', relocated_add), - ('ri', 'relocatedinfo', relocated_info), - ('rd', 'relocateddelete', relocated_delete), - ('cf', 'configure', configure), - ('gu', 'getuser', user_byID), - ('ld', 'listdomains', domain_list), - ('h', 'help', usage), - ('v', 'version', show_version),) - - main() + # replace the script's cwd (/usr/local/sbin) with our module dir + # (the location of the VirtualMailManager directory) + sys.path[0] = '/usr/local/lib/vmm' + from VirtualMailManager.cli.main import run + sys.exit(run(sys.argv))