9 """ |
9 """ |
10 |
10 |
11 from ConfigParser import \ |
11 from ConfigParser import \ |
12 Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \ |
12 Error, MissingSectionHeaderError, NoOptionError, NoSectionError, \ |
13 ParsingError, RawConfigParser |
13 ParsingError, RawConfigParser |
14 from cStringIO import StringIO# TODO: move interactive stff to cli |
14 from cStringIO import StringIO |
15 |
15 |
16 from VirtualMailManager.common import VERSION_RE, \ |
16 from VirtualMailManager.common import VERSION_RE, \ |
17 exec_ok, expand_path, get_unicode, lisdir, version_hex |
17 exec_ok, expand_path, get_unicode, lisdir, version_hex |
18 from VirtualMailManager.constants import CONF_ERROR |
18 from VirtualMailManager.constants import CONF_ERROR |
19 from VirtualMailManager.errors import ConfigError, VMMError |
19 from VirtualMailManager.errors import ConfigError, VMMError |
20 from VirtualMailManager.maillocation import known_format |
20 from VirtualMailManager.maillocation import known_format |
21 from VirtualMailManager.password import verify_scheme as _verify_scheme |
21 from VirtualMailManager.password import verify_scheme as _verify_scheme |
22 |
22 |
|
23 DB_MUDULES = ('psycopg2', 'pypgsql') |
|
24 DB_SSL_MODES = ('allow', 'disabled', 'prefer', 'require', 'verify-ca', |
|
25 'verify-full') |
23 |
26 |
24 _ = lambda msg: msg |
27 _ = lambda msg: msg |
25 |
28 |
26 |
29 |
27 class BadOptionError(Error): |
30 class BadOptionError(Error): |
289 path to the configuration file |
292 path to the configuration file |
290 """ |
293 """ |
291 LazyConfig.__init__(self) |
294 LazyConfig.__init__(self) |
292 self._cfg_filename = filename |
295 self._cfg_filename = filename |
293 self._cfg_file = None |
296 self._cfg_file = None |
294 self.__missing = {} |
297 self._missing = {} |
295 |
298 |
296 LCO = LazyConfigOption |
299 LCO = LazyConfigOption |
297 bool_t = self.bool_new |
300 bool_t = self.bool_new |
298 self._cfg = { |
301 self._cfg = { |
299 'account': { |
302 'account': { |
313 'du': LCO(str, '/usr/bin/du', self.get, exec_ok), |
316 'du': LCO(str, '/usr/bin/du', self.get, exec_ok), |
314 'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok), |
317 'postconf': LCO(str, '/usr/sbin/postconf', self.get, exec_ok), |
315 }, |
318 }, |
316 'database': { |
319 'database': { |
317 'host': LCO(str, 'localhost', self.get), |
320 'host': LCO(str, 'localhost', self.get), |
|
321 'module': LCO(str, 'psycopg2', self.get, check_db_module), |
318 'name': LCO(str, 'mailsys', self.get), |
322 'name': LCO(str, 'mailsys', self.get), |
319 'pass': LCO(str, None, self.get), |
323 'pass': LCO(str, None, self.get), |
|
324 'port': LCO(int, 5432, self.getint), |
|
325 'sslmode': LCO(str, 'prefer', self.get, check_db_ssl_mode), |
320 'user': LCO(str, None, self.get), |
326 'user': LCO(str, None, self.get), |
321 }, |
327 }, |
322 'domain': { |
328 'domain': { |
323 'auto_postmaster': LCO(bool_t, True, self.getboolean), |
329 'auto_postmaster': LCO(bool_t, True, self.getboolean), |
324 'delete_directory': LCO(bool_t, False, self.getboolean), |
330 'delete_directory': LCO(bool_t, False, self.getboolean), |
362 |
368 |
363 def check(self): |
369 def check(self): |
364 """Performs a configuration check. |
370 """Performs a configuration check. |
365 |
371 |
366 Raises a ConfigError if settings w/o a default value are missed. |
372 Raises a ConfigError if settings w/o a default value are missed. |
367 Or a ConfigValueError if 'misc.dovecot_version' has the wrong |
373 Or some settings have a invalid value. |
368 format. |
374 """ |
369 """ |
375 def iter_dict(): |
370 # TODO: There are only two settings w/o defaults. |
376 for section, options in self._missing.iteritems(): |
371 # So there is no need for cStringIO |
377 errmsg.write(_(u'* Section: %s\n') % section) |
372 if not self._chk_cfg(): |
378 errmsg.writelines(u' %s\n' % option for option in options) |
|
379 self._missing.clear() |
|
380 |
|
381 errmsg = None |
|
382 self._chk_non_default() |
|
383 miss_vers = 'misc' in self._missing and \ |
|
384 'dovecot_version' in self._missing['misc'] |
|
385 if self._missing: |
373 errmsg = StringIO() |
386 errmsg = StringIO() |
|
387 errmsg.write(_(u'Check of configuration file %s failed.\n') % |
|
388 self._cfg_filename) |
374 errmsg.write(_(u'Missing options, which have no default value.\n')) |
389 errmsg.write(_(u'Missing options, which have no default value.\n')) |
375 errmsg.write(_(u'Using configuration file: %s\n') % |
390 iter_dict() |
376 self._cfg_filename) |
391 self._chk_possible_values(miss_vers) |
377 for section, options in self.__missing.iteritems(): |
392 if self._missing: |
378 errmsg.write(_(u'* Section: %s\n') % section) |
393 if not errmsg: |
379 for option in options: |
394 errmsg = StringIO() |
380 errmsg.write((u' %s\n') % option) |
395 errmsg.write(_(u'Check of configuration file %s failed.\n') % |
|
396 self._cfg_filename) |
|
397 errmsg.write(_(u'Invalid configuration values.\n')) |
|
398 else: |
|
399 errmsg.write('\n' + _(u'Invalid configuration values.\n')) |
|
400 iter_dict() |
|
401 if errmsg: |
381 raise ConfigError(errmsg.getvalue(), CONF_ERROR) |
402 raise ConfigError(errmsg.getvalue(), CONF_ERROR) |
382 check_version_format(self.get('misc', 'dovecot_version')) |
|
383 |
403 |
384 def hexversion(self, section, option): |
404 def hexversion(self, section, option): |
385 """Converts the version number (e.g.: 1.2.3) from the *option*'s |
405 """Converts the version number (e.g.: 1.2.3) from the *option*'s |
386 value to an int.""" |
406 value to an int.""" |
387 return version_hex(self.get(section, option)) |
407 return version_hex(self.get(section, option)) |
389 def unicode(self, section, option): |
409 def unicode(self, section, option): |
390 """Returns the value of the `option` from `section`, converted |
410 """Returns the value of the `option` from `section`, converted |
391 to Unicode.""" |
411 to Unicode.""" |
392 return get_unicode(self.get(section, option)) |
412 return get_unicode(self.get(section, option)) |
393 |
413 |
394 def _chk_cfg(self): |
414 def _chk_non_default(self): |
395 """Checks all section's options for settings w/o a default |
415 """Checks all section's options for settings w/o a default |
396 value. |
416 value. Missing items will be stored in _missing. |
397 |
417 """ |
398 Returns `True` if everything is fine, else `False`. |
|
399 """ |
|
400 errors = False |
|
401 for section in self._cfg.iterkeys(): |
418 for section in self._cfg.iterkeys(): |
402 missing = [] |
419 missing = [] |
403 for option, value in self._cfg[section].iteritems(): |
420 for option, value in self._cfg[section].iteritems(): |
404 if (value.default is None and |
421 if (value.default is None and |
405 not RawConfigParser.has_option(self, section, option)): |
422 not RawConfigParser.has_option(self, section, option)): |
406 missing.append(option) |
423 missing.append(option) |
407 errors = True |
|
408 if missing: |
424 if missing: |
409 self.__missing[section] = missing |
425 self._missing[section] = missing |
410 return not errors |
426 |
|
427 def _chk_possible_values(self, miss_vers): |
|
428 """Check settings for which the possible values are known.""" |
|
429 if not miss_vers: |
|
430 value = self.get('misc', 'dovecot_version') |
|
431 if not VERSION_RE.match(value): |
|
432 self._missing['misc'] = ['version: ' +\ |
|
433 _(u"Not a valid Dovecot version: '%s'") % value] |
|
434 db_err = [] |
|
435 value = self.dget('database.module').lower() |
|
436 if value not in DB_MUDULES: |
|
437 db_err.append('module: ' + \ |
|
438 _(u"Unsupported database module: '%s'") % value) |
|
439 if value == 'psycopg2': |
|
440 value = self.dget('database.sslmode') |
|
441 if value not in DB_SSL_MODES: |
|
442 db_err.append('sslmode: ' + \ |
|
443 _(u"Unknown pgsql SSL mode: '%s'") % value) |
|
444 if db_err: |
|
445 self._missing['database'] = db_err |
411 |
446 |
412 |
447 |
413 def is_dir(path): |
448 def is_dir(path): |
414 """Check if the expanded path is a directory. When the expanded path |
449 """Check if the expanded path is a directory. When the expanded path |
415 is a directory the expanded path will be returned. Otherwise a |
450 is a directory the expanded path will be returned. Otherwise a |
417 """ |
452 """ |
418 path = expand_path(path) |
453 path = expand_path(path) |
419 if lisdir(path): |
454 if lisdir(path): |
420 return path |
455 return path |
421 raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path)) |
456 raise ConfigValueError(_(u"No such directory: %s") % get_unicode(path)) |
|
457 |
|
458 |
|
459 def check_db_module(module): |
|
460 """Check if the *module* is a supported pgsql module.""" |
|
461 if module.lower() in DB_MUDULES: |
|
462 return module |
|
463 raise ConfigValueError(_(u"Unsupported database module: '%s'") % |
|
464 get_unicode(module)) |
|
465 |
|
466 |
|
467 def check_db_ssl_mode(ssl_mode): |
|
468 """Check if the *ssl_mode* is one of the SSL modes, known by pgsql.""" |
|
469 if ssl_mode in DB_SSL_MODES: |
|
470 return ssl_mode |
|
471 raise ConfigValueError(_(u"Unknown pgsql SSL mode: '%s'") % |
|
472 get_unicode(ssl_mode)) |
422 |
473 |
423 |
474 |
424 def check_mailbox_format(format): |
475 def check_mailbox_format(format): |
425 """ |
476 """ |
426 Check if the mailbox format *format* is supported. When the *format* |
477 Check if the mailbox format *format* is supported. When the *format* |