summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/Bcfg2/Client/Frame.py8
-rw-r--r--src/lib/Bcfg2/Options.py17
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py12
-rwxr-xr-xsrc/sbin/bcfg2-crypt174
-rwxr-xr-xsrc/sbin/bcfg2-info38
5 files changed, 175 insertions, 74 deletions
diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py
index 508e3b616..2fb81d6ba 100644
--- a/src/lib/Bcfg2/Client/Frame.py
+++ b/src/lib/Bcfg2/Client/Frame.py
@@ -7,6 +7,7 @@ import logging
import sys
import time
import Bcfg2.Client.Tools
+from Bcfg2.Compat import input
def cmpent(ent1, ent2):
@@ -150,12 +151,7 @@ class Frame(object):
else:
iprompt = prompt % (entry.tag, entry.get('name'))
try:
- # py3k compatibility
- try:
- ans = raw_input(iprompt.encode(sys.stdout.encoding,
- 'replace'))
- except NameError:
- ans = input(iprompt)
+ ans = input(iprompt.encode(sys.stdout.encoding, 'replace'))
if ans in ['y', 'Y']:
ret.append(entry)
except EOFError:
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py
index 34f4b2bc6..6ac9cafb1 100644
--- a/src/lib/Bcfg2/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -21,9 +21,9 @@ DEFAULT_INSTALL_PREFIX = '/usr'
class DefaultConfigParser(ConfigParser.ConfigParser):
- def __init__(self,*args,**kwargs):
+ def __init__(self, *args, **kwargs):
"""Make configuration options case sensitive"""
- ConfigParser.ConfigParser.__init__(self,*args,**kwargs)
+ ConfigParser.ConfigParser.__init__(self, *args, **kwargs)
self.optionxform = str
def get(self, section, option, **kwargs):
@@ -100,7 +100,7 @@ class Option(object):
rv.append("%s %s" % (self.cmd, self.odesc))
else:
rv.append("%s" % self.cmd)
-
+
if self.cf:
if self.cmd:
rv.append("; ")
@@ -175,9 +175,10 @@ class Option(object):
% (self.deprecated_cf[0], self.deprecated_cf[1],
self.cf[0], self.cf[1]))
return
- except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+ except (ConfigParser.NoSectionError,
+ ConfigParser.NoOptionError):
pass
-
+
# Default value not cooked
self.value = self.default
@@ -907,8 +908,8 @@ DECRYPT = \
default=False,
cmd='--decrypt',
long_arg=True)
-DECRYPT_STDOUT = \
- Option('Decrypt the specified file to stdout',
+CRYPT_STDOUT = \
+ Option('Decrypt or encrypt the specified file to stdout',
default=False,
cmd='--stdout',
long_arg=True)
@@ -968,7 +969,7 @@ SERVER_COMMON_OPTIONS = dict(repo=SERVER_REPOSITORY,
CRYPT_OPTIONS = dict(encrypt=ENCRYPT,
decrypt=DECRYPT,
- decrypt_stdout=DECRYPT_STDOUT,
+ crypt_stdout=CRYPT_STDOUT,
passphrase=CRYPT_PASSPHRASE,
xpath=CRYPT_XPATH,
properties=CRYPT_PROPERTIES,
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index 49500e915..1b925ce46 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
SETUP = None
+
class PropertyFile(Bcfg2.Server.Plugin.StructFile):
"""Class for properties files."""
def write(self):
@@ -124,12 +125,15 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
SETUP = core.setup
def get_additional_data(self, metadata):
- autowatch = self.core.setup.cfp.getboolean("properties", "automatch",
- default=False)
+ if self.core.setup.cfp.getboolean("properties", "automatch",
+ default=False):
+ default_automatch = "true"
+ else:
+ default_automatch = "false"
rv = dict()
for fname, pfile in self.store.entries.items():
- if (autowatch or
- pfile.xdata.get("automatch", "false").lower() == "true"):
+ if pfile.xdata.get("automatch",
+ default_automatch).lower() == "true":
rv[fname] = pfile.XMLMatch(metadata)
else:
rv[fname] = copy.copy(pfile)
diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt
index cb1b956fb..1af1771cf 100755
--- a/src/sbin/bcfg2-crypt
+++ b/src/sbin/bcfg2-crypt
@@ -3,16 +3,18 @@
import os
import sys
+import copy
import logging
import lxml.etree
import Bcfg2.Logger
import Bcfg2.Options
+from Bcfg2.Server import XMLParser
+from Bcfg2.Compat import input
try:
import Bcfg2.Encryption
except ImportError:
err = sys.exc_info()[1]
- print("Import failed '%s'. Is M2Crypto installed?" %
- err)
+ print("Could not import %s. Is M2Crypto installed?" % err)
raise SystemExit(1)
LOGGER = None
@@ -103,7 +105,8 @@ class Encryptor(object):
self.logger.error("Error reading %s, skipping: %s" % (fname, err))
return False
- self.set_passphrase()
+ if not self.set_passphrase():
+ return False
crypted = []
try:
@@ -119,22 +122,7 @@ class Encryptor(object):
self.logger.error("Error getting data to encrypt from %s: %s" %
(fname, err))
return False
-
- new_fname = self.get_encrypted_filename(fname)
- try:
- open(new_fname, "wb").write(self.unchunk(crypted, plaintext))
- self.logger.info("Wrote encrypted data to %s" % new_fname)
- return True
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Error writing encrypted data from %s to %s: %s" %
- (fname, new_fname, err))
- return False
- except EncryptionChunkingError:
- err = sys.exc_info()[1]
- self.logger.error("Error assembling encrypted data from %s: %s" %
- (fname, err))
- return False
+ return self.unchunk(crypted, plaintext)
def _encrypt(self, plaintext, passphrase, name=None):
return Bcfg2.Encryption.ssl_encrypt(plaintext, passphrase)
@@ -200,6 +188,25 @@ class Encryptor(object):
(fname, err))
return False
+ def write_encrypted(self, fname, data=None):
+ if data is None:
+ data = self.decrypt(fname)
+ new_fname = self.get_encrypted_filename(fname)
+ try:
+ open(new_fname, "wb").write(data)
+ self.logger.info("Wrote encrypted data to %s" % new_fname)
+ return True
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error writing encrypted data from %s to %s: %s" %
+ (fname, new_fname, err))
+ return False
+ except EncryptionChunkingError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error assembling encrypted data from %s: %s" %
+ (fname, err))
+ return False
+
def write_decrypted(self, fname, data=None):
if data is None:
data = self.decrypt(fname)
@@ -262,12 +269,12 @@ class PropertiesEncryptor(Encryptor):
name = "true"
if plaintext.text and plaintext.text.strip():
plaintext.text = Bcfg2.Encryption.ssl_encrypt(plaintext.text,
- passphrase)
+ passphrase).strip()
plaintext.set("encrypted", name)
return plaintext
def chunk(self, data):
- xdata = lxml.etree.XML(data)
+ xdata = lxml.etree.XML(data, parser=XMLParser)
if self.setup['xpath']:
elements = xdata.xpath(self.setup['xpath'])
if not elements:
@@ -276,7 +283,28 @@ class PropertiesEncryptor(Encryptor):
else:
elements = xdata.xpath('//*[@encrypted]')
if not elements:
- elements = list(xdata.getiterator())
+ elements = list(xdata.getiterator(tag=lxml.etree.Element))
+
+ # filter out elements without text data
+ for el in elements[:]:
+ if not el.text:
+ elements.remove(el)
+
+ if self.setup['interactive']:
+ for element in elements[:]:
+ if len(element):
+ elt = copy.copy(element)
+ for child in elt.iterchildren():
+ elt.remove(child)
+ else:
+ elt = element
+ print(lxml.etree.tostring(
+ elt,
+ xml_declaration=False).decode("UTF-8").strip())
+ ans = input("Encrypt this element? [y/N] ")
+ if not ans.lower().startswith("y"):
+ elements.remove(element)
+
# this is not a good use of a generator, but we need to
# generate the full list of elements in order to ensure that
# some exist before we know what to return
@@ -291,7 +319,9 @@ class PropertiesEncryptor(Encryptor):
while xdata.getparent() != None:
xdata = xdata.getparent()
xdata.set("encryption", "true")
- return lxml.etree.tostring(xdata, xml_declaration=False).decode('UTF-8')
+ return lxml.etree.tostring(xdata,
+ xml_declaration=False,
+ pretty_print=True).decode('UTF-8')
def _get_passphrase(self, chunk):
pname = chunk.get("encrypted") or chunk.get("encryption")
@@ -304,13 +334,13 @@ class PropertiesEncryptor(Encryptor):
if not crypted.text or not crypted.text.strip():
self.logger.warning("Skipping empty element %s" % crypted.tag)
return crypted
- rv = Bcfg2.Encryption.ssl_decrypt(crypted.text, passphrase)
- crypted.text = rv
+ crypted.text = Bcfg2.Encryption.ssl_decrypt(crypted.text,
+ passphrase).strip()
return crypted
def main():
- optinfo = dict()
+ optinfo = dict(interactive=Bcfg2.Options.INTERACTIVE)
optinfo.update(Bcfg2.Options.CRYPT_OPTIONS)
optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
setup = Bcfg2.Options.OptionParser(optinfo)
@@ -321,18 +351,33 @@ def main():
if not setup['args']:
print(setup.hm)
raise SystemExit(1)
- elif setup['encrypt'] and (setup['decrypt_stdout'] or setup['decrypt']):
- print("You cannot specify both --encrypt and --decrypt or --stdout")
- raise SystemExit(1)
- elif setup['cfg'] and setup['properties']:
- print("You cannot specify both --cfg and --properties")
- raise SystemExit(1)
- elif setup['cfg'] and setup['properties']:
- print("Specifying --xpath with --cfg is nonsensical, ignoring --xpath")
- setup['xpath'] = Bcfg2.Options.CRYPT_XPATH.default
- elif setup['decrypt'] and setup['remove']:
- print("--remove cannot be used with --decrypt, ignoring")
- setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default
+
+ if setup['decrypt']:
+ if setup['encrypt']:
+ print("You cannot specify both --encrypt and --decrypt")
+ raise SystemExit(1)
+ elif setup['remove']:
+ print("--remove cannot be used with --decrypt, ignoring")
+ setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default
+ elif setup['interactive']:
+ print("Cannot decrypt interactively")
+ setup['interactive'] = False
+
+ if setup['cfg']:
+ if setup['properties']:
+ print("You cannot specify both --cfg and --properties")
+ raise SystemExit(1)
+ if setup['xpath']:
+ print("Specifying --xpath with --cfg is nonsensical, ignoring "
+ "--xpath")
+ setup['xpath'] = Bcfg2.Options.CRYPT_XPATH.default
+ if setup['interactive']:
+ print("You cannot use interactive mode with --cfg, ignoring -I")
+ setup['interactive'] = False
+ elif setup['properties']:
+ if setup['remove']:
+ print("--remove cannot be used with --properties, ignoring")
+ setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default
logger = get_logger(setup['verbose'])
@@ -369,32 +414,49 @@ def main():
if props:
encryptor = props_crypt
+ if setup['remove']:
+ logger.info("Cannot use --remove with Properties file %s, "
+ "ignoring for this file" % fname)
else:
+ if setup['xpath']:
+ logger.info("Cannot use xpath with Cfg file %s, ignoring xpath "
+ "for this file" % fname)
+ if setup['interactive']:
+ logger.info("Cannot use interactive mode with Cfg file %s, "
+ "ignoring -I for this file" % fname)
encryptor = cfg_crypt
+ data = None
if setup['encrypt']:
- if not encryptor.encrypt(fname):
- print("Failed to encrypt %s, skipping" % fname)
- elif setup['decrypt'] or setup['decrypt_stdout']:
- data = encryptor.decrypt(fname)
- if not data:
- print("Failed to decrypt %s, skipping" % fname)
- continue
- if setup['decrypt_stdout']:
- if len(setup['args']) > 1:
- print("----- %s -----" % fname)
- print(data)
- if len(setup['args']) > 1:
- print("")
- else:
- encryptor.write_decrypted(fname, data=data)
+ xform = encryptor.encrypt
+ write = encryptor.write_encrypted
+ elif setup['decrypt']:
+ xform = encryptor.decrypt
+ write = encryptor.write_decrypted
else:
logger.info("Neither --encrypt nor --decrypt specified, "
"determining mode")
- if not encryptor.decrypt(fname):
+ data = encryptor.decrypt(fname)
+ if data:
+ write = encryptor.write_decrypted
+ else:
logger.info("Failed to decrypt %s, trying encryption" % fname)
- if not encryptor.encrypt(fname):
- print("Failed to encrypt %s, skipping" % fname)
+ data = None
+ xform = encryptor.encrypt
+ write = encryptor.write_encrypted
+
+ if data is None:
+ data = xform(fname)
+ if not data:
+ print("Failed to %s %s, skipping" % (xform.__name__, fname))
+ if setup['crypt_stdout']:
+ if len(setup['args']) > 1:
+ print("----- %s -----" % fname)
+ print(data)
+ if len(setup['args']) > 1:
+ print("")
+ else:
+ write(fname, data=data)
if setup['remove'] and encryptor.get_encrypted_filename(fname) != fname:
try:
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index 9ee02fd71..95a18b608 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -42,6 +42,7 @@ buildall <directory> [<hostnames*>] - Build configs for all clients in directory
buildallfile <directory> <filename> [<hostnames*>] - Build config file for all clients in directory
buildfile <filename> <hostname> - Build config file for hostname (not written to disk)
buildbundle <bundle> <hostname> - Render a templated bundle for hostname (not written to disk)
+automatch <propertyfile> <hostname> - Perform automatch on a Properties file
bundles - Print out group/bundle information
clients - Print out client/profile information
config - Print out the configuration of the Bcfg2 server
@@ -427,6 +428,43 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore):
else:
print('Usage: buildbundle filename hostname')
+ def do_automatch(self, args):
+ alist = args.split()
+ force = False
+ for arg in alist:
+ if arg == '-f':
+ alist.remove('-f')
+ force = True
+ if len(alist) != 2:
+ print("Usage: automatch [-f] <propertiesfile> <hostname>")
+ return
+
+ if 'Properties' not in self.plugins:
+ print("Properties plugin not enabled")
+ return
+
+ pname, client = alist
+ try:
+ automatch = self.setup.cfp.getboolean("properties",
+ "automatch",
+ default=False)
+
+ pfile = self.plugins['Properties'].store.entries[pname]
+ if (not force and
+ not automatch and
+ pfile.xdata.get("automatch", "false").lower() != "true"):
+ print("Automatch not enabled on %s" % pname)
+ else:
+ metadata = self.build_metadata(client)
+ print(lxml.etree.tostring(pfile.XMLMatch(metadata),
+ xml_declaration=False,
+ pretty_print=True).decode('UTF-8'))
+ except:
+ err = sys.exc_info()[1]
+ print("Failed to automatch %s for host %s: %s" % (pname,
+ client,
+ err))
+
def do_bundles(self, _):
"""Print out group/bundle info."""
data = [('Group', 'Bundles')]