VMM/account: Check the account's transport only when we have a transport.
# -*- coding: UTF-8 -*-# Copyright 2007 - 2011, 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]!='default':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]!='default':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 default',_(u'update the quota limit for the given address')),'userservices':cmd('userservices','us',user_services,'address [service ...] | address default',_(u'enables the specified services and disables all 'u'not specified services')),'usertransport':cmd('usertransport','ut',user_transport,'address transport | address default',_(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)fmt=format_domain_defaultifdomaindefaultelselambdas: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_