Fix transport_maps function for non-existent domains
The postfix_transport_maps function had a bug causing
2012-04-15 17:40:22 CEST LOG: statement: SELECT transport FROM postfix_transport_map('logcheck', 'domine.madduck.net');
2012-04-15 17:40:22 CEST ERROR: query returned no rows
when the domain was not in the database. This would make did be NULL and make
the query fail.
This patch moves the tid query until after a check for did. If the latter is
NULL, the function RETURNs (rather than fails).
# -*- 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_