# -*- coding: UTF-8 -*-# Copyright (c) 2007 - 2010, Pascal Volk# See COPYING for distribution information.""" VirtualMailManager.Config VMM's configuration module for simplified configuration access."""fromConfigParserimport \Error,MissingSectionHeaderError,NoOptionError,NoSectionError, \ParsingError,RawConfigParserfromcStringIOimportStringIO# TODO: move interactive stff to clifromVirtualMailManagerimportexec_ok,get_unicode,is_dirfromVirtualMailManager.constants.ERRORimportCONF_ERRORfromVirtualMailManager.errorsimportConfigError_=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)!=2:# do we need a regexp to check the format?raiseBadOptionError(_(u"Bad format: '%s' - expected: 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-msg=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-msg=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),'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),},'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),'name':LCO(str,'mailsys',self.get),'pass':LCO(str,None,self.get),'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),},'maildir':{'folders':LCO(str,'Drafts:Sent:Templates:Trash',self.get),'name':LCO(str,'Maildir',self.get),},'misc':{'base_directory':LCO(str,'/srv/mail',self.get,is_dir),'dovecot_version':LCO(int,12,self.getint),'gid_mail':LCO(int,8,self.getint),'password_scheme':LCO(str,'CRAM-MD5',self.get,self.known_scheme),'transport':LCO(str,'dovecot:',self.get),},}defload(self):"""Loads the configuration, read only. Raises a ConfigError if the configuration syntax is invalid. """try:self._cfg_file=open(self._cfg_filename,'r')self.readfp(self._cfg_file)except(MissingSectionHeaderError,ParsingError),err:raiseConfigError(str(err),CONF_ERROR)finally:ifself._cfg_fileandnotself._cfg_file.closed:self._cfg_file.close()defcheck(self):"""Performs a configuration check. Raises a ConfigError if the check fails. """# TODO: There are only two settings w/o defaults.# So there is no need for cStringIOifnotself.__chk_cfg():errmsg=StringIO()errmsg.write(_(u'Missing options, which have no default value.\n'))errmsg.write(_(u'Using configuration file: %s\n')%self._cfg_filename)forsection,optionsinself.__missing.iteritems():errmsg.write(_(u'* Section: %s\n')%section)foroptioninoptions:errmsg.write((u' %s\n')%option)raiseConfigError(errmsg.getvalue(),CONF_ERROR)defknown_scheme(self,scheme):"""Converts `scheme` to upper case and checks if is known by Dovecot (listed in VirtualMailManager.SCHEMES). Throws a `ConfigValueError` if the scheme is not listed in VirtualMailManager.SCHEMES. """scheme=scheme.upper()# TODO: VMM.SCHEMESdefunicode(self,section,option):"""Returns the value of the `option` from `section`, converted to Unicode. """returnget_unicode(self.get(section,option))def__chk_cfg(self):"""Checks all section's options for settings w/o a default value. Returns `True` if everything is fine, else `False`. """errors=Falseforsectioninself._cfg.iterkeys():missing=[]foroption,valueinself._cfg[section].iteritems():if(value.defaultisNoneandnotRawConfigParser.has_option(self,section,option)):missing.append(option)errors=Trueifmissing:self.__missing[section]=missingreturnnoterrorsdel_