Include account note in getuser output
Modify the getuser output to include an account note, if one is present
# -*- coding: UTF-8 -*-# Copyright (c) 2007 - 2011, Pascal Volk# See COPYING for distribution information.""" VirtualMailManager.config ~~~~~~~~~~~~~~~~~~~~~~~~~ VMM's configuration module for simplified configuration access."""fromConfigParserimport \Error,MissingSectionHeaderError,NoOptionError,NoSectionError, \ParsingError,RawConfigParserfromcStringIOimportStringIOfromVirtualMailManager.commonimportVERSION_RE, \exec_ok,expand_path,get_unicode,lisdir,size_in_bytes,version_hexfromVirtualMailManager.constantsimportCONF_ERRORfromVirtualMailManager.errorsimportConfigError,VMMErrorfromVirtualMailManager.maillocationimportknown_formatfromVirtualMailManager.passwordimportverify_schemeas_verify_schemeDB_MODULES=('psycopg2','pypgsql')DB_SSL_MODES=('allow','disabled','prefer','require','verify-ca','verify-full')_=lambdamsg:msgclassBadOptionError(Error):"""Raised when a option isn't in the format 'section.option'."""passclassConfigValueError(Error):"""Raised when creating or validating of new values fails."""passclassNoDefaultError(Error):"""Raised when the requested option has no default value."""def__init__(self,section,option):Error.__init__(self,'Option %r in section %r has no default value'%(option,section))classLazyConfig(RawConfigParser):"""The **lazy** derivate of the `RawConfigParser`. There are two additional getters: `pget()` The polymorphic getter, which returns a option's value with the appropriate type. `dget()` Like `LazyConfig.pget()`, but returns the option's default, from `LazyConfig._cfg['sectionname']['optionname'].default`, if the option is not configured in a ini-like configuration file. `set()` differs from `RawConfigParser`'s `set()` method. `set()` takes the `section` and `option` arguments combined to a single string in the form "section.option". """def__init__(self):RawConfigParser.__init__(self)self._modified=False# sample _cfg dict. Create your own in your derived class.self._cfg={'sectionname':{'optionname':LazyConfigOption(int,1,self.getint),}}defbool_new(self,value):"""Converts the string `value` into a `bool` and returns it. | '1', 'on', 'yes' and 'true' will become `True` | '0', 'off', 'no' and 'false' will become `False` Throws a `ConfigValueError` for all other values, except bools. """ifisinstance(value,bool):returnvalueifvalue.lower()inself._boolean_states:returnself._boolean_states[value.lower()]else:raiseConfigValueError(_(u"Not a boolean: '%s'")%get_unicode(value))defgetboolean(self,section,option):"""Returns the boolean value of the option, in the given section. For a boolean True, the value must be set to '1', 'on', 'yes', 'true' or True. For a boolean False, the value must set to '0', 'off', 'no', 'false' or False. If the option has another value assigned this method will raise a ValueError. """# if the setting was modified it may be still a boolean value lets seetmp=self.get(section,option)ifisinstance(tmp,bool):returntmpifnottmp.lower()inself._boolean_states:raiseValueError('Not a boolean: %s'%tmp)returnself._boolean_states[tmp.lower()]def_get_section_option(self,section_option):"""splits ``section_option`` (section.option) in two parts and returns them as list ``[section, option]``, if: * it likes the format of ``section_option`` * the ``section`` is known * the ``option`` is known Else one of the following exceptions will be thrown: * `BadOptionError` * `NoSectionError` * `NoOptionError` """sect_opt=section_option.lower().split('.')# TODO: cache itiflen(sect_opt)!=2ornotsect_opt[0]ornotsect_opt[1]:raiseBadOptionError(_(u"Bad format: '%s' - expected: "u"section.option")%get_unicode(section_option))ifnotsect_opt[0]inself._cfg:raiseNoSectionError(sect_opt[0])ifnotsect_opt[1]inself._cfg[sect_opt[0]]:raiseNoOptionError(sect_opt[1],sect_opt[0])returnsect_optdefitems(self,section):"""returns an iterable that returns key, value ``tuples`` from the given ``section``. """ifsectioninself._sections:# check if the section was parsedsect=self._sections[section]elifnotsectioninself._cfg:raiseNoSectionError(section)else:return((k,self._cfg[section][k].default) \forkinself._cfg[section].iterkeys())# still here? Get defaults and merge defaults with configured settingdefaults=dict((k,self._cfg[section][k].default) \forkinself._cfg[section].iterkeys())defaults.update(sect)if'__name__'indefaults:deldefaults['__name__']returndefaults.iteritems()defdget(self,option):"""Returns the value of the `option`. If the option could not be found in the configuration file, the configured default value, from ``LazyConfig._cfg`` will be returned. Arguments: `option` : string the configuration option in the form "section.option" Throws a `NoDefaultError`, if no default value was passed to `LazyConfigOption.__init__()` for the `option`. """section,option=self._get_section_option(option)try:returnself._cfg[section][option].getter(section,option)except(NoSectionError,NoOptionError):ifnotself._cfg[section][option].defaultisNone:# may be Falsereturnself._cfg[section][option].defaultelse:raiseNoDefaultError(section,option)defpget(self,option):"""Returns the value of the `option`."""section,option=self._get_section_option(option)returnself._cfg[section][option].getter(section,option)defset(self,option,value):"""Set the `value` of the `option`. Throws a `ValueError` if `value` couldn't be converted using `LazyConfigOption.cls`. """# pylint: disable=W0221# @pylint: _L A Z Y_section,option=self._get_section_option(option)val=self._cfg[section][option].cls(value)ifself._cfg[section][option].validate:val=self._cfg[section][option].validate(val)ifnotRawConfigParser.has_section(self,section):self.add_section(section)RawConfigParser.set(self,section,option,val)self._modified=Truedefhas_section(self,section):"""Checks if `section` is a known configuration section."""returnsection.lower()inself._cfgdefhas_option(self,option):"""Checks if the option (section.option) is a known configuration option. """# pylint: disable=W0221# @pylint: _L A Z Y_try:self._get_section_option(option)returnTrueexcept(BadOptionError,NoSectionError,NoOptionError):returnFalsedefsections(self):"""Returns an iterator object for all configuration sections."""returnself._cfg.iterkeys()classLazyConfigOption(object):"""A simple container class for configuration settings. `LazyConfigOption` instances are required by `LazyConfig` instances, and instances of classes derived from `LazyConfig`, like the `Config` class. """__slots__=('__cls','__default','__getter','__validate')def__init__(self,cls,default,getter,validate=None):"""Creates a new `LazyConfigOption` instance. Arguments: `cls` : type The class/type of the option's value `default` Default value of the option. Use ``None`` if the option should not have a default value. `getter` : callable A method's name of `RawConfigParser` and derived classes, to get a option's value, e.g. `self.getint`. `validate` : NoneType or a callable None or any method, that takes one argument, in order to check the value, when `LazyConfig.set()` is called. """self.__cls=clsifnotdefaultisNone:# enforce the type of the default valueself.__default=self.__cls(default)else:self.__default=defaultifnotcallable(getter):raiseTypeError('getter has to be a callable, got a %r'%getter.__class__.__name__)self.__getter=getterifvalidateandnotcallable(validate):raiseTypeError('validate has to be callable or None, got a %r'%validate.__class__.__name__)self.__validate=validate@propertydefcls(self):"""The class of the option's value e.g. `str`, `unicode` or `bool`."""returnself.__cls@propertydefdefault(self):"""The option's default value, may be `None`"""returnself.__default@propertydefgetter(self):"""The getter method or function to get the option's value"""returnself.__getter@propertydefvalidate(self):"""A method or function to validate the value"""returnself.__validateclassConfig(LazyConfig):"""This class is for reading vmm's configuration file."""def__init__(self,filename):"""Creates a new Config instance Arguments: `filename` : str path to the configuration file """LazyConfig.__init__(self)self._cfg_filename=filenameself._cfg_file=Noneself._missing={}LCO=LazyConfigOptionbool_t=self.bool_newself._cfg={'account':{'delete_directory':LCO(bool_t,False,self.getboolean),'directory_mode':LCO(int,448,self.getint),'disk_usage':LCO(bool_t,False,self.getboolean),'password_length':LCO(int,8,self.getint),'random_password':LCO(bool_t,False,self.getboolean),},'bin':{'dovecotpw':LCO(str,'/usr/sbin/dovecotpw',self.get,exec_ok),'du':LCO(str,'/usr/bin/du',self.get,exec_ok),'postconf':LCO(str,'/usr/sbin/postconf',self.get,exec_ok),},'database':{'host':LCO(str,'localhost',self.get),'module':LCO(str,'psycopg2',self.get,check_db_module),'name':LCO(str,'mailsys',self.get),'pass':LCO(str,None,self.get),'port':LCO(int,5432,self.getint),'sslmode':LCO(str,'prefer',self.get,check_db_ssl_mode),'user':LCO(str,None,self.get),},'domain':{'auto_postmaster':LCO(bool_t,True,self.getboolean),'delete_directory':LCO(bool_t,False,self.getboolean),'directory_mode':LCO(int,504,self.getint),'force_deletion':LCO(bool_t,False,self.getboolean),'imap':LCO(bool_t,True,self.getboolean),'pop3':LCO(bool_t,True,self.getboolean),'sieve':LCO(bool_t,True,self.getboolean),'smtp':LCO(bool_t,True,self.getboolean),'quota_bytes':LCO(str,'0',self.get_in_bytes,check_size_value),'quota_messages':LCO(int,0,self.getint),'transport':LCO(str,'dovecot:',self.get),},'mailbox':{'folders':LCO(str,'Drafts:Sent:Templates:Trash',self.unicode),'format':LCO(str,'maildir',self.get,check_mailbox_format),'root':LCO(str,'Maildir',self.unicode),'subscribe':LCO(bool_t,True,self.getboolean),},'misc':{'base_directory':LCO(str,'/srv/mail',self.get,is_dir),'crypt_blowfish_rounds':LCO(int,5,self.getint),'crypt_sha256_rounds':LCO(int,5000,self.getint),'crypt_sha512_rounds':LCO(int,5000,self.getint),'dovecot_version':LCO(str,None,self.hexversion,check_version_format),'password_scheme':LCO(str,'CRAM-MD5',self.get,verify_scheme),},}defload(self):"""Loads the configuration, read only. Raises a ConfigError if the configuration syntax is invalid. """self._cfg_file=open(self._cfg_filename,'r')try:self.readfp(self._cfg_file)except(MissingSectionHeaderError,ParsingError),err:raiseConfigError(str(err),CONF_ERROR)self._cfg_file.close()defcheck(self):"""Performs a configuration check. Raises a ConfigError if settings w/o a default value are missed. Or some settings have a invalid value. """defiter_dict():forsection,optionsinself._missing.iteritems():errmsg.write(_(u'* Section: %s\n')%section)errmsg.writelines(u' %s\n'%optionforoptioninoptions)self._missing.clear()errmsg=Noneself._chk_non_default()miss_vers='misc'inself._missingand \'dovecot_version'inself._missing['misc']ifself._missing:errmsg=StringIO()errmsg.write(_(u'Check of configuration file %s failed.\n')%self._cfg_filename)errmsg.write(_(u'Missing options, which have no default value.\n'))iter_dict()self._chk_possible_values(miss_vers)ifself._missing:ifnoterrmsg:errmsg=StringIO()errmsg.write(_(u'Check of configuration file %s failed.\n')%self._cfg_filename)errmsg.write(_(u'Invalid configuration values.\n'))else:errmsg.write('\n'+_(u'Invalid configuration values.\n'))iter_dict()iferrmsg:raiseConfigError(errmsg.getvalue(),CONF_ERROR)defhexversion(self,section,option):"""Converts the version number (e.g.: 1.2.3) from the *option*'s value to an int."""returnversion_hex(self.get(section,option))defget_in_bytes(self,section,option):"""Converts the size value (e.g.: 1024k) from the *option*'s value to a long"""returnsize_in_bytes(self.get(section,option))defunicode(self,section,option):"""Returns the value of the `option` from `section`, converted to Unicode."""returnget_unicode(self.get(section,option))def_chk_non_default(self):"""Checks all section's options for settings w/o a default value. Missing items will be stored in _missing. """forsectioninself._cfg.iterkeys():missing=[]foroption,valueinself._cfg[section].iteritems():if(value.defaultisNoneandnotRawConfigParser.has_option(self,section,option)):missing.append(option)ifmissing:self._missing[section]=missingdef_chk_possible_values(self,miss_vers):"""Check settings for which the possible values are known."""ifnotmiss_vers:value=self.get('misc','dovecot_version')ifnotVERSION_RE.match(value):self._missing['misc']=['version: '+\_(u"Not a valid Dovecot version: '%s'")%value]# section databasedb_err=[]value=self.dget('database.module').lower()ifvaluenotinDB_MODULES:db_err.append('module: '+ \_(u"Unsupported database module: '%s'")%value)ifvalue=='psycopg2':value=self.dget('database.sslmode')ifvaluenotinDB_SSL_MODES:db_err.append('sslmode: '+ \_(u"Unknown pgsql SSL mode: '%s'")%value)ifdb_err:self._missing['database']=db_err# section mailboxvalue=self.dget('mailbox.format')ifnotknown_format(value):self._missing['mailbox']=['format: '+\_(u"Unsupported mailbox format: '%s'")%value]# section domaintry:value=self.dget('domain.quota_bytes')except(ValueError,TypeError),err:self._missing['domain']=[u'quota_bytes: '+str(err)]defis_dir(path):"""Check if the expanded path is a directory. When the expanded path is a directory the expanded path will be returned. Otherwise a ConfigValueError will be raised. """path=expand_path(path)iflisdir(path):returnpathraiseConfigValueError(_(u"No such directory: %s")%get_unicode(path))defcheck_db_module(module):"""Check if the *module* is a supported pgsql module."""ifmodule.lower()inDB_MODULES:returnmoduleraiseConfigValueError(_(u"Unsupported database module: '%s'")%get_unicode(module))defcheck_db_ssl_mode(ssl_mode):"""Check if the *ssl_mode* is one of the SSL modes, known by pgsql."""ifssl_modeinDB_SSL_MODES:returnssl_moderaiseConfigValueError(_(u"Unknown pgsql SSL mode: '%s'")%get_unicode(ssl_mode))defcheck_mailbox_format(format):""" Check if the mailbox format *format* is supported. When the *format* is supported it will be returned, otherwise a `ConfigValueError` will be raised. """format=format.lower()ifknown_format(format):returnformatraiseConfigValueError(_(u"Unsupported mailbox format: '%s'")%get_unicode(format))defcheck_size_value(value):"""Check if the size value *value* has the proper format, e.g.: 1024k. Returns the validated value string if it has the expected format. Otherwise a `ConfigValueError` will be raised."""try:tmp=size_in_bytes(value)except(TypeError,ValueError),err:raiseConfigValueError(_(u"Not a valid size value: '%s'")%get_unicode(value))returnvaluedefcheck_version_format(version_string):"""Check if the *version_string* has the proper format, e.g.: '1.2.3'. Returns the validated version string if it has the expected format. Otherwise a `ConfigValueError` will be raised. """ifnotVERSION_RE.match(version_string):raiseConfigValueError(_(u"Not a valid Dovecot version: '%s'")%get_unicode(version_string))returnversion_stringdefverify_scheme(scheme):"""Checks if the password scheme *scheme* can be accepted and returns the verified scheme. """try:scheme,encoding=_verify_scheme(scheme)exceptVMMError,err:# 'cast' itraiseConfigValueError(err.msg)ifnotencoding:returnschemereturn'%s.%s'%(scheme,encoding)del_