VMM/Config: reworked configuration handling. v0.6.x
authorPascal Volk <neverseen@users.sourceforge.net>
Sun, 24 Jan 2010 06:40:38 +0000
branchv0.6.x
changeset 174 974bafa59330
parent 173 c0e2c7687dd3
child 175 b241272eb1bd
VMM/Config: reworked configuration handling. Implemented LazyConfig(RawConfigParser) and LazyConfigOption(object) Rewrote Config class: * use default values and added some validation stuff * removed attributes: __VMMsections and __changes * replaced methods __chkSections() and __chkOptions() with __chkCfg VMM/VMM: Adjusted to reworked Config class. * removed attribute __cfgSections * removed methods: cfgGetBoolean(), cfgGetInt(), cfgGetString() * added methods: cfgDget(), cfgPget(), cfgSet() VMM/__init__: added function get_unicode() vmm: Adjusted to replaced methods in VMM/VMM.
VirtualMailManager/Config.py
VirtualMailManager/VirtualMailManager.py
VirtualMailManager/__init__.py
vmm
--- a/VirtualMailManager/Config.py	Fri Jan 22 04:31:38 2010 +0000
+++ b/VirtualMailManager/Config.py	Sun Jan 24 06:40:38 2010 +0000
@@ -2,73 +2,326 @@
 # Copyright (c) 2007 - 2010, Pascal Volk
 # See COPYING for distribution information.
 
-"""Configuration class for read, modify and write the
-configuration from Virtual Mail Manager.
+"""vmm's configuration module for simplified configuration access.
+
+This module defines a few classes:
+
+``LazyConfig``
+    This class provides the following additonal methods
 
