vmm
author Tobias Berling <t-obi@users.sourceforge.net>
Tue, 27 Apr 2010 22:49:46 +0000
branchv0.6.x
changeset 266 e14c345b44a1
parent 232 3c766114d0b9
child 340 4515afec62e5
permissions -rwxr-xr-x
VMM/{Account,common,Handler}: Improved version_hex(). - common: version_hex() now supports 'serials' > 16. Added version_str() as counterpart to version_hex(). - Account, Handler: updated hardcoded Dovecot versions.

#!/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.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'))

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