summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Properties.py
blob: 33c9e1909c3f2b8f76149cc5b42d3794a50b3bac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import os
import re
import sys
import copy
import logging
import lxml.etree
import Bcfg2.Server.Plugin
try:
    from Bcfg2.Encryption import ssl_decrypt, EVPError
    have_crypto = True
except ImportError:
    have_crypto = False

logger = logging.getLogger(__name__)

SETUP = None

def passphrases():
    section = "encryption"
    if SETUP.cfp.has_section(section):
        return dict([(o, SETUP.cfp.get(section, o))
                     for o in SETUP.cfp.options(section)])
    else:
        return dict()


class PropertyFile(Bcfg2.Server.Plugin.StructFile):
    """Class for properties files."""
    def __init__(self, name):
        Bcfg2.Server.Plugin.StructFile.__init__(self, name)

    def write(self):
        """ Write the data in this data structure back to the property
        file """
        if self.validate_data():
            try:
                open(self.name,
                     "wb").write(lxml.etree.tostring(self.xdata,
                                                     pretty_print=True))
                return True
            except IOError:
                err = sys.exc_info()[1]
                logger.error("Failed to write %s: %s" % (self.name, err))
                return False
        else:
            return False

    def validate_data(self):
        """ ensure that the data in this object validates against the
        XML schema for this property file (if a schema exists) """
        schemafile = self.name.replace(".xml", ".xsd")
        if os.path.exists(schemafile):
            try:
                schema = lxml.etree.XMLSchema(file=schemafile)
            except:
                logger.error("Failed to process schema for %s" % self.name)
                return False
        else:
            # no schema exists
            return True

        if not schema.validate(self.xdata):
            logger.error("Data for %s fails to validate; run bcfg2-lint for "
                         "more details" % self.name)
            return False
        else:
            return True

    def Index(self):
        Bcfg2.Server.Plugin.StructFile.Index(self)
        if self.xdata.get("encryption", "false").lower() != "false":
            if not have_crypto:
                msg = "Properties: M2Crypto is not available: %s" % self.name
                logger.error(msg)
                raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
            for el in self.xdata.xpath("*[@encrypted]"):
                try:
                    el.text = self._decrypt(el)
                except EVPError:
                    msg = "Failed to decrypt %s element in %s" % (el.tag,
                                                                  self.name)
                    logger.error(msg)
                    raise Bcfg2.Server.PluginExecutionError(msg)

    def _decrypt(self, element):
        if not element.text.strip():
            return
        passes = passphrases()
        try:
            passphrase = passes[element.get("encrypted")]
            try:
                return ssl_decrypt(element.text, passphrase)
            except EVPError:
                # error is raised below
                pass
        except KeyError:
            for passwd in passes.values():
                try:
                    return ssl_decrypt(element.text, passwd)
                except EVPError:
                    pass
        raise EVPError("Failed to decrypt")

class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
    __child__ = PropertyFile
    patterns = re.compile(r'.*\.xml$')


class Properties(Bcfg2.Server.Plugin.Plugin,
                 Bcfg2.Server.Plugin.Connector):
    """
       The properties plugin maps property
       files into client metadata instances.
    """
    name = 'Properties'

    def __init__(self, core, datastore):
        global SETUP
        Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
        Bcfg2.Server.Plugin.Connector.__init__(self)
        try:
            self.store = PropDirectoryBacked(self.data, core.fam)
        except OSError:
            e = sys.exc_info()[1]
            self.logger.error("Error while creating Properties store: %s %s" %
                              (e.strerror, e.filename))
            raise Bcfg2.Server.Plugin.PluginInitError

        SETUP = core.setup

    def get_additional_data(self, _):
        return copy.copy(self.store.entries)