vmm
author Pascal Volk <neverseen@users.sourceforge.net>
Fri, 26 Feb 2010 02:35:25 +0000
branchv0.6.x
changeset 216 0c8c053b451c
parent 188 cf1b5f22dbd2
child 232 3c766114d0b9
permissions -rwxr-xr-x
Moved VirtualMailManager/Exceptions to VirtualMailManager/errors. Renamed VMM*Exception classes to *Error. No longer add the attribute 'message' to VMMError if it doesn't exist, like in Python 2.4. It has been deprecated as of Python 2.6. Also removed the methods code() and msg(), the values are now accessible via the attributes 'code' and 'msg'.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Copyright 2007 - 2010, Pascal Volk
# See COPYING for distribution information.

"""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.VMMAliasException, 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 e

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'))

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()