# HG changeset patch # User Pascal Volk # Date 1264315238 0 # Node ID 974bafa593300a23f389d4fe5bdc9e477c6bbbdf # Parent c0e2c7687dd374e4eea2fe17640f0c0d1ed9fd87 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. diff -r c0e2c7687dd3 -r 974bafa59330 VirtualMailManager/Config.py --- 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 diff -r c0e2c7687dd3 -r 974bafa59330 VirtualMailManager/VirtualMailManager.py --- 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) diff -r c0e2c7687dd3 -r 974bafa59330 VirtualMailManager/__init__.py --- 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 diff -r c0e2c7687dd3 -r 974bafa59330 vmm --- 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))