root/trunk/src/cryptobox/core/settings.py @ 1090

Revision 1090, 29.9 kB (checked in by lars, 3 years ago)

add "is_parent_of" function to blockdevice module
improve pre-defined exceptions
improve few failure behaviours

  • Property svn:keywords set to Id
Line 
1#
2# Copyright 2006 sense.lab e.V.
3#
4# This file is part of the CryptoBox.
5#
6# The CryptoBox is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# The CryptoBox is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with the CryptoBox; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19#
20
21"""Manage the configuration of a CryptoBox
22"""
23
24__revision__ = "$Id$"
25
26from cryptobox.core.exceptions import *
27import logging
28import subprocess
29import os
30import configobj, validate
31import syslog
32
33
34CONF_LOCATIONS = [
35    "./cryptobox.conf",
36    "~/.cryptobox.conf",
37    "/etc/cryptobox-server/cryptobox.conf"]
38
39VOLUMESDB_FILE = "cryptobox_volumes.db"
40PLUGINCONF_FILE = "cryptobox_plugins.conf"
41USERDB_FILE = "cryptobox_users.db"
42
43## allow to retrieve the most recently created setting object
44CURRENT_SETTING = []
45
46
47def get_current_settings():
48    """return the most recently created setting object
49    """
50    if not CURRENT_SETTING:
51        return None
52    else:
53        return CURRENT_SETTING[0]
54
55
56
57class CryptoBoxSettings:
58    """Manage the various configuration files of the CryptoBox
59    """
60
61    def __init__(self, config_file=None):
62        self.__is_initialized = False
63        self.log = logging.getLogger("CryptoBox")
64        config_file = self.__get_config_filename(config_file)
65        self.log.info("loading config file: %s" % config_file)
66        self.prefs = self.__get_preferences(config_file)
67        if not "PluginSettings" in self.prefs:
68            self.prefs["PluginSettings"] = {}
69        self.__validate_config()
70        self.__configure_log_handler()
71        self.__check_unknown_preferences()
72        self.prepare_partition()
73        self.volumes_db = self.__get_volumes_database()
74        self.plugin_conf = self.__get_plugin_config()
75        self.user_db = self.__get_user_db()
76        self.misc_files = []
77        self.reload_misc_files()
78        self.__is_initialized = True
79        CURRENT_SETTING.insert(0, self)
80   
81
82    def reload_misc_files(self):
83        """Call this method after creating or removing a 'misc' configuration file
84        """
85        self.misc_files = self.__get_misc_files()
86   
87
88    def write(self):
89        """
90        write all local setting files including the content of the "misc" subdirectory
91        """
92        status = True
93        try:
94            self.volumes_db.write()
95        except IOError:
96            self.log.warn("Could not save the volume database")
97            status = False
98        try:
99            self.plugin_conf.write()
100        except IOError:
101            self.log.warn("Could not save the plugin configuration")
102            status = False
103        try:
104            self.user_db.write()
105        except IOError:
106            self.log.warn("Could not save the user database")
107            status = False
108        for misc_file in self.misc_files:
109            if not misc_file.save():
110                self.log.warn("Could not save a misc setting file (%s)" % misc_file.filename)
111                status = False
112        return status
113
114
115    def get_misc_config_filename(self, name):
116        """Return an absolute filename for a given filename 'name'
117
118        'name' should not contain slashes (no directory part!)
119        """
120        return os.path.join(self.prefs["Locations"]["SettingsDir"], "misc", name)
121   
122
123    def create_misc_config_file(self, name, content):
124        """Create a new configuration file in the 'settings' directory
125
126        "name" should be the basename (without a directory)
127        "content" will be directly written to the file
128        this method may throw an IOException
129        """
130        misc_conf_file = self.get_misc_config_filename(name)
131        misc_conf_dir = os.path.dirname(misc_conf_file)
132        if not os.path.isdir(misc_conf_dir):
133            try:
134                os.mkdir(misc_conf_dir)
135            except OSError, err_msg:
136                ## the caller expects only IOError
137                raise IOError, err_msg
138        cfile = open(misc_conf_file, "w")
139        try:
140            cfile.write(content)
141        except IOError:
142            cfile.close()
143            raise
144        cfile.close()
145        ## reread all misc files automatically - this should be ok
146        self.reload_misc_files()
147
148
149    def requires_partition(self):
150        return bool(self.prefs["Main"]["UseConfigPartition"])
151   
152
153    def get_active_partition(self):
154        """Return the currently active cnfiguration partition.
155        """
156        settings_dir = self.prefs["Locations"]["SettingsDir"]
157        if not os.path.ismount(settings_dir):
158            return None
159        for line in file("/proc/mounts"):
160            fields = line.split(" ")
161            mount_dir = fields[1]
162            fs_type = fields[2]
163            if fs_type == "tmpfs":
164                ## skip ramdisks - these are not really "active partitions"
165                continue
166            try:
167                if os.path.samefile(mount_dir, settings_dir):
168                    return fields[0]
169            except OSError:
170                pass
171        ## no matching entry found
172        return None
173
174
175    def mount_partition(self):
176        """Mount a config partition.
177        """
178        self.log.debug("trying to mount configuration partition")
179        if not self.requires_partition():
180            self.log.warn("mountConfigPartition: configuration partition is "
181                    + "not required - mounting anyway")
182        if self.get_active_partition():
183            self.log.warn("mountConfigPartition: configuration partition already "
184                    + "mounted - not mounting again")
185            return False
186        conf_partitions = self.get_available_partitions()
187        mount_dir = self.prefs["Locations"]["SettingsDir"]
188        if not conf_partitions:
189            ## return, if tmpfs is already mounted
190            if os.path.ismount(mount_dir):
191                self.log.info("A ramdisk seems to be already mounted as a config " \
192                        + "partition - doing nothing ...")
193                ## return without any actions
194                return True
195            self.log.warn("no configuration partition found - you have to create "
196                    + "it first")
197            ## mount tmpfs instead to provide a place for storing stuff
198            ## "_tmpfs_" as parameter for mount is interpreted as a magic word
199            ## by CryptoBoxRootActions
200            proc = subprocess.Popen(
201                shell = False,
202                stdout = subprocess.PIPE,
203                stderr = subprocess.PIPE,
204                args = [
205                    self.prefs["Programs"]["super"],
206                    self.prefs["Programs"]["CryptoBoxRootActions"],
207                    "program", "mount",
208                    "_tmpfs_",
209                    mount_dir ])
210            (stdout, stderr) = proc.communicate()
211            if proc.returncode != 0:
212                self.log.error("Failed to mount a ramdisk for storing settings: %s" \
213                        % stderr)
214                return False
215            self.log.info("Ramdisk (tmpfs) mounted as config partition ...")
216        else:
217            partition = conf_partitions[0]
218            ## umount tmpfs in case it is active
219            if os.path.ismount(mount_dir):
220                self.umount_partition()
221            proc = subprocess.Popen(
222                shell = False,
223                stdout = subprocess.PIPE,
224                stderr = subprocess.PIPE,
225                args = [
226                    self.prefs["Programs"]["super"],
227                    self.prefs["Programs"]["CryptoBoxRootActions"],
228                    "program", "mount",
229                    partition,
230                    mount_dir ])
231            (stdout, stderr) = proc.communicate()
232            if proc.returncode != 0:
233                self.log.error("Failed to mount the configuration partition (%s): %s" % \
234                        (partition, stderr))
235                return False
236            self.log.info("configuration partition mounted: %s" % partition)
237        ## write config files (not during first initialization of this object)
238        if self.__is_initialized:
239            self.write()
240        return True
241
242
243    def umount_partition(self):
244        """Umount the currently active configuration partition.
245        """
246        mount_dir = self.prefs["Locations"]["SettingsDir"]
247        if not os.path.ismount(mount_dir):
248            self.log.warn("umountConfigPartition: no configuration partition mounted")
249            return False
250        self.reload_misc_files()
251        proc = subprocess.Popen(
252            shell = False,
253            stdout = subprocess.PIPE,
254            stderr = subprocess.PIPE,
255            args = [
256                self.prefs["Programs"]["super"],
257                self.prefs["Programs"]["CryptoBoxRootActions"],
258                "program", "umount",
259                mount_dir ])
260        (stdout, stderr) = proc.communicate()
261        if proc.returncode != 0:
262            self.log.error("Failed to unmount the configuration partition: %s" % stderr)
263            return False
264        self.log.info("configuration partition unmounted")
265        return True
266       
267
268    def get_available_partitions(self):
269        """returns a sequence of found config partitions"""
270        self.log.debug("Retrieving available configuration partitions ...")
271        proc = subprocess.Popen(
272            shell = False,
273            stdout = subprocess.PIPE,
274            stderr = subprocess.PIPE,
275            args = [
276                self.prefs["Programs"]["blkid"],
277                "-c", os.path.devnull,
278                "-t", "LABEL=%s" % self.prefs["Main"]["ConfigVolumeLabel"] ])
279        (output, error) = proc.communicate()
280        if proc.returncode == 2:
281            self.log.info("No configuration partitions found")
282            return []
283        elif proc.returncode == 4:
284            self.log.warn("Failed to call 'blkid' for unknown reasons.")
285            return []
286        elif proc.returncode == 0:
287            if output:
288                return [e.strip().split(":", 1)[0] for e in output.splitlines()]
289            else:
290                return []
291        else:
292            self.log.warn("Unknown exit code of 'blkid': %d - %s" \
293                    % (proc.returncode, error))
294
295   
296    def prepare_partition(self):
297        """Mount a config partition if necessary.
298        """
299        if self.requires_partition() and not self.get_active_partition():
300            self.mount_partition()
301
302
303    def __getitem__(self, key):
304        """redirect all requests to the 'prefs' attribute"""
305        return self.prefs[key]
306
307
308    def __get_preferences(self, config_file):
309        """Load the CryptoBox configuration.
310        """
311        import StringIO
312        config_rules = StringIO.StringIO(self.validation_spec)
313        try:
314            prefs = configobj.ConfigObj(config_file, configspec=config_rules)
315            if prefs:
316                self.log.info("found config: %s" % prefs.items())
317            else:
318                raise CBConfigUnavailableError(
319                        "failed to load the config file: %s" % config_file)
320        except IOError, err_msgg:
321            raise CBConfigUnavailableError(
322                    "unable to open the config file (%s): %s" % \
323                            (config_file, err_msg))
324        except configobj.ConfigObjError, err_msg:
325            raise CBConfigError("failed to load config file (%s): %s" % \
326                (config_file, err_msg))
327        return prefs
328
329
330    def __validate_config(self):
331        """Check the configuration settings and cast value types.
332        """
333        result = self.prefs.validate(CryptoBoxSettingsValidator(), preserve_errors=True)
334        error_list = configobj.flatten_errors(self.prefs, result)
335        if not error_list:
336            return
337        error_msgs = []
338        for sections, key, text in error_list:
339            section_name = "->".join(sections)
340            if not text:
341                error_msg = "undefined configuration value (%s) in section '%s'" % \
342                        (key, section_name)
343            else:
344                error_msg = "invalid configuration value (%s) in section '%s': %s" % \
345                        (key, section_name, text)
346            error_msgs.append(error_msg)
347        raise CBConfigError, "\n".join(error_msgs)
348
349
350    def __check_unknown_preferences(self):
351        """Check the configuration file for unknown settings to avoid spelling mistakes.
352        """
353        import StringIO
354        config_rules = configobj.ConfigObj(StringIO.StringIO(self.validation_spec),
355                list_values=False)
356        self.__recursive_section_check("", self.prefs, config_rules)
357       
358   
359    def __recursive_section_check(self, section_path, section_config, section_rules):
360        """should be called by '__check_unknown_preferences' for every section
361        sends a warning message to the logger for every undefined (see validation_spec)
362        configuration setting
363        """
364        for section in section_config.keys():
365            element_path = section_path + section
366            if section in section_rules.keys():
367                if isinstance(section_config[section], configobj.Section):
368                    if isinstance(section_rules[section], configobj.Section):
369                        self.__recursive_section_check(element_path + "->",
370                                section_config[section], section_rules[section])
371                    else:
372                        self.log.warn("configuration setting should be a value "
373                                + "instead of a section name: %s" % element_path)
374                else:
375                    if not isinstance(section_rules[section], configobj.Section):
376                        pass    # good - the setting is valid
377                    else:
378                        self.log.warn("configuration setting should be a section "
379                                + "name instead of a value: %s" % element_path)
380            elif element_path.startswith("PluginSettings->"):
381                ## ignore plugin settings
382                pass
383            else:
384                self.log.warn("unknown configuration setting: %s" % element_path)
385
386
387    def __get_plugin_config(self):
388        """Load the plugin configuration file if it exists.
389        """
390        import StringIO
391        plugin_rules = StringIO.StringIO(self.pluginValidationSpec)
392        try:
393            try:
394                plugin_conf_file = os.path.join(
395                        self.prefs["Locations"]["SettingsDir"], PLUGINCONF_FILE)
396            except KeyError:
397                raise CBConfigUndefinedError("Locations", "SettingsDir")
398        except SyntaxError:
399            raise CBConfigInvalidValueError("Locations", "SettingsDir", plugin_conf_file,
400                    "failed to interprete the filename of the plugin config file "
401                    + "correctly (%s)" % plugin_conf_file)
402        ## create plugin_conf_file if necessary
403        if os.path.exists(plugin_conf_file):
404            plugin_conf = configobj.ConfigObj(plugin_conf_file, configspec=plugin_rules)
405        else:
406            try:
407                plugin_conf = configobj.ConfigObj(plugin_conf_file,
408                        configspec=plugin_rules, create_empty=True)
409            except IOError:
410                plugin_conf = configobj.ConfigObj(configspec=plugin_rules)
411                plugin_conf.filename = plugin_conf_file
412        ## validate and convert values according to the spec
413        plugin_conf.validate(validate.Validator())
414        return plugin_conf
415
416
417    def __get_volumes_database(self):
418        """Load the volume database file if it exists.
419        """
420        #TODO: add configuration specification and validation [a]: -v
421        try:
422            try:
423                conf_file = os.path.join(
424                        self.prefs["Locations"]["SettingsDir"], VOLUMESDB_FILE)
425            except KeyError:
426                raise CBConfigUndefinedError("Locations", "SettingsDir")
427        except SyntaxError:
428            raise CBConfigInvalidValueError("Locations", "SettingsDir", conf_file,
429                    "failed to interprete the filename of the volume database "
430                    + "correctly (%s)" % conf_file)
431        ## create conf_file if necessary
432        if os.path.exists(conf_file):
433            conf = configobj.ConfigObj(conf_file)
434        else:
435            try:
436                conf = configobj.ConfigObj(conf_file, create_empty=True)
437            except IOError:
438                conf = configobj.ConfigObj()
439                conf.filename = conf_file
440        return conf
441
442
443    def __get_user_db(self):
444        """Load the user database file if it exists.
445        """
446        import StringIO, sha
447        user_db_rules = StringIO.StringIO(self.userDatabaseSpec)
448        try:
449            try:
450                user_db_file = os.path.join(
451                        self.prefs["Locations"]["SettingsDir"], USERDB_FILE)
452            except KeyError:
453                raise CBConfigUndefinedError("Locations", "SettingsDir")
454        except SyntaxError:
455            raise CBConfigInvalidValueError("Locations", "SettingsDir", user_db_file,
456                    "failed to interprete the filename of the users database file "
457                    + "correctly (%s)" % user_db_file)
458        ## create user_db_file if necessary
459        if os.path.exists(user_db_file):
460            user_db = configobj.ConfigObj(user_db_file, configspec=user_db_rules)
461        else:
462            try:
463                user_db = configobj.ConfigObj(user_db_file,
464                        configspec=user_db_rules, create_empty=True)
465            except IOError:
466                user_db = configobj.ConfigObj(configspec=user_db_rules)
467                user_db.filename = user_db_file
468        ## validate and set default value for "admin" user
469        user_db.validate(validate.Validator())
470        ## define password hash function - never use "sha" directly - SPOT
471        user_db.get_digest = lambda password: sha.new(password).hexdigest()
472        return user_db
473
474
475    def __get_misc_files(self):
476        """Load miscelleanous configuration files.
477
478        e.g.: an ssl certificate, ...
479        """
480        misc_dir = os.path.join(self.prefs["Locations"]["SettingsDir"], "misc")
481        if (not os.path.isdir(misc_dir)) or (not os.access(misc_dir, os.X_OK)):
482            return []
483        misc_files = []
484        for root, dirs, files in os.walk(misc_dir):
485            misc_files.extend([os.path.join(root, e) for e in files])
486        return [MiscConfigFile(os.path.join(misc_dir, f), self.log) for f in misc_files]
487
488
489    def __get_config_filename(self, config_file):
490        """Search for the configuration file.
491        """
492        import types
493        if config_file is None:
494            # no config file was specified - we will look for it in the ususal locations
495            conf_file_list = [os.path.expanduser(f)
496                for f in CONF_LOCATIONS
497                if os.path.exists(os.path.expanduser(f))]
498            if not conf_file_list:
499                # no possible config file found in the usual locations
500                raise CBConfigUnavailableError()
501            config_file = conf_file_list[0]
502        else:
503            # a config file was specified (e.g. via command line)
504            if type(config_file) != types.StringType:
505                raise CBConfigUnavailableError(
506                        "invalid config file specified: %s" % config_file)
507            if not os.path.exists(config_file):
508                raise CBConfigUnavailableError(
509                        "could not find the specified configuration file (%s)" % config_file)
510        return config_file
511
512
513    def __configure_log_handler(self):
514        """Configure the log handler of the CryptoBox according to the config.
515        """
516        log_level = self.prefs["Log"]["Level"].upper()
517        log_level_avail = ["DEBUG", "INFO", "WARN", "ERROR"]
518        if not log_level in log_level_avail:
519            raise CBConfigInvalidValueError("Log", "Level", log_level,
520                    "invalid log level: only %s are allowed" % str(log_level_avail))
521        log_destination = self.prefs["Log"]["Destination"].lower()
522        ## keep this in sync with the spec and the log_destination branches below
523        log_dest_avail = ['file', 'syslog']
524        if not log_destination in log_dest_avail:
525            raise CBConfigInvalidValueError("Log", "Destination", log_destination,
526                    "invalid log destination: only %s are allowed" % str(log_dest_avail))
527        if log_destination == 'file':
528            try:
529                log_handler = logging.FileHandler(self.prefs["Log"]["Details"])
530            except IOError:
531                raise CBEnvironmentError("could not write to log file (%s)" % \
532                        self.prefs["Log"]["Details"])
533            log_handler.setFormatter(
534                    logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
535        elif log_destination == 'syslog':
536            log_facility = self.prefs["Log"]["Details"].upper()
537            log_facil_avail = ['KERN', 'USER', 'MAIL', 'DAEMON', 'AUTH', 'SYSLOG',
538                    'LPR', 'NEWS', 'UUCP', 'CRON', 'AUTHPRIV', 'LOCAL0', 'LOCAL1',
539                    'LOCAL2', 'LOCAL3', 'LOCAL4', 'LOCAL5', 'LOCAL6', 'LOCAL7']
540            if not log_facility in log_facil_avail:
541                raise CBConfigInvalidValueError("Log", "Details", log_facility,
542                        "invalid log details for 'syslog': only %s are allowed" % \
543                        str(log_facil_avail))
544            ## retrive the log priority from the syslog module
545            log_handler = LocalSysLogHandler("CryptoBox",
546                    getattr(syslog, 'LOG_%s' % log_facility))
547            log_handler.setFormatter(
548                    logging.Formatter('%(asctime)s CryptoBox %(levelname)s: %(message)s'))
549        else:
550            ## this should never happen - we just have it in case someone forgets
551            ## to update the spec, the 'log_dest_avail' or the above branches
552            raise CBConfigInvalidValueError("Log", "Destination", log_destination,
553                    "invalid log destination: only %s are allowed" % str(log_dest_avail))
554        cbox_log = logging.getLogger("CryptoBox")
555        ## remove previous handlers (from 'basicConfig')
556        cbox_log.handlers = []
557        ## add new one
558        cbox_log.addHandler(log_handler)
559        ## do not call parent's handlers
560        cbox_log.propagate = False
561        ## 'log_level' is a string -> use 'getattr'
562        cbox_log.setLevel(getattr(logging, log_level))
563        ## the logger named "CryptoBox" is configured now
564
565
566    validation_spec = """
567[Main]
568AllowedDevices = listOfDevices(default="")
569DefaultVolumePrefix = string(min=1)
570DefaultCipher = string(default="aes-cbc-essiv:sha256")
571ConfigVolumeLabel = string(min=1, default="cbox_config")
572UseConfigPartition = integer(min=0, max=1, default=0)
573DisabledPlugins = list(default=list())
574
575[Locations]
576MountParentDir = directoryExists(default="/var/cache/cryptobox-server/mnt")
577SettingsDir = directoryExists(default="/var/cache/cryptobox-server/settings")
578TemplateDir = directoryExists(default="/usr/share/cryptobox-server/template")
579DocDir = directoryExists(default="/usr/share/doc/cryptobox-server/www-data")
580PluginDir = listOfExistingDirectories(default=list("/usr/share/cryptobox-server/plugins"))
581EventDir = string(default="/etc/cryptobox-server/events.d")
582
583[Log]
584Level = option("debug", "info", "warn", "error", default="warn")
585Destination = option("file", "syslog", default="file")
586Details = string(min=1, default="/var/log/cryptobox-server/cryptobox.log")
587
588[WebSettings]
589Stylesheet = string(min=1)
590Languages = listOfLanguages(default="en")
591
592[Programs]
593cryptsetup = fileExecutable(default="/sbin/cryptsetup")
594mkfs = fileExecutable(default="/sbin/mkfs")
595nice = fileExecutable(default="/usr/bin/nice")
596blkid = fileExecutable(default="/sbin/blkid")
597blockdev = fileExecutable(default="/sbin/blockdev")
598mount = fileExecutable(default="/bin/mount")
599umount = fileExecutable(default="/bin/umount")
600super = fileExecutable(default="/usr/bin/super")
601# this is the "program" name as defined in /etc/super.tab
602CryptoBoxRootActions = string(min=1)
603
604[PluginSettings]
605[[__many__]]
606        """
607
608    pluginValidationSpec = """
609[__many__]
610visibility = boolean(default=None)
611requestAuth = boolean(default=None)
612rank = integer(default=None)
613        """
614   
615    userDatabaseSpec = """
616[admins]
617admin = string(default=d033e22ae348aeb5660fc2140aec35850c4da997)
618        """
619   
620
621class CryptoBoxSettingsValidator(validate.Validator):
622    """Some custom configuration check functions.
623    """
624
625    def __init__(self):
626        validate.Validator.__init__(self)
627        self.functions["directoryExists"] = self.check_directory_exists
628        self.functions["fileExecutable"] = self.check_file_executable
629        self.functions["fileWriteable"] = self.check_file_writeable
630        self.functions["listOfExistingDirectories"] = self.check_existing_directories
631        self.functions["listOfLanguages"] = self.list_languages
632        self.functions["listOfDevices"] = self.list_devices
633   
634
635    def check_directory_exists(self, value):
636        """Is the directory accessible?
637        """
638        dir_path = os.path.abspath(value)
639        if not os.path.isdir(dir_path):
640            raise validate.VdtValueError("%s (not found)" % value)
641        if not os.access(dir_path, os.X_OK):
642            raise validate.VdtValueError("%s (access denied)" % value)
643        return dir_path
644   
645
646    def check_file_executable(self, value):
647        """Is the file executable?
648        """
649        file_path = os.path.abspath(value)
650        if not os.path.isfile(file_path):
651            raise validate.VdtValueError("%s (not found)" % value)
652        if not os.access(file_path, os.X_OK):
653            raise validate.VdtValueError("%s (access denied)" % value)
654        return file_path
655   
656
657    def check_file_writeable(self, value):
658        """Is the file writeable?
659        """
660        file_path = os.path.abspath(value)
661        if os.path.isfile(file_path):
662            if not os.access(file_path, os.W_OK):
663                raise validate.VdtValueError("%s (not found)" % value)
664        else:
665            parent_dir = os.path.dirname(file_path)
666            if os.path.isdir(parent_dir) and os.access(parent_dir, os.W_OK):
667                return file_path
668            raise validate.VdtValueError("%s (directory does not exist)" % value)
669        return file_path
670
671
672    def check_existing_directories(self, value):
673        """Are these directories accessible?
674        """
675        if not value:
676            raise validate.VdtValueError("no plugin directory specified")
677        if not isinstance(value, list):
678            value = [value]
679        result = []
680        for one_dir in value:
681            dir_path = os.path.abspath(one_dir)
682            if not os.path.isdir(dir_path):
683                raise validate.VdtValueError(
684                        "%s (plugin directory not found)" % one_dir)
685            if not os.access(dir_path, os.X_OK):
686                raise validate.VdtValueError(
687                        "%s (access denied for plugin directory)" % one_dir)
688            result.append(dir_path)
689        return result
690
691    def list_languages(self, langs):
692        """Return languages as a list.
693        """
694        if not langs:
695            raise validate.VdtValueError("no language specified")
696        if not isinstance(langs, list):
697            langs = [langs]
698        return langs
699
700    def list_devices(self, devices):
701        """Return devices as a list.
702        """
703        if not devices:
704            raise validate.VdtValueError("no device specified")
705        if not isinstance(devices, list):
706            devices = [devices]
707        return devices
708
709
710
711class MiscConfigFile:
712    """all other config files (e.g. a ssl certificate) to be stored"""
713
714    maxSize = 20480
715
716    def __init__(self, filename, logger):
717        self.filename = filename
718        self.log = logger
719        self.content = None
720        self.load()
721
722   
723    def load(self):
724        """Load a configuration file into memory.
725        """
726        fdesc = open(self.filename, "rb")
727        ## limit the maximum size
728        self.content = fdesc.read(self.maxSize)
729        if fdesc.tell() == self.maxSize:
730            self.log.warn("file in misc settings directory (" + str(self.filename) \
731                    + ") is bigger than allowed (" + str(self.maxSize) + ")")
732        fdesc.close()
733
734
735    def save(self):
736        """Save a configuration file to disk.
737        """
738        ## overriding of ro-files is not necessary (e.g. samba-include.conf)
739        if os.path.exists(self.filename) and not os.access(self.filename, os.W_OK):
740            return True
741        save_dir = os.path.dirname(self.filename)
742        ## create the directory, if necessary
743        if not os.path.isdir(save_dir):
744            try:
745                os.mkdir(save_dir)
746            except IOError:
747                return False
748        ## save the content of the file
749        try:
750            fdesc = open(self.filename, "wb")
751        except IOError:
752            return False
753        try:
754            fdesc.write(self.content)
755            fdesc.close()
756            return True
757        except IOError:
758            fdesc.close()
759            return False
760
761
762
763class LocalSysLogHandler(logging.Handler):
764    """Pass logging messages to a local syslog server without unix sockets.
765
766    derived from: logging.SysLogHandler
767    """
768
769    def __init__(self, prepend='CryptoBox', facility=syslog.LOG_USER):
770        logging.Handler.__init__(self)
771        self.formatter = None
772        self.facility = facility
773        syslog.openlog(prepend, 0, facility)
774   
775
776    def close(self):
777        """close the syslog connection
778        """
779        syslog.closelog()
780        logging.Handler.close(self)
781
782
783    def emit(self, record):
784        """format and send the log message
785        """
786        msg = "%s: %s" % (record.levelname, record.getMessage())
787        try:
788            syslog.syslog(record.levelno, msg)
789        except Exception:
790            self.handleError(record)
Note: See TracBrowser for help on using the browser.