+    * `LazyConfig.pget()`
+        polymorphic getter which returns the value with the appropriate
+        type.
+    * `LazyConfig.dget()`
+        like *pget()*, but checks additonal for default values in
+        `LazyConfig._cfg`.
+    * `LazyConfig.set()`
+        like `RawConfigParser.set()`, but converts the new value to the
+        appropriate type/class and optional validates the new value.
+    * `LazyConfig.bool_new()`
+        converts data from raw_input into boolean values.
+    * `LazyConfig.get_boolean()`
+        like `RawConfigParser.getboolean()`, but doesn't fail on real
+        `bool` values.
+
+``Config``
+    The Config class used by vmm.
+
+``LazyConfigOption``
+    The class for the configuration objects in the ``Config`` class'
+    ``_cfg`` dictionary.
 """
 
+
 from shutil import copy2
-from ConfigParser import ConfigParser, MissingSectionHeaderError, ParsingError
+from ConfigParser import (Error, MissingSectionHeaderError, NoOptionError,
+                          NoSectionError, ParsingError, RawConfigParser)
 from cStringIO import StringIO
 
-from __main__ import ENCODING, ERR, w_std
+from __main__ import os, ENCODING, ERR, get_unicode, w_std
 from Exceptions import VMMConfigException
 
-class Config(ConfigParser):
+
+class BadOptionError(Error):
+    """Raised when a option isn't in the format 'section.option'."""
+    pass
+
+
+class ConfigValueError(Error):
+    """Raised when creating or validating of new values fails."""
+    pass
+
+
+class NoDefaultError(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))
+
+
+class LazyConfig(RawConfigParser):
+    """The **lazy** derivate of the `RawConfigParser`.
+    
+    There are two additional getters:
+    
+    `LazyConfig.pget()`
+        The polymorphic getter, which returns a option's value with the
+        appropriate type.
+    `LazyConfig.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.
+
+
+    `LazyConfig.set()` differs from ``RawConfigParser``'s ``set()`` method.
+    ``LazyConfig.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
+        self._cfg = {
+            'sectionname': {
+                'optionname': LazyConfigOption(int, 1, self.getint)
+            }
+        }
+        """sample _cfg dictionary. Create your own in your derived class."""
+
+    def bool_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 ``bool``\ s.
+        """
+        if isinstance(value, bool):
+            return value
+        if value.lower() in self._boolean_states:
+            return self._boolean_states[value.lower()]
+        else:
+            raise ConfigValueError(_(u'Not a boolean: “%s”') % \
+                                   get_unicode(value))
+
+    def get_boolean(self, section, option):
+        # if the setting was not written to the configuration file, it may
+        # be still a boolean value - lets see
+        if self._modified:
+           tmp = self.get(section, option)
+           if isinstance(tmp, bool):
+               return tmp
+        return self.getboolean(section, option)
+
+    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('.')
+        if len(sect_opt) != 2:# do we need a regexp to check the format?
+            raise BadOptionError(
+                        _(u'Bad format: “%s” - expected: section.option') % \
+                        get_unicode(section_option))
+        if not sect_opt[0] in self._cfg:
+            raise NoSectionError(sect_opt[0])
+        if not sect_opt[1] in self._cfg[sect_opt[0]]:
+            raise NoOptionError(sect_opt[1], sect_opt[0])
+        return sect_opt
+
+    def items(self, section):
+        """returns a ``list`` with key, value ``tuples`` from the given
+        ``section``: ``[(key, value), …]``"""
+        if section in self._sections:# check if the section was parsed
+            d2 = self._sections[section]
+        elif not section in self._cfg:
+            raise NoSectionError(section)
+        else:
+            return ((k, self._cfg[section][k].default) \
+                    for k in self._cfg[section].iterkeys())
+        # still here? Get defaults and merge defaults with configured setting
+        d = dict((k, self._cfg[section][k].default) \
+                 for k in self._cfg[section].iterkeys())
+        d.update(d2)
+        if '__name__' in d: del d['__name__']
+        return d.iteritems()
+
+    def dget(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:
+            return self._cfg[section][option].getter(section, option)
+        except (NoSectionError, NoOptionError):
+            if not self._cfg[section][option].default is None:
+                return self._cfg[section][option].default
+            else:
+                raise NoDefaultError(section, option)
+
+    def pget(self, option):
+        """Returns the value of the `option`."""
+        section, option = self.__get_section_option(option)
+        return self._cfg[section][option].getter(section, option)
+
+    def set(self, option, value):
+        """Set the value of an option.
+        
+        Throws a ``ValueError`` if `value` couldn't be converted to
+        ``LazyConfigOption.cls``"""
+        section, option = self.__get_section_option(option)
+        val = self._cfg[section][option].cls(value)
+        if not self._cfg[section][option].validate is None:
+            val = self._cfg[section][option].validate(val)
+        if not RawConfigParser.has_section(self, section):
+            self.add_section(section)
+        RawConfigParser.set(self, section, option, val)
+        self._modified = True
+
+    def has_section(self, section):
+        """Checks if ``section`` is a known configuration section."""
+        return section.lower() in self._cfg 
+
+    def has_option(self, option):
+        """Checks if the option (section\ **.**\ option) is a known
+        configuration option."""
+        try:
+            self.__get_section_option(option)
+            return True
+        except(BadOptionError, NoSectionError, NoOptionError):
+            return False
+
+
+
+class LazyConfigOption(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 = cls
+        """The class of the option's value e.g. `str`, `unicode` or `bool`"""
+        self.default = default
+        """The option's default value, may be ``None``"""
+        if callable(getter):
+            self.getter = getter
+            """The getter method to get the option's value"""
+        else:
+            raise TypeError('getter has to be a callable, got a %r'\
+                            % getter.__class__.__name__)
+        if validate is None or callable(validate):
+            self.validate = validate
+            """A method to validate the value"""
+        else:
+            raise TypeError('validate has to be callable or None, got a %r'\
+                            % validate.__class__.__name__)
+
+
+class Config(LazyConfig):
     """This class is for reading and modifying vmm's configuration file."""
 
     def __init__(self, filename):
         """Creates a new Config instance
 
         Arguments:
-        filename -- path to the configuration file
+     
+        ``filename``
+            path to the configuration file
         """
-        ConfigParser.__init__(self)
+        LazyConfig.__init__(self)
         self.__cfgFileName = filename
         self.__cfgFile = None
-        self.__VMMsections = ('account', 'bin', 'database', 'domain',
-                              'maildir', 'misc', 'config')
-        self.__changes = False
         self.__missing = {}
-        self.__dbopts = [
-                ['host', 'localhot'],
-                ['user', 'vmm'],
-                ['pass', 'your secret password'],
-                ['name', 'mailsys']
-                ]
-        self.__mdopts = [
-                ['name', 'Maildir'],
-                ['folders', 'Drafts:Sent:Templates:Trash'],
-                ]
-        self.__accountopts = [
-                ['delete_directory', 'false'],
-                ['directory_mode', 448],
-                ['disk_usage', 'false'],
-                ['password_length', 8],
-                ['random_password', 'false'],
-                ['smtp', 'true'],
-                ['pop3', 'true'],
-                ['imap', 'true'],
-                ['sieve', 'true']
-                ]
-        self.__domdopts = [
-                ['auto_postmaster', 'true'],
-                ['delete_directory', 'false'],
-                ['directory_mode', 504],
-                ['force_deletion', 'false'],
-                ]
-        self.__binopts = [
-                ['dovecotpw', '/usr/sbin/dovecotpw'],
-                ['du', '/usr/bin/du'],
-                ['postconf', '/usr/sbin/postconf']
-                ]
-        self.__miscopts = [
-                ['base_directory', '/srv/mail'],
-                ['dovecot_version', '11'],
-                ['gid_mail', 8],
-                ['password_scheme', 'PLAIN'],
-                ['transport', 'dovecot:'],
-                ]
+
+        LCO = LazyConfigOption
+        bool_t = self.bool_new
+        self._cfg = {
+            'account': {
+                'delete_directory': LCO(bool_t, False, self.get_boolean),
+                'directory_mode':   LCO(int,    448,   self.getint),
+                'disk_usage':       LCO(bool_t, False, self.get_boolean),
+                'password_length':  LCO(int,    8,     self.getint),
+                'random_password':  LCO(bool_t, False, self.get_boolean),
+                'imap' :            LCO(bool_t, True,  self.get_boolean),
+                'pop3' :            LCO(bool_t, True,  self.get_boolean),
+                'sieve':            LCO(bool_t, True,  self.get_boolean),
+                'smtp' :            LCO(bool_t, True,  self.get_boolean),
+            },
+            'bin': {
+                'dovecotpw': LCO(str, '/usr/sbin/dovecotpw', self.get,
+                                 self.exec_ok),
+                'du':        LCO(str, '/usr/bin/du', self.get, self.exec_ok),
+                'postconf':  LCO(str, '/usr/sbin/postconf', self.get,
+                                 self.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.get_boolean),
+                'delete_directory': LCO(bool_t, False, self.get_boolean),
+                'directory_mode':   LCO(int,    504,   self.getint),
+                'force_deletion':   LCO(bool_t, False, self.get_boolean),
+            },
+            '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, self.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),
+            },
+            'config': {'done': LCO(bool_t, False, self.get_boolean)}
+        }
 
     def load(self):
         """Loads the configuration, read only.
@@ -76,7 +329,7 @@
         Raises a VMMConfigException if the configuration syntax is invalid.
         """
         try:
-            self.__cfgFile = file(self.__cfgFileName, 'r')
+            self.__cfgFile = open(self.__cfgFileName, 'r')
             self.readfp(self.__cfgFile)
         except (MissingSectionHeaderError, ParsingError), e:
             self.__cfgFile.close()
@@ -88,26 +341,62 @@
 
         Raises a VMMConfigException if the check fails.
         """
-        if not self.__chkSections():
+        if not self.__chkCfg():
             errmsg = StringIO()
-            errmsg.write(_("Using configuration file: %s\n") %\
-                    self.__cfgFileName)
-            for k,v in self.__missing.items():
-                if v[0] is True:
-                    errmsg.write(_(u"missing section: %s\n") % k)
-                else:
-                    errmsg.write(_(u"missing options in section %s:\n") % k)
-                    for o in v:
-                        errmsg.write(" * %s\n" % o)
+            errmsg.write(_(u'Missing options, which have no default value.\n'))
+            errmsg.write(_(u'Using configuration file: %s\n') %\
+                         self.__cfgFileName)
+            for section, options in self.__missing.iteritems():
+                errmsg.write(_(u'* Section: %s\n') % section)
+                for option in options:
+                    errmsg.write((u'    %s\n') % option)
             raise VMMConfigException(errmsg.getvalue(), ERR.CONF_ERROR)
 
     def getsections(self):
-        """Return a list with all configurable sections."""
-        return self.__VMMsections[:-1]
+        """Returns a generator object for all configurable sections."""
+        return (s for s in self._cfg.iterkeys() if s != 'config')
+
+    def is_dir(self, path):
+        """Checks if ``path`` is a directory.
+        
+        Throws a `ConfigValueError` if ``path`` is not a directory.
+        """
+        path = self.__expand_path(path)
+        if not os.path.isdir(path):
+            raise ConfigValueError(_(u'“%s” is not a directory') % \
+                                   get_unicode(path))
+        return path
 
-    def get(self, section, option, raw=False, vars=None):
-        return unicode(ConfigParser.get(self, section, option, raw, vars),
-                ENCODING, 'replace')
+    def exec_ok(self, binary):
+        """Checks if the ``binary`` exists and if it is executable.
+        
+        Throws a `ConfigValueError` if the ``binary`` isn't a file or is
+        not executable.
+        """
+        binary = self.__expand_path(binary)
+        if not os.path.isfile(binary):
+            raise ConfigValueError(_(u'“%s” is not a file') % \
+                                   get_unicode(binary))
+        if not os.access(binary, os.X_OK):
+            raise ConfigValueError(_(u'File is not executable: “%s”') % \
+                                   get_unicode(binary))
+        return binary
+
+    def known_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.SCHEMES
+
+    def unicode(self, section, option):
+        """Returns the value of the ``option`` from ``section``, converted
+        to Unicode.
+        """
+        return get_unicode(self.get(section, option))
 
     def configure(self, sections):
         """Interactive method for configuring all options in the given sections
@@ -115,76 +404,67 @@
         Arguments:
         sections -- list of strings with section names
         """
-        if not isinstance(sections, list):
-            raise TypeError("Argument 'sections' is not a list.")
-        # if [config] done = false (default at 1st run),
+        input_fmt = _(u'Enter new value for option %(option)s \
+[%(current_value)s]: ')
+        failures = 0
+
+        # if config.done == false (default at 1st run),
         # then set changes true
-        try:
-            if not self.getboolean('config', 'done'):
-                self.__changes = True
-        except ValueError:
-            self.set('config', 'done', 'False')
-            self.__changes = True
+        if not self.dget('config.done'):
+            self._modified = True
         w_std(_(u'Using configuration file: %s\n') % self.__cfgFileName)
         for s in sections:
-            if s != 'config':
-                w_std(_(u'* Config section: “%s”') % s )
+            w_std(_(u'* Config section: “%s”') % s )
             for opt, val in self.items(s):
-                newval = raw_input(
-                _('Enter new value for option %(opt)s [%(val)s]: ').encode(
-                    ENCODING, 'replace') % {'opt': opt, 'val': val})
-                if newval and newval != val:
-                    self.set(s, opt, newval)
-                    self.__changes = True
+                failures = 0
+                while True:
+                    newval = raw_input(input_fmt.encode(ENCODING,'replace') %{
+                                       'option': opt, 'current_value': val})
+                    if newval and newval != val:
+                        try:
+                            self.set('%s.%s' % (s, opt), newval)
+                            break
+                        except (ValueError, ConfigValueError), e:
+                            w_std(_(u'Warning: %s') % e)
+                            failures += 1
+                            if failures > 2:
+                                raise VMMConfigException(
+                                    _(u'Too many failures - try again later.'),
+                                    ERR.VMM_TOO_MANY_FAILURES)
+                    else:
+                        break
             print
-        if self.__changes:
+        if self._modified:
             self.__saveChanges()
 
     def __saveChanges(self):
         """Writes changes to the configuration file."""
-        self.set('config', 'done', 'true')
+        self.set('config.done', True)
         copy2(self.__cfgFileName, self.__cfgFileName+'.bak')
-        self.__cfgFile = file(self.__cfgFileName, 'w')
+        self.__cfgFile = open(self.__cfgFileName, 'w')
         self.write(self.__cfgFile)
         self.__cfgFile.close()
 
-    def __chkSections(self):
-        """Checks if all configuration sections are existing."""
+    def __chkCfg(self):
+        """Checks all section's options for settings w/o default values.
+        
+        Returns ``True`` if everything is fine, else ``False``."""
         errors = False
-        for s in self.__VMMsections:
-            if not self.has_section(s):
-                self.__missing[s] = [True]
-                errors = True
-            elif not self.__chkOptions(s):
-                errors = True
+        for section in self._cfg.iterkeys():
+            missing = []
+            for option, value in self._cfg[section].iteritems():
+                if (value.default is None
+                and not RawConfigParser.has_option(self, section, option)):
+                    missing.append(option)
+                    errors = True
+            if len(missing):
+                self.__missing[section] = missing
         return not errors
 
-    def __chkOptions(self, section):
-        """Checks if all configuration options in section are existing.
-
-        Arguments:
-        section -- the section to be checked
-        """
-        retval = True
-        missing = []
-        if section == 'database':
-            opts = self.__dbopts
-        elif section == 'maildir':
-            opts = self.__mdopts
-        elif section == 'account':
-            opts = self.__accountopts
-        elif section == 'domain':
-            opts = self.__domdopts
-        elif section == 'bin':
-            opts = self.__binopts
-        elif section == 'misc':
-            opts = self.__miscopts
-        elif section == 'config':
-            opts = [['done', 'false']]
-        for o, v in opts:
-            if not self.has_option(section, o):
-                missing.append(o)
-                retval = False
-        if len(missing):
-            self.__missing[section] = missing
-        return retval
+    def __expand_path(self, path):
+        """Expands paths, starting with ``.`` or ``~``, to an absolute path."""
+        if path.startswith('.'):
+            return os.path.abspath(path)
+        if path.startswith('~'):
+            return os.path.expanduser(path)
+        return path
--- a/VirtualMailManager/VirtualMailManager.py	Fri Jan 22 04:31:38 2010 +0000
+++ b/VirtualMailManager/VirtualMailManager.py	Sun Jan 24 06:40:38 2010 +0000
@@ -32,8 +32,8 @@
 
 class VirtualMailManager(object):
     """The main class for vmm"""
-    __slots__ = ('__Cfg', '__cfgFileName', '__cfgSections', '__dbh', '__scheme',
-            '__warnings', '_postconf')
+    __slots__ = ('__Cfg', '__cfgFileName', '__dbh', '__scheme', '__warnings',
+                 '_postconf')
     def __init__(self):
         """Creates a new VirtualMailManager instance.
         Throws a VMMNotRootException if your uid is greater 0.
@@ -50,9 +50,8 @@
             self.__Cfg = Cfg(self.__cfgFileName)
             self.__Cfg.load()
             self.__Cfg.check()
-            self.__cfgSections = self.__Cfg.getsections()
-            self.__scheme = self.__Cfg.get('misc', 'password_scheme')
-            self._postconf = Postconf(self.__Cfg.get('bin', 'postconf'))
+            self.__scheme = self.__Cfg.dget('misc.password_scheme')
+            self._postconf = Postconf(self.__Cfg.dget('bin.postconf'))
         if not os.sys.argv[1] in ['cf', 'configure']:
             self.__chkenv()
 
@@ -83,11 +82,11 @@
 
     def __chkenv(self):
         """"""
-        basedir = self.__Cfg.get('misc', 'base_directory')
+        basedir = self.__Cfg.dget('misc.base_directory')
         if not os.path.exists(basedir):
             old_umask = os.umask(0006)
             os.makedirs(basedir, 0771)
-            os.chown(basedir, 0, self.__Cfg.getint('misc', 'gid_mail'))
+            os.chown(basedir, 0, self.__Cfg.dget('misc.gid_mail'))
             os.umask(old_umask)
         elif not os.path.isdir(basedir):
             raise VMMException(_(u'“%s” is not a directory.\n\
@@ -108,10 +107,10 @@
         if self.__dbh is None or not self.__dbh._isOpen:
             try:
                 self.__dbh = PgSQL.connect(
-                        database=self.__Cfg.get('database', 'name'),
-                        user=self.__Cfg.get('database', 'user'),
-                        host=self.__Cfg.get('database', 'host'),
-                        password=self.__Cfg.get('database', 'pass'),
+                        database=self.__Cfg.dget('database.name'),
+                        user=self.__Cfg.pget('database.user'),
+                        host=self.__Cfg.dget('database.host'),
+                        password=self.__Cfg.pget('database.pass'),
                         client_encoding='utf8', unicode_results=True)
                 dbc = self.__dbh.cursor()
                 dbc.execute("SET NAMES 'UTF8'")
@@ -234,10 +233,10 @@
 
     def __getDomain(self, domainname, transport=None):
         if transport is None:
-            transport = self.__Cfg.get('misc', 'transport')
+            transport = self.__Cfg.dget('misc.transport')
         self.__dbConnect()
         return Domain(self.__dbh, domainname,
-                self.__Cfg.get('misc', 'base_directory'), transport)
+                self.__Cfg.dget('misc.base_directory'), transport)
 
     def __getDiskUsage(self, directory):
         """Estimate file space usage for the given directory.
@@ -246,7 +245,7 @@
         directory -- the directory to summarize recursively disk usage for
         """
         if self.__isdir(directory):
-            return Popen([self.__Cfg.get('bin', 'du'), "-hs", directory],
+            return Popen([self.__Cfg.dget('bin.du'), "-hs", directory],
                 stdout=PIPE).communicate()[0].split('\t')[0]
         else:
             return 0
@@ -259,7 +258,7 @@
 
     def __makedir(self, directory, mode=None, uid=None, gid=None):
         if mode is None:
-            mode = self.__Cfg.getint('account', 'directory_mode')
+            mode = self.__Cfg.dget('account.directory_mode')
         if uid is None:
             uid = 0
         if gid is None:
@@ -270,21 +269,21 @@
     def __domDirMake(self, domdir, gid):
         os.umask(0006)
         oldpwd = os.getcwd()
-        basedir = self.__Cfg.get('misc', 'base_directory')
+        basedir = self.__Cfg.dget('misc.base_directory')
         domdirdirs = domdir.replace(basedir+'/', '').split('/')
 
         os.chdir(basedir)
         if not os.path.isdir(domdirdirs[0]):
             self.__makedir(domdirdirs[0], 489, 0,
-                           self.__Cfg.getint('misc', 'gid_mail'))
+                           self.__Cfg.dget('misc.gid_mail'))
         os.chdir(domdirdirs[0])
         os.umask(0007)
-        self.__makedir(domdirdirs[1],
-                       self.__Cfg.getint('domain', 'directory_mode'), 0, gid)
+        self.__makedir(domdirdirs[1], self.__Cfg.dget('domain.directory_mode'),
+                       0, gid)
         os.chdir(oldpwd)
 
     def __subscribeFL(self, folderlist, uid, gid):
-        fname = os.path.join(self.__Cfg.get('maildir','name'), 'subscriptions')
+        fname = os.path.join(self.__Cfg.dget('maildir.name'), 'subscriptions')
         sf = file(fname, 'w')
         for f in folderlist:
             sf.write(f+'\n')
@@ -305,15 +304,15 @@
         oldpwd = os.getcwd()
         os.chdir(domdir)
 
-        maildir = self.__Cfg.get('maildir', 'name')
+        maildir = self.__Cfg.dget('maildir.name')
         folders = [maildir]
-        for folder in self.__Cfg.get('maildir', 'folders').split(':'):
+        for folder in self.__Cfg.dget('maildir.folders').split(':'):
             folder = folder.strip()
             if len(folder) and not folder.count('..')\
             and re.match(RE_MBOX_NAMES, folder):
                 folders.append('%s/.%s' % (maildir, folder))
         subdirs = ['cur', 'new', 'tmp']
-        mode = self.__Cfg.getint('account', 'directory_mode')
+        mode = self.__Cfg.dget('account.directory_mode')
 
         self.__makedir('%s' % uid, mode, uid, gid)
         os.chdir('%s' % uid)
@@ -348,7 +347,7 @@
         if gid > 0:
             if not self.__isdir(domdir):
                 return
-            basedir = self.__Cfg.get('misc', 'base_directory')
+            basedir = self.__Cfg.dget('misc.base_directory')
             domdirdirs = domdir.replace(basedir+'/', '').split('/')
             domdirparent = os.path.join(basedir, domdirdirs[0])
             if basedir.count('..') or domdir.count('..'):
@@ -415,7 +414,7 @@
             return '{%s}%s' % (self.__scheme, self.__pwMD4(password))
         elif self.__scheme in ['SMD5', 'SSHA', 'CRAM-MD5', 'HMAC-MD5',
                 'LANMAN', 'NTLM', 'RPA']:
-            return Popen([self.__Cfg.get('bin', 'dovecotpw'), '-s',
+            return Popen([self.__Cfg.dget('bin.dovecotpw'), '-s',
                 self.__scheme,'-p',password],stdout=PIPE).communicate()[0][:-1]
         else:
             return '{%s}%s' % (self.__scheme, password)
@@ -428,19 +427,19 @@
         """Returns a list with all available warnings."""
         return self.__warnings
 
-    def cfgGetBoolean(self, section, option):
-        return self.__Cfg.getboolean(section, option)
+    def cfgDget(self, option):
+        return self.__Cfg.dget(option)
 
-    def cfgGetInt(self, section, option):
-        return self.__Cfg.getint(section, option)
+    def cfgPget(self, option):
+        return self.__Cfg.pget(option)
 
-    def cfgGetString(self, section, option):
-        return self.__Cfg.get(section, option)
+    def cfgSet(self, option, value):
+        return self.__Cfg.set(option, value)
 
     def setupIsDone(self):
         """Checks if vmm is configured, returns bool"""
         try:
-            return self.__Cfg.getboolean('config', 'done')
+            return self.__Cfg.dget('config.done')
         except ValueError, e:
             raise VMMConfigException(_(u"""Configuration error: "%s"
 (in section "config", option "done") see also: vmm.cfg(5)\n""") % str(e),
@@ -458,8 +457,8 @@
             'database', 'maildir', 'bin' or 'misc'
         """
         if section is None:
-            self.__Cfg.configure(self.__cfgSections)
-        elif section in self.__cfgSections:
+            self.__Cfg.configure(self.__Cfg.getsections())
+        elif section in self.__Cfg.getsections():
             self.__Cfg.configure([section])
         else:
             raise VMMException(_(u"Invalid section: “%s”") % section,
@@ -487,8 +486,7 @@
         dom = self.__getDomain(domainname)
         gid = dom.getID()
         domdir = dom.getDir()
-        if self.__Cfg.getboolean('domain', 'force_deletion')\
-        or force == 'delall':
+        if self.__Cfg.dget('domain.force_deletion') or force == 'delall':
             dom.delete(True, True)
         elif force == 'deluser':
             dom.delete(delUser=True)
@@ -496,7 +494,7 @@
             dom.delete(delAlias=True)
         else:
             dom.delete()
-        if self.__Cfg.getboolean('domain', 'delete_directory'):
+        if self.__Cfg.dget('domain.delete_directory'):
             self.__domDirDelete(domdir, gid)
 
     def domainInfo(self, domainname, details=None):
@@ -589,12 +587,12 @@
         if password is None:
             password = self._readpass()
             acc.setPassword(self.__pwhash(password))
-        acc.save(self.__Cfg.get('maildir', 'name'),
-                self.__Cfg.getint('misc', 'dovecot_version'),
-                self.__Cfg.getboolean('account', 'smtp'),
-                self.__Cfg.getboolean('account', 'pop3'),
-                self.__Cfg.getboolean('account', 'imap'),
-                self.__Cfg.getboolean('account', 'sieve'))
+        acc.save(self.__Cfg.dget('maildir.name'),
+                 self.__Cfg.dget('misc.dovecot_version'),
+                 self.__Cfg.dget('account.smtp'),
+                 self.__Cfg.dget('account.pop3'),
+                 self.__Cfg.dget('account.imap'),
+                 self.__Cfg.dget('account.sieve'))
         self.__mailDirMake(acc.getDir('domain'), acc.getUID(), acc.getGID())
 
     def aliasAdd(self, aliasaddress, targetaddress):
@@ -616,7 +614,7 @@
         uid = acc.getUID()
         gid = acc.getGID()
         acc.delete(force)
-        if self.__Cfg.getboolean('account', 'delete_directory'):
+        if self.__Cfg.dget('account.delete_directory'):
             try:
                 self.__userDirDelete(acc.getDir('domain'), uid, gid)
             except VMMException, e:
@@ -640,17 +638,16 @@
         alias.delete()
 
     def userInfo(self, emailaddress, details=None):
-        if details not in [None, 'du', 'aliases', 'full']:
+        if details not in (None, 'du', 'aliases', 'full'):
             raise VMMException(_(u'Invalid argument: “%s”') % details,
-                    ERR.INVALID_AGUMENT)
+                               ERR.INVALID_AGUMENT)
         acc = self.__getAccount(emailaddress)
-        info = acc.getInfo(self.__Cfg.getint('misc', 'dovecot_version'))
-        if self.__Cfg.getboolean('account', 'disk_usage')\
-        or details in ['du', 'full']:
+        info = acc.getInfo(self.__Cfg.dget('misc.dovecot_version'))
+        if self.__Cfg.dget('account.disk_usage') or details in ('du', 'full'):
             info['disk usage'] = self.__getDiskUsage('%(maildir)s' % info)
-            if details in [None, 'du']:
+            if details in (None, 'du'):
                 return info
-        if details in ['aliases', 'full']:
+        if details in ('aliases', 'full'):
             return (info, acc.getAliases())
         return info
 
@@ -683,7 +680,7 @@
    in a future release.\n\
    Please use the service name “sieve” instead.'))
         acc = self.__getAccount(emailaddress)
-        acc.disable(self.__Cfg.getint('misc', 'dovecot_version'), service)
+        acc.disable(self.__Cfg.dget('misc.dovecot_version'), service)
 
     def userEnable(self, emailaddress, service=None):
         if service == 'managesieve':
@@ -693,7 +690,7 @@
    in a future release.\n\
    Please use the service name “sieve” instead.'))
         acc = self.__getAccount(emailaddress)
-        acc.enable(self.__Cfg.getint('misc', 'dovecot_version'), service)
+        acc.enable(self.__Cfg.dget('misc.dovecot_version'), service)
 
     def relocatedAdd(self, emailaddress, targetaddress):
         relocated = self.__getRelocated(emailaddress, targetaddress)
--- a/VirtualMailManager/__init__.py	Fri Jan 22 04:31:38 2010 +0000
+++ b/VirtualMailManager/__init__.py	Sun Jan 24 06:40:38 2010 +0000
@@ -38,6 +38,12 @@
         _write('\n')
     os.sys.exit(code)
 
+def get_unicode(string):
+    """Converts `string` to `unicode`, if necessary."""
+    if isinstance(string, unicode):
+        return string
+    return unicode(string, ENCODING, 'replace')
+
 __all__ = [
         # imported modules
         'os', 're', 'locale',
@@ -46,6 +52,6 @@
         # error codes
         'ERR',
         # defined stuff
-        'ENCODING', 'w_std', 'w_err'
+        'ENCODING', 'get_unicode', 'w_std', 'w_err'
         ]
 # EOF
--- a/vmm	Fri Jan 22 04:31:38 2010 +0000
+++ b/vmm	Sun Jan 24 06:40:38 2010 +0000
@@ -77,7 +77,7 @@
 
 def _getOrder():
     order = ()
-    if vmm.cfgGetInt('misc', 'dovecot_version') > 11:
+    if vmm.cfgDget('misc.dovecot_version') > 11:
         sieve_name = u'sieve'
     else:
         sieve_name = u'managesieve'
@@ -87,7 +87,7 @@
                 (u'aliases', 0), (u'relocated', 0))
     elif argv[1] in (u'ui', u'userinfo'):
         if argc == 4 and argv[3] != u'aliases'\
-        or vmm.cfgGetBoolean('account', 'disk_usage'):
+        or vmm.cfgDget('account.disk_usage'):
             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
                     (u'transport', 0), (u'maildir', 0), (u'disk usage', 0),
                     (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve_name, 1))