If an alias has multiple destinations, multiple records exist, due to
the nature of the database. address_list would then return the same
alias multiple times, which does not add any information, eats screen
space and is potentially confusing.
Therefore, we SELECT DISTINCTly from the alias table.
Signed-off-by: martin f. krafft <madduck@debian.org>
---
VirtualMailManager/common.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
# -*- coding: UTF-8 -*-# Copyright (c) 2007 - 2012, Pascal Volk# See COPYING for distribution information.""" VirtualMailManager.cli.subcommands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VirtualMailManager's cli subcommands."""importlocaleimportosfromtextwrapimportTextWrapperfromtimeimportstrftime,strptimefromVirtualMailManagerimportENCODINGfromVirtualMailManager.cliimportget_winsize,prog,w_err,w_stdfromVirtualMailManager.commonimporthuman_size,size_in_bytes, \version_str,format_domain_defaultfromVirtualMailManager.constantsimport__copyright__,__date__, \__version__,ACCOUNT_EXISTS,ALIAS_EXISTS,ALIASDOMAIN_ISDOMAIN, \DOMAIN_ALIAS_EXISTS,INVALID_ARGUMENT,EX_MISSING_ARGS, \RELOCATED_EXISTS,TYPE_ACCOUNT,TYPE_ALIAS,TYPE_RELOCATEDfromVirtualMailManager.errorsimportVMMErrorfromVirtualMailManager.passwordimportlist_schemesfromVirtualMailManager.servicesetimportSERVICES__all__=('Command','RunContext','cmd_map','usage','alias_add','alias_delete','alias_info','aliasdomain_add','aliasdomain_delete','aliasdomain_info','aliasdomain_switch','catchall_add','catchall_info','catchall_delete','config_get','config_set','configure','domain_add','domain_delete','domain_info','domain_quota','domain_services','domain_transport','domain_note','get_user','help_','list_domains','list_pwschemes','list_users','list_aliases','list_relocated','list_addresses','relocated_add','relocated_delete','relocated_info','user_add','user_delete','user_info','user_name','user_password','user_quota','user_services','user_transport','user_note','version',)_=lambdamsg:msgtxt_wrpr=TextWrapper(width=get_winsize()[1]-1)cmd_map={}classCommand(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=nameself.alias=aliasself.func=funcself.args=argsself.descr=descr@propertydefusage(self):"""the command's usage info."""returnu'%s%s%s'%(prog,self.name,self.args)classRunContext(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)forarginargv]self.cget=handler.cfg_dgetself.hdlr=handlerself.scmd=commanddefalias_add(ctx):"""create a new alias e-mail address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing alias address and destination.'),ctx.scmd)elifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing destination address.'),ctx.scmd)ctx.hdlr.alias_add(ctx.args[2].lower(),*ctx.args[3:])defalias_delete(ctx):"""delete the specified alias e-mail address or one of its destinations"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing alias address.'),ctx.scmd)elifctx.argc<4:ctx.hdlr.alias_delete(ctx.args[2].lower())else:ctx.hdlr.alias_delete(ctx.args[2].lower(),ctx.args[3])defalias_info(ctx):"""show the destination(s) of the specified alias"""ifctx.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))exceptVMMError,err:iferr.codeisACCOUNT_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'userinfo','object':address})ctx.scmd=ctx.args[1]='userinfo'user_info(ctx)eliferr.codeisRELOCATED_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'relocatedinfo','object':address})ctx.scmd=ctx.args[1]='relocatedinfo'relocated_info(ctx)else:raisedefaliasdomain_add(ctx):"""create a new alias for an existing domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing alias domain name and destination 'u'domain name.'),ctx.scmd)elifctx.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())defaliasdomain_delete(ctx):"""delete the specified alias domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing alias domain name.'),ctx.scmd)ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())defaliasdomain_info(ctx):"""show the destination of the given alias domain"""ifctx.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()))exceptVMMError,err:iferr.codeisALIASDOMAIN_ISDOMAIN:w_err(0,ctx.plan_a_b%{'subcommand':u'domaininfo','object':ctx.args[2].lower()})ctx.scmd=ctx.args[1]='domaininfo'domain_info(ctx)else:raisedefaliasdomain_switch(ctx):"""assign the given alias domain to an other domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing alias domain name and destination 'u'domain name.'),ctx.scmd)elifctx.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())defcatchall_add(ctx):"""create a new catchall alias e-mail address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain and destination.'),ctx.scmd)elifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing destination address.'),ctx.scmd)ctx.hdlr.catchall_add(ctx.args[2].lower(),*ctx.args[3:])defcatchall_delete(ctx):"""delete the specified destination or all of the catchall destination"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain.'),ctx.scmd)elifctx.argc<4:ctx.hdlr.catchall_delete(ctx.args[2].lower())else:ctx.hdlr.catchall_delete(ctx.args[2].lower(),ctx.args[3])defcatchall_info(ctx):"""show the catchall destination(s) of the specified domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain.'),ctx.scmd)address=ctx.args[2].lower()_print_catchall_info(address,ctx.hdlr.catchall_info(address))defconfig_get(ctx):"""show the actual value of the configuration option"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u"Missing option name."),ctx.scmd)noop=lambdaoption:optionopt_formater={'misc.dovecot_version':version_str,'domain.quota_bytes':human_size,}option=ctx.args[2].lower()w_std('%s = %s'%(option,opt_formater.get(option,noop)(ctx.cget(option))))defconfig_set(ctx):"""set a new value for the configuration option"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing option and new value.'),ctx.scmd)ifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing new configuration value.'),ctx.scmd)ctx.hdlr.cfg_set(ctx.args[2].lower(),ctx.args[3])defconfigure(ctx):"""start interactive configuration modus"""ifctx.argc<3:ctx.hdlr.configure()else:ctx.hdlr.configure(ctx.args[2].lower())defdomain_add(ctx):"""create a new domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name.'),ctx.scmd)elifctx.argc<4:ctx.hdlr.domain_add(ctx.args[2].lower())else:ctx.hdlr.domain_add(ctx.args[2].lower(),ctx.args[3])ifctx.cget('domain.auto_postmaster'):w_std(_(u'Creating account for postmaster@%s')%ctx.args[2].lower())ctx.scmd='useradd'ctx.args=[prog,ctx.scmd,u'postmaster@'+ctx.args[2].lower()]ctx.argc=3user_add(ctx)defdomain_delete(ctx):"""delete the given domain and all its alias domains"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name.'),ctx.scmd)elifctx.argc<4:ctx.hdlr.domain_delete(ctx.args[2].lower())elifctx.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)defdomain_info(ctx):"""display information about the given domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name.'),ctx.scmd)ifctx.argc<4:details=Noneelse:details=ctx.args[3].lower()ifdetailsnotin('accounts','aliasdomains','aliases','full','relocated','catchall'):usage(INVALID_ARGUMENT,_(u"Invalid argument: '%s'")%details,ctx.scmd)try:info=ctx.hdlr.domain_info(ctx.args[2].lower(),details)exceptVMMError,err:iferr.codeisDOMAIN_ALIAS_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'aliasdomaininfo','object':ctx.args[2].lower()})ctx.scmd=ctx.args[1]='aliasdomaininfo'aliasdomain_info(ctx)else:raiseelse:q_limit=u'Storage: %(bytes)s; Messages: %(messages)s'ifnotdetails:info['bytes']=human_size(info['bytes'])info['messages']=locale.format('%d',info['messages'],True)info['quota limit/user']=q_limit%info_print_info(ctx,info,_(u'Domain'))else:info[0]['bytes']=human_size(info[0]['bytes'])info[0]['messages']=locale.format('%d',info[0]['messages'],True)info[0]['quota limit/user']=q_limit%info[0]_print_info(ctx,info[0],_(u'Domain'))ifdetails==u'accounts':_print_list(info[1],_(u'accounts'))elifdetails==u'aliasdomains':_print_list(info[1],_(u'alias domains'))elifdetails==u'aliases':_print_list(info[1],_(u'aliases'))elifdetails==u'relocated':_print_list(info[1],_(u'relocated users'))elifdetails==u'catchall':_print_list(info[1],_(u'catch-all destinations'))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'))_print_list(info[5],_(u'catch-all destinations'))defdomain_quota(ctx):"""update the quota limit of the specified domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name and storage value.'),ctx.scmd)ifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing storage value.'),ctx.scmd)messages=0force=Nonetry:bytes_=size_in_bytes(ctx.args[3])except(ValueError,TypeError):usage(INVALID_ARGUMENT,_(u"Invalid storage value: '%s'")%ctx.args[3],ctx.scmd)ifctx.argc<5:passelifctx.argc<6:try:messages=int(ctx.args[4])exceptValueError:ifctx.args[4].lower()!='force':usage(INVALID_ARGUMENT,_(u"Neither a valid number of messages nor the keyword "u"'force': '%s'")%ctx.args[4],ctx.scmd)force='force'else:try:messages=int(ctx.args[4])exceptValueError:usage(INVALID_ARGUMENT,_(u"Not a valid number of messages: '%s'")%ctx.args[4],ctx.scmd)ifctx.args[5].lower()!='force':usage(INVALID_ARGUMENT,_(u"Invalid argument: '%s'")%ctx.args[5],ctx.scmd)force='force'ctx.hdlr.domain_quotalimit(ctx.args[2].lower(),bytes_,messages,force)defdomain_services(ctx):"""allow all named service and block the uncredited."""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name.'),ctx.scmd)services=[]force=Falseifctx.argcis3:passelifctx.argcis4:arg=ctx.args[3].lower()ifarginSERVICES:services.append(arg)elifarg=='force':force=Trueelse:usage(INVALID_ARGUMENT,_(u"Invalid argument: '%s'")%arg,ctx.scmd)else:services.extend([service.lower()forserviceinctx.args[3:-1]])arg=ctx.args[-1].lower()ifarg=='force':force=Trueelse:services.append(arg)unknown=[serviceforserviceinservicesifservicenotinSERVICES]ifunknown:usage(INVALID_ARGUMENT,_(u'Invalid service arguments: %s')%' '.join(unknown),ctx.scmd)ctx.hdlr.domain_services(ctx.args[2].lower(),(None,'force')[force],*services)defdomain_transport(ctx):"""update the transport of the specified domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name and new transport.'),ctx.scmd)ifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing new transport.'),ctx.scmd)ifctx.argc<5:ctx.hdlr.domain_transport(ctx.args[2].lower(),ctx.args[3])else:force=ctx.args[4].lower()ifforce!='force':usage(INVALID_ARGUMENT,_(u"Invalid argument: '%s'")%force,ctx.scmd)ctx.hdlr.domain_transport(ctx.args[2].lower(),ctx.args[3],force)defdomain_note(ctx):"""update the note of the given domain"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing domain name.'),ctx.scmd)elifctx.argc<4:note=Noneelse:note=' '.join(ctx.args[3:])ctx.hdlr.domain_note(ctx.args[2].lower(),note)defget_user(ctx):"""get the address of the user with the given UID"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing UID.'),ctx.scmd)_print_info(ctx,ctx.hdlr.user_by_uid(ctx.args[2]),_(u'Account'))defhelp_(ctx):"""print help messages."""ifctx.argc>2:hlptpc=ctx.args[2].lower()ifhlptpcincmd_map:topic=hlptpcelse:forscmdincmd_map.itervalues():ifscmd.alias==hlptpc:topic=scmd.namebreakelse:usage(INVALID_ARGUMENT,_(u"Unknown help topic: '%s'")%ctx.args[2],ctx.scmd)# FIXMEw_err(1,"'help %s' not yet implemented."%topic,'see also: vmm(1)')old_ii=txt_wrpr.initial_indentold_si=txt_wrpr.subsequent_indenttxt_wrpr.initial_indent=' '# len(max(_overview.iterkeys(), key=len)) #Py25txt_wrpr.subsequent_indent=20*' 'order=cmd_map.keys()order.sort()w_std(_(u'List of available subcommands:')+'\n')forkeyinorder:w_std('\n'.join(txt_wrpr.wrap('%-18s%s'%(key,cmd_map[key].descr))))txt_wrpr.initial_indent=old_iitxt_wrpr.subsequent_indent=old_sitxt_wrpr.initial_indent=''deflist_domains(ctx):"""list all domains / search domains by pattern"""matching=ctx.argc>2ifmatching:gids,domains=ctx.hdlr.domain_list(ctx.args[2].lower())else:gids,domains=ctx.hdlr.domain_list()_print_domain_list(gids,domains,matching)deflist_pwschemes(ctx_unused):"""Prints all usable password schemes and password encoding suffixes."""# TODO: Remove trailing colons from keys.# For now it is to late, the translators has stared their workkeys=(_(u'Usable password schemes:'),_(u'Usable encoding suffixes:'))old_ii,old_si=txt_wrpr.initial_indent,txt_wrpr.subsequent_indenttxt_wrpr.initial_indent=txt_wrpr.subsequent_indent='\t'txt_wrpr.width=txt_wrpr.width-8forkey,valueinzip(keys,list_schemes()):ifkey.endswith(':'):# who knows … (see TODO above)#key = key.rpartition(':')[0]key=key[:-1]# This one is for Py24w_std(key,len(key)*'-')w_std('\n'.join(txt_wrpr.wrap(' '.join(value))),'')txt_wrpr.initial_indent,txt_wrpr.subsequent_indent=old_ii,old_sitxt_wrpr.width=txt_wrpr.width+8deflist_addresses(ctx,limit=None):"""List all addresses / search addresses by pattern. The output can be limited with TYPE_ACCOUNT, TYPE_ALIAS and TYPE_RELOCATED, which can be bitwise ORed as a combination. Not specifying a limit is the same as combining all three."""iflimitisNone:limit=TYPE_ACCOUNT|TYPE_ALIAS|TYPE_RELOCATEDmatching=ctx.argc>2ifmatching:gids,addresses=ctx.hdlr.address_list(limit,ctx.args[2].lower())else:gids,addresses=ctx.hdlr.address_list(limit)_print_address_list(limit,gids,addresses,matching)deflist_users(ctx):"""list all user accounts / search user accounts by pattern"""returnlist_addresses(ctx,TYPE_ACCOUNT)deflist_aliases(ctx):"""list all aliases / search aliases by pattern"""returnlist_addresses(ctx,TYPE_ALIAS)deflist_relocated(ctx):"""list all relocated records / search relocated records by pattern"""returnlist_addresses(ctx,TYPE_RELOCATED)defrelocated_add(ctx):"""create a new record for a relocated user"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing relocated address and destination.'),ctx.scmd)elifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing destination address.'),ctx.scmd)ctx.hdlr.relocated_add(ctx.args[2].lower(),ctx.args[3])defrelocated_delete(ctx):"""delete the record of the relocated user"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing relocated address.'),ctx.scmd)ctx.hdlr.relocated_delete(ctx.args[2].lower())defrelocated_info(ctx):"""print information about a relocated user"""ifctx.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))exceptVMMError,err:iferr.codeisACCOUNT_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'userinfo','object':relocated})ctx.scmd=ctx.args[1]='userinfoi'user_info(ctx)eliferr.codeisALIAS_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'aliasinfo','object':relocated})ctx.scmd=ctx.args[1]='aliasinfo'alias_info(ctx)else:raisedefuser_add(ctx):"""create a new e-mail user with the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)elifctx.argc<4:password=Noneelse:password=ctx.args[3]gen_pass=ctx.hdlr.user_add(ctx.args[2].lower(),password)ifctx.argc<4andgen_pass:w_std(_(u"Generated password: %s")%gen_pass)defuser_delete(ctx):"""delete the specified user"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)elifctx.argc<4:ctx.hdlr.user_delete(ctx.args[2].lower())elifctx.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)defuser_info(ctx):"""display information about the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)ifctx.argc<4:details=Noneelse:details=ctx.args[3].lower()ifdetailsnotin('aliases','du','full'):usage(INVALID_ARGUMENT,_(u"Invalid argument: '%s'")%details,ctx.scmd)try:info=ctx.hdlr.user_info(ctx.args[2].lower(),details)exceptVMMError,err:iferr.codeisALIAS_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'aliasinfo','object':ctx.args[2].lower()})ctx.scmd=ctx.args[1]='aliasinfo'alias_info(ctx)eliferr.codeisRELOCATED_EXISTS:w_err(0,ctx.plan_a_b%{'subcommand':u'relocatedinfo','object':ctx.args[2].lower()})ctx.scmd=ctx.args[1]='relocatedinfo'relocated_info(ctx)else:raiseelse:ifdetailsin(None,'du'):info['quota storage']=_format_quota_usage(info['ql_bytes'],info['uq_bytes'],True,info['ql_domaindefault'])info['quota messages']=_format_quota_usage(info['ql_messages'],info['uq_messages'],domaindefault=info['ql_domaindefault'])_print_info(ctx,info,_(u'Account'))else:info[0]['quota storage']=_format_quota_usage(info[0]['ql_bytes'],info[0]['uq_bytes'],True,info[0]['ql_domaindefault'])info[0]['quota messages']= \_format_quota_usage(info[0]['ql_messages'],info[0]['uq_messages'],domaindefault=info[0]['ql_domaindefault'])_print_info(ctx,info[0],_(u'Account'))_print_list(info[1],_(u'alias addresses'))defuser_name(ctx):"""set or update the real name for an address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u"Missing e-mail address and user's name."),ctx.scmd)elifctx.argc<4:name=Noneelse:name=ctx.args[3]ctx.hdlr.user_name(ctx.args[2].lower(),name)defuser_password(ctx):"""update the password for the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)elifctx.argc<4:password=Noneelse:password=ctx.args[3]ctx.hdlr.user_password(ctx.args[2].lower(),password)defuser_note(ctx):"""update the note of the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)elifctx.argc<4:note=Noneelse:note=' '.join(ctx.args[3:])ctx.hdlr.user_note(ctx.args[2].lower(),note)defuser_quota(ctx):"""update the quota limit for the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address and storage value.'),ctx.scmd)elifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing storage value.'),ctx.scmd)ifctx.args[3]!='domain':try:bytes_=size_in_bytes(ctx.args[3])except(ValueError,TypeError):usage(INVALID_ARGUMENT,_(u"Invalid storage value: '%s'")%ctx.args[3],ctx.scmd)else:bytes_=ctx.args[3]ifctx.argc<5:messages=0else:try:messages=int(ctx.args[4])exceptValueError:usage(INVALID_ARGUMENT,_(u"Not a valid number of messages: '%s'")%ctx.args[4],ctx.scmd)ctx.hdlr.user_quotalimit(ctx.args[2].lower(),bytes_,messages)defuser_services(ctx):"""allow all named service and block the uncredited."""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address.'),ctx.scmd)services=[]ifctx.argc>=4:services.extend([service.lower()forserviceinctx.args[3:]])unknown=[serviceforserviceinservicesifservicenotinSERVICES]ifunknownandctx.args[3]!='domain':usage(INVALID_ARGUMENT,_(u'Invalid service arguments: %s')%' '.join(unknown),ctx.scmd)ctx.hdlr.user_services(ctx.args[2].lower(),*services)defuser_transport(ctx):"""update the transport of the given address"""ifctx.argc<3:usage(EX_MISSING_ARGS,_(u'Missing e-mail address and transport.'),ctx.scmd)ifctx.argc<4:usage(EX_MISSING_ARGS,_(u'Missing transport.'),ctx.scmd)ctx.hdlr.user_transport(ctx.args[2].lower(),ctx.args[3])defusage(errno,errmsg,subcommand=None):"""print usage message for the given command or all commands. When errno > 0, sys,exit(errno) will interrupt the program. """ifsubcommandandsubcommandincmd_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""")%progorder=cmd_map.keys()order.sort()w_err(0,u_head)forkeyinorder:scmd=cmd_map[key]w_err(0,' %-5s%-19s%s'%(scmd.alias,scmd.name,scmd.args))w_err(errno,'',_(u"Error: %s")%errmsg)defversion(ctx_unused):"""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.')))defupdate_cmd_map():"""Update the cmd_map, after gettext's _ was installed."""cmd=Commandcmd_map.update({# 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')),'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')),'userquota':cmd('userquota','uq',user_quota,'address storage [messages] | address domain',_(u'update the quota limit for the given address')),'userservices':cmd('userservices','us',user_services,'address [service ...] | address domain',_(u'enables the specified services and disables all 'u'not specified services')),'usertransport':cmd('usertransport','ut',user_transport,'address transport | address domain',_(u'update the transport of the given address')),'usernote':cmd('usernote','uo',user_note,'address note',_(u'update the note of the given address')),# Alias commands'aliasadd':cmd('aliasadd','aa',alias_add,'address destination ...',_(u'create a new alias e-mail address with one or more 'u'destinations')),'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')),# CatchallAlias commands'catchalladd':cmd('catchalladd','caa',catchall_add,'fqdn destination ...',_(u'add one or more catch-all destinations for a 'u'domain')),'catchalldelete':cmd('catchalldelete','cad',catchall_delete,'fqdn [destination]',_(u'delete the specified catch-all destination or all 'u'of a domain\'s destinations')),'catchallinfo':cmd('catchallinfo','cai',catchall_info,'fqdn',_(u'show the catch-all destination(s) of the specified 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')),'domainquota':cmd('domainquota','dq',domain_quota,'fqdn storage [messages] [force]',_(u'update the quota limit of the specified domain')),'domainservices':cmd('domainservices','ds',domain_services,'fqdn [service ...] [force]',_(u'enables the specified services and disables all 'u'not specified services of the given domain')),'domaintransport':cmd('domaintransport','dt',domain_transport,'fqdn transport [force]',_(u'update the transport of the specified domain')),'domainnote':cmd('domainnote','do',domain_note,'fqdn note',_(u'update the note of the given domain')),# List commands'listdomains':cmd('listdomains','ld',list_domains,'[pattern]',_(u'list all domains or search for domains by pattern')),'listaddresses':cmd('listaddresses','ll',list_addresses,'[pattern]',_(u'list all addresses or search for addresses by pattern')),'listusers':cmd('listusers','lu',list_users,'[pattern]',_(u'list all user accounts or search for accounts by pattern')),'listaliases':cmd('listaliases','la',list_aliases,'[pattern]',_(u'list all aliases or search for aliases by pattern')),'listrelocated':cmd('listrelocated','lr',list_relocated,'[pattern]',_(u'list all relocated entries or search for entries 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'configget':cmd('configget','cg',config_get,'option',_('show the actual value of the configuration option')),'configset':cmd('configset','cs',config_set,'option value',_('set a new value for the configuration option')),'configure':cmd('configure','cf',configure,'[section]',_(u'start interactive configuration modus')),'listpwschemes':cmd('listpwschemes','lp',list_pwschemes,'',_(u'lists all usable password schemes and password 'u'encoding suffixes')),'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=()ifctx.scmd=='domaininfo':order=((u'domain name',0),(u'gid',1),(u'domain directory',0),(u'quota limit/user',0),(u'active services',0),(u'transport',0),(u'alias domains',0),(u'accounts',0),(u'aliases',0),(u'relocated',0),(u'catch-all dests',0))elifctx.scmd=='userinfo':ifctx.argc==4andctx.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'quota storage',0),(u'quota messages',0),(u'disk usage',0),(u'transport',0),(u'smtp',1),(u'pop3',1),(u'imap',1),(u'sieve',1))else:order=((u'address',0),(u'name',0),(u'uid',1),(u'gid',1),(u'home',0),(u'mail_location',0),(u'quota storage',0),(u'quota messages',0),(u'transport',0),(u'smtp',1),(u'pop3',1),(u'imap',1),(u'sieve',1))elifctx.scmd=='getuser':order=((u'uid',1),(u'gid',1),(u'address',0))returnorderdef_format_quota_usage(limit,used,human=False,domaindefault=False):"""Put quota's limit / usage / percentage in a formatted string."""ifhuman:q_usage={'used':human_size(used),'limit':human_size(limit),}else:q_usage={'used':locale.format('%d',used,True),'limit':locale.format('%d',limit,True),}iflimit:q_usage['percent']=locale.format('%6.2f',100./limit*used,True)else:q_usage['percent']=locale.format('%6.2f',0,True)# Py25: fmt = format_domain_default if domaindefault else lambda s: sifdomaindefault:fmt=format_domain_defaultelse:fmt=lambdas:sreturnfmt(_(u'[%(percent)s%%] %(used)s/%(limit)s')%q_usage)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))forkey,upperin_get_order(ctx):ifupper:w_std(u'\t%s: %s'%(key.upper().ljust(17,u'.'),info[key]))else:w_std(u'\t%s: %s'%(key.title().ljust(17,u'.'),info[key]))printnote=info.get('note',None)ifnoteisnotNone:_print_note(note)def_print_note(note):msg=_(u'Note')w_std(msg,u'-'*len(msg))old_ii=txt_wrpr.initial_indentold_si=txt_wrpr.subsequent_indenttxt_wrpr.initial_indent=txt_wrpr.subsequent_indent='\t'txt_wrpr.width-=8forparainnote.split('\n'):w_std(txt_wrpr.fill(para))txt_wrpr.width+=8txt_wrpr.subsequent_indent=old_sitxt_wrpr.initial_indent=old_iidef_print_list(alist,title):"""Print a list."""# TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'msg=u'%s%s'%(_(u'Existing'),title)w_std(msg,u'-'*len(msg))ifalist:iftitle!=_(u'alias domains'):w_std(*(u'\t%s'%itemforiteminalist))else:fordomaininalist:ifnotdomain.startswith('xn--'):w_std(u'\t%s'%domain)else:w_std(u'\t%s (%s)'%(domain,domain.decode('idna')))printelse: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'%destfordestindestinations))printdef_print_catchall_info(domain,destinations):"""Print the catchall destinations of a domain"""title=_(u'Catch-all information')w_std(title,u'-'*len(title))w_std(_(u'\tMail to unknown localparts in domain %s will be sent to:')%domain)w_std(*(u'\t * %s'%destfordestindestinations))printdef_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."""ifdomain.startswith('xn--'):domain=u'%s (%s)'%(domain,domain.decode('idna'))ifmain:returnu'\t[+] %s'%domainreturnu'\t[-] %s'%domaindef_print_domain_list(dids,domains,matching):"""Print a list of (matching) domains/alias domains."""ifmatching:title=_(u'Matching domains')else:title=_(u'Existing domains')w_std(title,'-'*len(title))ifdomains:fordidindids:ifdomains[did][0]isnotNone:w_std(_format_domain(domains[did][0]))iflen(domains[did])>1:w_std(*(_format_domain(a,False)foraindomains[did][1:]))else:w_std(_('\tNone'))printdef_print_address_list(which,dids,addresses,matching):"""Print a list of (matching) addresses."""_trans={TYPE_ACCOUNT:_('user accounts'),TYPE_ALIAS:_('aliases'),TYPE_RELOCATED:_('relocated entries'),TYPE_ACCOUNT|TYPE_ALIAS:_('user accounts and aliases'),TYPE_ACCOUNT|TYPE_RELOCATED:_('user accounts and relocated entries'),TYPE_ALIAS|TYPE_RELOCATED:_('aliases and relocated entries'),TYPE_ACCOUNT|TYPE_ALIAS|TYPE_RELOCATED:_('addresses')}try:ifmatching:title=_(u'Matching %s')%_trans[which]else:title=_(u'Existing %s')%_trans[which]w_std(title,'-'*len(title))exceptKeyError:raiseVMMError(_("Invalid address type for list: '%s'")%which,INVALID_ARGUMENT)ifaddresses:ifwhich&(which-1)==0:# only one type is requested, so no type indicator_trans={TYPE_ACCOUNT:_(''),TYPE_ALIAS:_(''),TYPE_RELOCATED:_('')}else:_trans={TYPE_ACCOUNT:_('u'),TYPE_ALIAS:_('a'),TYPE_RELOCATED:_('r')}fordidindids:foraddr,atype,aliasdomaininaddresses[did]:ifaliasdomain:leader='[%s-]'%_trans[atype]else:leader='[%s+]'%_trans[atype]w_std('\t%s%s'%(leader,addr))else:w_std(_('\tNone'))printdef_print_aliasdomain_info(info):"""Print alias domain information."""title=_(u'Alias domain information')forkeyin('alias','domain'):ifinfo[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_