summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Client/Tools/POSIX.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Client/Tools/POSIX.py')
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX.py1239
1 files changed, 0 insertions, 1239 deletions
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX.py b/src/lib/Bcfg2/Client/Tools/POSIX.py
deleted file mode 100644
index 64ea1b3e8..000000000
--- a/src/lib/Bcfg2/Client/Tools/POSIX.py
+++ /dev/null
@@ -1,1239 +0,0 @@
-"""All POSIX Type client support for Bcfg2."""
-
-import binascii
-from datetime import datetime
-import difflib
-import errno
-import grp
-import logging
-import os
-import pwd
-import shutil
-import stat
-import sys
-import time
-# py3k compatibility
-if sys.hexversion >= 0x03000000:
- unicode = str
-
-import Bcfg2.Client.Tools
-import Bcfg2.Options
-from Bcfg2.Client import XML
-
-log = logging.getLogger(__name__)
-
-try:
- import selinux
- has_selinux = True
-except ImportError:
- has_selinux = False
-
-try:
- import posix1e
- has_acls = True
-except ImportError:
- has_acls = False
-
-
-# map between dev_type attribute and stat constants
-device_map = {'block': stat.S_IFBLK,
- 'char': stat.S_IFCHR,
- 'fifo': stat.S_IFIFO}
-
-# map between permissions characters and numeric ACL constants
-acl_map = dict(r=posix1e.ACL_READ,
- w=posix1e.ACL_WRITE,
- x=posix1e.ACL_EXECUTE)
-
-
-class POSIX(Bcfg2.Client.Tools.Tool):
- """POSIX File support code."""
- name = 'POSIX'
- __handles__ = [('Path', 'device'),
- ('Path', 'directory'),
- ('Path', 'file'),
- ('Path', 'hardlink'),
- ('Path', 'nonexistent'),
- ('Path', 'permissions'),
- ('Path', 'symlink')]
- __req__ = dict(Path=dict(
- device=['name', 'dev_type', 'perms', 'owner', 'group'],
- directory=['name', 'perms', 'owner', 'group'],
- file=['name', 'perms', 'owner', 'group'],
- hardlink=['name', 'to'],
- nonexistent=['name'],
- permissions=['name', 'perms', 'owner', 'group'],
- symlink=['name', 'to']))
-
- # grab paranoid options from /etc/bcfg2.conf
- opts = {'ppath': Bcfg2.Options.PARANOID_PATH,
- 'max_copies': Bcfg2.Options.PARANOID_MAX_COPIES}
- setup = Bcfg2.Options.OptionParser(opts)
- setup.parse([])
- ppath = setup['ppath']
- max_copies = setup['max_copies']
-
- def canInstall(self, entry):
- """Check if entry is complete for installation."""
- if Bcfg2.Client.Tools.Tool.canInstall(self, entry):
- if (entry.get('type') == 'file' and
- entry.text is None and
- entry.get('empty', 'false') == 'false'):
- return False
- return True
- else:
- return False
-
- def gatherCurrentData(self, entry):
- if entry.tag == 'Path' and entry.get('type') == 'file':
- try:
- ondisk = os.stat(entry.get('name'))
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.debug("%s %s does not exist" %
- (entry.tag, entry.get('name')))
- return False
- try:
- entry.set('current_owner', str(ondisk[stat.ST_UID]))
- entry.set('current_group', str(ondisk[stat.ST_GID]))
- except (OSError, KeyError):
- pass
-
- if has_selinux:
- try:
- entry.set('current_secontext',
- selinux.getfilecon(entry.get('name'))[1])
- except (OSError, KeyError):
- pass
- entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
-
- def _set_perms(self, entry, path=None):
- if path is None:
- path = entry.get("name")
-
- if (entry.get('perms') == None or
- entry.get('owner') == None or
- entry.get('group') == None):
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % entry.get('name'))
- return False
-
- rv = True
- # split this into multiple try...except blocks so that even if a
- # chown fails, the chmod can succeed -- get as close to the
- # desired state as we can
- try:
- self.logger.debug("Setting ownership of %s to %s:%s" %
- (path,
- self._norm_entry_uid(entry),
- self._norm_entry_gid(entry)))
- os.chown(path, self._norm_entry_uid(entry),
- self._norm_entry_gid(entry))
- except KeyError:
- self.logger.error('Failed to change ownership of %s' % path)
- rv = False
- os.chown(path, 0, 0)
- except OSError:
- self.logger.error('Failed to change ownership of %s' % path)
- rv = False
-
- configPerms = int(entry.get('perms'), 8)
- if entry.get('dev_type'):
- configPerms |= device_map[entry.get('dev_type')]
- try:
- self.logger.debug("Setting permissions on %s to %s" %
- (path, oct(configPerms)))
- os.chmod(path, configPerms)
- except (OSError, KeyError):
- self.logger.error('Failed to change permissions mode of %s' % path)
- rv = False
-
- recursive = entry.get("recursive", "false").lower() == "true"
- return (self._set_secontext(entry, path=path, recursive=recursive) and
- self._set_acls(entry, path=path, recursive=recursive) and
- rv)
-
- def _set_acls(self, entry, path=None, recursive=True):
- """ set POSIX ACLs on the file on disk according to the config """
- if not has_acls:
- if entry.findall("ACL"):
- self.logger.debug("ACLs listed for %s but no pylibacl library "
- "installed" % entry.get('name'))
- return True
-
- if path is None:
- path = entry.get("name")
-
- acl = posix1e.ACL(file=path)
- # clear ACLs out so we start fresh -- way easier than trying
- # to add/remove/modify ACLs
- for aclentry in acl:
- if aclentry.tag_type in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
- acl.delete_entry(aclentry)
- if os.path.isdir(path):
- defacl = posix1e.ACL(filedef=path)
- if not defacl.valid():
- # when a default ACL is queried on a directory that
- # has no default ACL entries at all, you get an empty
- # ACL, which is not valid. in this circumstance, we
- # just copy the access ACL to get a base valid ACL
- # that we can add things to.
- defacl = posix1e.ACL(acl=acl)
- else:
- for aclentry in defacl:
- if aclentry.tag_type in [posix1e.ACL_USER,
- posix1e.ACL_GROUP]:
- defacl.delete_entry(aclentry)
- else:
- defacl = None
-
- for aclkey, perms in self._list_entry_acls(entry).items():
- atype, scope, qualifier = aclkey
- if atype == "default":
- if defacl is None:
- self.logger.warning("Cannot set default ACLs on "
- "non-directory %s" % path)
- continue
- entry = posix1e.Entry(defacl)
- else:
- entry = posix1e.Entry(acl)
- for perm in acl_map.values():
- if perm & perms:
- entry.permset.add(perm)
- entry.tag_type = scope
- try:
- if scope == posix1e.ACL_USER:
- scopename = "user"
- entry.qualifier = self._norm_uid(qualifier)
- elif scope == posix1e.ACL_GROUP:
- scopename = "group"
- entry.qualifier = self._norm_gid(qualifier)
- except (OSError, KeyError):
- err = sys.exc_info()[1]
- self.logger.error("Could not resolve %s %s: %s" %
- (scopename, qualifier, err))
- continue
- acl.calc_mask()
-
- def _apply_acl(acl, path, atype=posix1e.ACL_TYPE_ACCESS):
- if atype == posix1e.ACL_TYPE_ACCESS:
- atype_str = "access"
- else:
- atype_str = "default"
- if acl.valid():
- self.logger.debug("Applying %s ACL to %s:" % (atype_str, path))
- for line in str(acl).splitlines():
- self.logger.debug(" " + line)
- try:
- acl.applyto(path, atype)
- return True
- except:
- err = sys.exc_info()[1]
- self.logger.error("Failed to set ACLs on %s: %s" %
- (path, err))
- return False
- else:
- self.logger.warning("%s ACL created for %s was invalid:" %
- (atype_str.title(), path))
- for line in str(acl).splitlines():
- self.logger.warning(" " + line)
- return False
-
- rv = _apply_acl(acl, path)
- if defacl:
- defacl.calc_mask()
- rv &= _apply_acl(defacl, path, posix1e.ACL_TYPE_DEFAULT)
- if recursive:
- for root, dirs, files in os.walk(path):
- for p in dirs + files:
- rv &= _apply_acl(acl, p)
- if defacl:
- rv &= _apply_acl(defacl, p, posix1e.ACL_TYPE_DEFAULT)
- return rv
-
- def _set_secontext(self, entry, path=None, recursive=False):
- """ set the SELinux context of the file on disk according to the
- config"""
- if not has_selinux:
- return True
-
- if path is None:
- path = entry.get("name")
- context = entry.get("secontext")
- if context is None:
- # no context listed
- return True
-
- rv = True
- if context == '__default__':
- try:
- selinux.restorecon(path, recursive=recursive)
- except:
- err = sys.exc_info()[1]
- self.logger.error("Failed to restore SELinux context for %s: %s"
- % (path, err))
- rv = False
- else:
- try:
- rv &= selinux.lsetfilecon(path, context) == 0
- except:
- err = sys.exc_info()[1]
- self.logger.error("Failed to restore SELinux context for %s: %s"
- % (path, err))
- rv = False
-
- if recursive:
- for root, dirs, files in os.walk(path):
- for p in dirs + files:
- try:
- rv &= selinux.lsetfilecon(p, context) == 0
- except:
- err = sys.exc_info()[1]
- self.logger.error("Failed to restore SELinux "
- "context for %s: %s" %
- (path, err))
- rv = False
- return rv
-
- def _secontext_matches(self, entry):
- """ determine if the SELinux context of the file on disk matches
- the desired context """
- if not has_selinux:
- # no selinux libraries
- return True
-
- path = entry.get("path")
- context = entry.get("secontext")
- if context is None:
- # no context listed
- return True
-
- if context == '__default__':
- if selinux.getfilecon(entry.get('name'))[1] == \
- selinux.matchpathcon(entry.get('name'), 0)[1]:
- return True
- else:
- return False
- elif selinux.getfilecon(entry.get('name'))[1] == context:
- return True
- else:
- return False
-
- def _norm_gid(self, gid):
- """ This takes a group name or gid and returns the
- corresponding gid. """
- try:
- return int(gid)
- except ValueError:
- return int(grp.getgrnam(gid)[2])
-
- def _norm_entry_gid(self, entry):
- try:
- return self._norm_gid(entry.get('group'))
- except (OSError, KeyError):
- err = sys.exc_info()[1]
- self.logger.error('GID normalization failed for %s on %s: %s' %
- (entry.get('group'), entry.get('name'), err))
- return False
-
- def _norm_uid(self, uid):
- """ This takes a username or uid and returns the
- corresponding uid. """
- try:
- return int(uid)
- except ValueError:
- return int(pwd.getpwnam(uid)[2])
-
- def _norm_entry_uid(self, entry):
- try:
- return self._norm_uid(entry.get("owner"))
- except (OSError, KeyError):
- err = sys.exc_info()[1]
- self.logger.error('UID normalization failed for %s on %s: %s' %
- (entry.get('owner'), entry.get('name'), err))
- return False
-
- def _norm_acl_perms(self, perms):
- """ takes a representation of an ACL permset and returns a digit
- representing the permissions entailed by it. representations can
- either be a single octal digit, a string of up to three 'r',
- 'w', 'x', or '-' characters, or a posix1e.Permset object"""
- if hasattr(perms, 'test'):
- # Permset object
- return sum([p for p in acl_map.values()
- if perms.test(p)])
-
- try:
- # single octal digit
- return int(perms)
- except ValueError:
- # couldn't be converted to an int; process as a string
- rv = 0
- for char in perms:
- if char == '-':
- continue
- elif char not in acl_map:
- self.logger.error("Unknown permissions character in ACL: %s"
- % char)
- return 0
- else:
- rv |= acl_map[char]
- return rv
-
- def _acl2string(self, aclkey, perms):
- atype, scope, qualifier = aclkey
- acl_str = []
- if atype == 'default':
- acl_str.append(atype)
- if scope == posix1e.ACL_USER:
- acl_str.append("user")
- elif scope == posix1e.ACL_GROUP:
- acl_str.append("group")
- acl_str.append(qualifier)
- acl_str.append(self._acl_perm2string(perms))
- return ":".join(acl_str)
-
- def _acl_perm2string(self, perm):
- rv = []
- for char in 'rwx':
- if acl_map[char] & perm:
- rv.append(char)
- else:
- rv.append('-')
- return ''.join(rv)
-
- def _is_string(self, strng, encoding):
- """ Returns true if the string contains no ASCII control
- characters and can be decoded from the specified encoding. """
- for char in strng:
- if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
- return False
- try:
- strng.decode(encoding)
- return True
- except:
- return False
-
- def Verifydevice(self, entry, _):
- """Verify device entry."""
- if entry.get('dev_type') in ['block', 'char']:
- # check if major/minor are properly specified
- if (entry.get('major') == None or
- entry.get('minor') == None):
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' %
- (entry.get('name')))
- return False
-
- try:
- ondisk = os.stat(path)
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.debug("%s %s does not exist" %
- (entry.tag, path))
- return False
-
- rv = self._verify_metadata(entry)
-
- # attempt to verify device properties as specified in config
- dev_type = entry.get('dev_type')
- if dev_type in ['block', 'char']:
- major = int(entry.get('major'))
- minor = int(entry.get('minor'))
- if major != os.major(ondisk.st_rdev):
- entry.set('current_mtime', mtime)
- msg = ("Major number for device %s is incorrect. "
- "Current major is %s but should be %s" %
- (path, os.major(ondisk.st_rdev), major))
- self.logger.debug(msg)
- entry.set('qtext', entry.get('qtext') + "\n" + msg)
- rv = False
-
- if minor != os.minor(ondisk.st_rdev):
- entry.set('current_mtime', mtime)
- msg = ("Minor number for device %s is incorrect. "
- "Current minor is %s but should be %s" %
- (path, os.minor(ondisk.st_rdev), minor))
- self.logger.debug(msg)
- entry.set('qtext', entry.get('qtext') + "\n" + msg)
- rv = False
-
- return rv
-
- def Installdevice(self, entry):
- """Install device entries."""
- try:
- # check for existing paths and remove them
- os.lstat(entry.get('name'))
- try:
- os.unlink(entry.get('name'))
- exists = False
- except OSError:
- self.logger.info('Failed to unlink %s' %
- entry.get('name'))
- return False
- except OSError:
- exists = False
-
- if not exists:
- try:
- dev_type = entry.get('dev_type')
- mode = device_map[dev_type] | int(entry.get('mode', '0600'), 8)
- if dev_type in ['block', 'char']:
- # check if major/minor are properly specified
- if (entry.get('major') == None or
- entry.get('minor') == None):
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' %
- entry.get('name'))
- return False
- major = int(entry.get('major'))
- minor = int(entry.get('minor'))
- device = os.makedev(major, minor)
- os.mknod(entry.get('name'), mode, device)
- else:
- os.mknod(entry.get('name'), mode)
- return self._set_perms(entry)
- except KeyError:
- self.logger.error('Failed to install %s' % entry.get('name'))
- except OSError:
- self.logger.error('Failed to install %s' % entry.get('name'))
- return False
-
- def Verifydirectory(self, entry, modlist):
- """Verify Path type='directory' entry."""
- pruneTrue = True
- ex_ents = []
- if (entry.get('prune', 'false') == 'true'
- and (entry.tag == 'Path' and entry.get('type') == 'directory')):
- # check for any extra entries when prune='true' attribute is set
- try:
- entries = ['/'.join([entry.get('name'), ent])
- for ent in os.listdir(entry.get('name'))]
- ex_ents = [e for e in entries if e not in modlist]
- if ex_ents:
- pruneTrue = False
- self.logger.info("POSIX: Directory %s contains "
- "extra entries:" % entry.get('name'))
- self.logger.info(ex_ents)
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "Directory %s contains extra entries: " % \
- entry.get('name')
- nqtext += ":".join(ex_ents)
- entry.set('qtext', nqtext)
- [entry.append(XML.Element('Prune', path=x))
- for x in ex_ents]
- except OSError:
- ex_ents = []
- pruneTrue = True
-
- return pruneTrue and self._verify_metadata(entry)
-
- def Installdirectory(self, entry):
- """Install Path type='directory' entry."""
- self.logger.info("Installing directory %s" % entry.get('name'))
- try:
- fmode = os.lstat(entry.get('name'))
- except OSError:
- # stat failed
- exists = False
-
- if not stat.S_ISDIR(fmode[stat.ST_MODE]):
- self.logger.debug("Found a non-directory entry at %s" %
- entry.get('name'))
- try:
- os.unlink(entry.get('name'))
- exists = False
- except OSError:
- self.logger.info("Failed to unlink %s" % entry.get('name'))
- return False
- else:
- self.logger.debug("Found a pre-existing directory at %s" %
- entry.get('name'))
- exists = True
-
- if not exists:
- parent = "/".join(entry.get('name').split('/')[:-1])
- if parent:
- try:
- os.stat(parent)
- except:
- self.logger.debug('Creating parent path for directory %s' %
- entry.get('name'))
- for idx in range(len(parent.split('/')[:-1])):
- current = '/' + '/'.join(parent.split('/')[1:2+idx])
- try:
- sloc = os.stat(current)
- except OSError:
- try:
- os.mkdir(current)
- continue
- except OSError:
- return False
- if not stat.S_ISDIR(sloc[stat.ST_MODE]):
- try:
- os.unlink(current)
- os.mkdir(current)
- except OSError:
- return False
-
- try:
- os.mkdir(entry.get('name'))
- except OSError:
- self.logger.error('Failed to create directory %s' %
- entry.get('name'))
- return False
- if entry.get('prune', 'false') == 'true' and entry.get("qtext"):
- for pent in entry.findall('Prune'):
- pname = pent.get('path')
- ulfailed = False
- if os.path.isdir(pname):
- self.logger.info("Not removing extra directory %s, "
- "please check and remove manually" % pname)
- continue
- try:
- self.logger.debug("Unlinking file %s" % pname)
- os.unlink(pname)
- except OSError:
- self.logger.error("Failed to unlink path %s" % pname)
- ulfailed = True
- if ulfailed:
- return False
- return self.Installpermissions(entry)
-
- def Verifyfile(self, entry, _):
- """Verify Path type='file' entry."""
- # permissions check + content check
- permissionStatus = self._verify_metadata(entry)
- tbin = False
- if entry.text == None and entry.get('empty', 'false') == 'false':
- self.logger.error("Cannot verify incomplete Path type='%s' %s" %
- (entry.get('type'), entry.get('name')))
- return False
- if entry.get('encoding', 'ascii') == 'base64':
- tempdata = binascii.a2b_base64(entry.text)
- tbin = True
- elif entry.get('empty', 'false') == 'true':
- tempdata = ''
- else:
- tempdata = entry.text
- if type(tempdata) == unicode:
- try:
- tempdata = tempdata.encode(self.setup['encoding'])
- except UnicodeEncodeError:
- e = sys.exc_info()[1]
- self.logger.error("Error encoding file %s:\n %s" %
- (entry.get('name'), e))
-
- different = False
- content = None
- if not os.path.exists(entry.get("name")):
- # first, see if the target file exists at all; if not,
- # they're clearly different
- different = True
- content = ""
- else:
- # next, see if the size of the target file is different
- # from the size of the desired content
- try:
- estat = os.stat(entry.get('name'))
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to stat %s: %s" %
- (err.filename, err))
- return False
- if len(tempdata) != estat[stat.ST_SIZE]:
- different = True
- else:
- # finally, read in the target file and compare them
- # directly. comparison could be done with a checksum,
- # which might be faster for big binary files, but
- # slower for everything else
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
- different = content != tempdata
-
- if different:
- if self.setup['interactive']:
- prompt = [entry.get('qtext', '')]
- if not tbin and content is None:
- # it's possible that we figured out the files are
- # different without reading in the local file. if
- # the supplied version of the file is not binary,
- # we now have to read in the local file to figure
- # out if _it_ is binary, and either include that
- # fact or the diff in our prompts for -I
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
- if tbin or not self._is_string(content, self.setup['encoding']):
- # don't compute diffs if the file is binary
- prompt.append('Binary file, no printable diff')
- else:
- diff = self._diff(content, tempdata,
- difflib.unified_diff,
- filename=entry.get("name"))
- if diff:
- udiff = '\n'.join(diff)
- try:
- prompt.append(udiff.decode(self.setup['encoding']))
- except UnicodeDecodeError:
- prompt.append("Binary file, no printable diff")
- else:
- prompt.append("Diff took too long to compute, no "
- "printable diff")
- entry.set("qtext", "\n".join(prompt))
-
- if entry.get('sensitive', 'false').lower() != 'true':
- if content is None:
- # it's possible that we figured out the files are
- # different without reading in the local file. we
- # now have to read in the local file to figure out
- # if _it_ is binary, and either include the whole
- # file or the diff for reports
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
-
- if tbin or not self._is_string(content, self.setup['encoding']):
- # don't compute diffs if the file is binary
- entry.set('current_bfile', binascii.b2a_base64(content))
- else:
- diff = self._diff(content, tempdata, difflib.ndiff,
- filename=entry.get("name"))
- if diff:
- entry.set("current_bdiff",
- binascii.b2a_base64("\n".join(diff)))
- elif not tbin and self._is_string(content,
- self.setup['encoding']):
- entry.set('current_bfile', binascii.b2a_base64(content))
-
- return permissionStatus and not different
-
- def Installfile(self, entry):
- """Install Path type='file' entry."""
- self.logger.info("Installing file %s" % (entry.get('name')))
-
- parent = "/".join(entry.get('name').split('/')[:-1])
- if parent:
- try:
- os.stat(parent)
- except:
- self.logger.debug('Creating parent path for config file %s' %
- entry.get('name'))
- current = '/'
- for next in parent.split('/')[1:]:
- current += next + '/'
- try:
- sloc = os.stat(current)
- try:
- if not stat.S_ISDIR(sloc[stat.ST_MODE]):
- self.logger.debug('%s is not a directory; recreating'
- % current)
- os.unlink(current)
- os.mkdir(current)
- except OSError:
- return False
- except OSError:
- try:
- self.logger.debug("Creating non-existent path %s" %
- current)
- os.mkdir(current)
- except OSError:
- return False
-
- # If we get here, then the parent directory should exist
- if (entry.get("paranoid", 'false').lower() == 'true' and
- self.setup.get("paranoid", False) and
- entry.get('current_exists', 'true') != 'false'):
- bkupnam = entry.get('name').replace('/', '_')
- # current list of backups for this file
- try:
- bkuplist = [f for f in os.listdir(self.ppath) if
- f.startswith(bkupnam)]
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error("Failed to create backup list in %s: %s" %
- (self.ppath, e.strerror))
- return False
- bkuplist.sort()
- while len(bkuplist) >= int(self.max_copies):
- # remove the oldest backup available
- oldest = bkuplist.pop(0)
- self.logger.info("Removing %s" % oldest)
- try:
- os.remove("%s/%s" % (self.ppath, oldest))
- except:
- self.logger.error("Failed to remove %s/%s" %
- (self.ppath, oldest))
- return False
- try:
- # backup existing file
- shutil.copy(entry.get('name'),
- "%s/%s_%s" % (self.ppath, bkupnam,
- datetime.isoformat(datetime.now())))
- self.logger.info("Backup of %s saved to %s" %
- (entry.get('name'), self.ppath))
- except IOError:
- e = sys.exc_info()[1]
- self.logger.error("Failed to create backup file for %s" %
- entry.get('name'))
- self.logger.error(e)
- return False
- try:
- newfile = open("%s.new"%(entry.get('name')), 'w')
- if entry.get('encoding', 'ascii') == 'base64':
- filedata = binascii.a2b_base64(entry.text)
- elif entry.get('empty', 'false') == 'true':
- filedata = ''
- else:
- if type(entry.text) == unicode:
- filedata = entry.text.encode(self.setup['encoding'])
- else:
- filedata = entry.text
- newfile.write(filedata)
- newfile.close()
-
- rv = self._set_perms(entry, newfile.name)
- os.rename(newfile.name, entry.get('name'))
- if entry.get('mtime'):
- try:
- os.utime(entry.get('name'), (int(entry.get('mtime')),
- int(entry.get('mtime'))))
- except:
- self.logger.error("Failed to set mtime of %s" % path)
- rv = False
- return rv
- except (OSError, IOError):
- err = sys.exc_info()[1]
- self.logger.error("Failed to open %s for writing: %s" %
- (entry.get('name'), err))
- return False
-
- def Verifyhardlink(self, entry, _):
- """Verify HardLink entry."""
- rv = True
-
- try:
- if not os.path.samefile(entry.get('name'), entry.get('to')):
- msg = "Hardlink %s is incorrect." % entry.get('name')
- self.logger.debug(msg)
- entry.set('qtext', "\n".join([entry.get('qtext', ''), msg]))
- rv = False
- except OSError:
- entry.set('current_exists', 'false')
- return False
-
- rv &= self._verify_secontext(entry)
- return rv
-
- def Installhardlink(self, entry):
- """Install HardLink entry."""
- self.logger.info("Installing Hardlink %s" % entry.get('name'))
- if os.path.lexists(entry.get('name')):
- try:
- fmode = os.lstat(entry.get('name'))[stat.ST_MODE]
- if stat.S_ISREG(fmode) or stat.S_ISLNK(fmode):
- self.logger.debug("Non-directory entry already exists at "
- "%s. Unlinking entry." %
- entry.get('name'))
- os.unlink(entry.get('name'))
- elif stat.S_ISDIR(fmode):
- self.logger.debug("Directory already exists at %s" %
- entry.get('name'))
- self.cmd.run("mv %s/ %s.bak" % (entry.get('name'),
- entry.get('name')))
- else:
- os.unlink(entry.get('name'))
- except OSError:
- self.logger.info("Hardlink %s cleanup failed" % \
- (entry.get('name')))
- try:
- os.link(entry.get('to'), entry.get('name'))
- return self._set_perms(entry)
- except OSError:
- return False
-
- def Verifynonexistent(self, entry, _):
- """Verify nonexistent entry."""
- # return true if path does _not_ exist
- return not os.path.lexists(entry.get('name'))
-
- def Installnonexistent(self, entry):
- '''Remove nonexistent entries'''
- ename = entry.get('name')
- if entry.get('recursive').lower() == 'true':
- # ensure that configuration spec is consistent first
- if [e for e in self.buildModlist() \
- if e.startswith(ename) and e != ename]:
- self.logger.error('Not installing %s. One or more files '
- 'in this directory are specified in '
- 'your configuration.' % ename)
- return False
- try:
- shutil.rmtree(ename)
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- else:
- if os.path.islink(ename):
- os.remove(ename)
- return True
- elif os.path.isdir(ename):
- try:
- os.rmdir(ename)
- return True
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- return False
- try:
- os.remove(ename)
- return True
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- return False
-
- def Verifypermissions(self, entry, _):
- """Verify Path type='permissions' entry"""
- rv = self._verify_metadata(entry)
-
- if entry.get('recursive', 'false').lower() == 'true':
- # verify ownership information recursively
- for root, dirs, files in os.walk(entry.get('name')):
- for p in dirs + files:
- rv &= self._verify_metadata(entry,
- path=os.path.join(root, p))
- return rv
-
- def Installpermissions(self, entry):
- """Install POSIX permissions"""
- plist = [entry.get('name')]
- if entry.get('recursive', 'false').lower() == 'true':
- # verify ownership information recursively
- for root, dirs, files in os.walk(entry.get('name')):
- for p in dirs + files:
- if not self._verify_metadata(entry,
- path=os.path.join(root, p),
- checkonly=True):
- plist.append(path)
- rv = True
- for path in plist:
- rv &= self._set_perms(entry, path)
- return rv
-
- def Verifysymlink(self, entry, _):
- """Verify Path type='symlink' entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' %
- (entry.get('name')))
- return False
-
- rv = True
-
- try:
- sloc = os.readlink(entry.get('name'))
- if sloc != entry.get('to'):
- entry.set('current_to', sloc)
- msg = ("Symlink %s points to %s, should be %s" %
- (entry.get('name'), sloc, entry.get('to')))
- self.logger.debug(msg)
- entry.set('qtext', "\n".join([entry.get('qtext', ''), msg]))
- rv = False
- except OSError:
- entry.set('current_exists', 'false')
- return False
-
- rv &= self._verify_secontext(entry)
- return rv
-
- def Installsymlink(self, entry):
- """Install Path type='symlink' entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % entry.get('name'))
- return False
- self.logger.info("Installing symlink %s" % (entry.get('name')))
- if os.path.lexists(entry.get('name')):
- try:
- fmode = os.lstat(entry.get('name'))[stat.ST_MODE]
- if stat.S_ISREG(fmode) or stat.S_ISLNK(fmode):
- self.logger.debug("Non-directory entry already exists at "
- "%s. Unlinking entry." %
- entry.get('name'))
- os.unlink(entry.get('name'))
- elif stat.S_ISDIR(fmode):
- self.logger.debug("Directory already exists at %s" %
- entry.get('name'))
- self.cmd.run("mv %s/ %s.bak" % (entry.get('name'),
- entry.get('name')))
- else:
- os.unlink(entry.get('name'))
- except OSError:
- self.logger.info("Symlink %s cleanup failed" %
- (entry.get('name')))
- try:
- os.symlink(entry.get('to'), entry.get('name'))
- return self._set_setcontext(entry)
- except OSError:
- return False
-
- def InstallPath(self, entry):
- """Dispatch install to the proper method according to type"""
- ret = getattr(self, 'Install%s' % entry.get('type'))
- return ret(entry)
-
- def VerifyPath(self, entry, _):
- """Dispatch verify to the proper method according to type"""
- ret = getattr(self, 'Verify%s' % entry.get('type'))(entry, _)
- if entry.get('qtext') and self.setup['interactive']:
- entry.set('qtext',
- '%s\nInstall %s %s: (y/N) ' %
- (entry.get('qtext'),
- entry.get('type'), entry.get('name')))
- return ret
-
- def _verify_metadata(self, entry, path=None, checkonly=False):
- """ generic method to verify perms, owner, group, secontext,
- and mtime """
-
- # allow setting an alternate path for recursive permissions checking
- if path is None:
- path = entry.get('name')
-
- while len(entry.get('perms', '')) < 4:
- entry.set('perms', '0' + entry.get('perms', ''))
-
- try:
- ondisk = os.stat(path)
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.debug("POSIX: %s %s does not exist" %
- (entry.tag, path))
- return False
-
- try:
- owner = str(ondisk[stat.ST_UID])
- group = str(ondisk[stat.ST_GID])
- except (OSError, KeyError):
- self.logger.error('POSIX: User/Group resolution failed for path %s'
- % path)
- owner = 'root'
- group = '0'
-
- perms = oct(ondisk[stat.ST_MODE])[-4:]
- if entry.get('mtime', '-1') != '-1':
- mtime = str(ondisk[stat.ST_MTIME])
- else:
- mtime = '-1'
-
- configOwner = str(self._norm_entry_uid(entry))
- configGroup = str(self._norm_entry_gid(entry))
- configPerms = int(entry.get('perms'), 8)
- if entry.get('dev_type'):
- configPerms |= device_map[entry.get('dev_type')]
- if has_selinux:
- if entry.get("secontext") == "__default__":
- try:
- configContext = selinux.matchpathcon(path, 0)[1]
- except OSError:
- self.logger.warning("Failed to get default SELinux context "
- "for %s; missing fcontext rule?" %
- path)
- return False
- else:
- configContext = entry.get("secontext")
-
- errors = []
- if owner != configOwner:
- if checkonly:
- return False
- entry.set('current_owner', owner)
- errors.append("POSIX: Owner for path %s is incorrect. "
- "Current owner is %s but should be %s" %
- (path, ondisk.st_uid, entry.get('owner')))
-
- if group != configGroup:
- if checkonly:
- return False
- entry.set('current_group', group)
- errors.append("POSIX: Group for path %s is incorrect. "
- "Current group is %s but should be %s" %
- (path, ondisk.st_gid, entry.get('group')))
-
- if oct(int(perms, 8)) != oct(configPerms):
- if checkonly:
- return False
- entry.set('current_perms', perms)
- errors.append("POSIX: Permissions for path %s are incorrect. "
- "Current permissions are %s but should be %s" %
- (path, perms, entry.get('perms')))
-
- if entry.get('mtime') and mtime != entry.get('mtime', '-1'):
- if checkonly:
- return False
- entry.set('current_mtime', mtime)
- errors.append("POSIX: mtime for path %s is incorrect. "
- "Current mtime is %s but should be %s" %
- (path, mtime, entry.get('mtime')))
-
- seVerifies = self._verify_secontext(entry)
- aclVerifies = self._verify_acls(entry)
-
- if errors:
- for error in errors:
- self.logger.debug(error)
- entry.set('qtext', "\n".join([entry.get('qtext', '')] + errors))
- return False
- else:
- return seVerifies and aclVerifies
-
- def _list_entry_acls(self, entry):
- wanted = dict()
- for acl in entry.findall("ACL"):
- if acl.get("scope") == "user":
- scope = posix1e.ACL_USER
- elif acl.get("scope") == "group":
- scope = posix1e.ACL_GROUP
- else:
- self.logger.error("Unknown ACL scope %s" % acl.get("scope"))
- continue
- wanted[(acl.get("type"), scope, acl.get(acl.get("scope")))] = \
- self._norm_acl_perms(acl.get('perms'))
- return wanted
-
- def _list_file_acls(self, entry):
- def _process_acl(acl, atype):
- try:
- if acl.tag_type == posix1e.ACL_USER:
- qual = pwd.getpwuid(acl.qualifier)[0]
- elif acl.tag_type == posix1e.ACL_GROUP:
- qual = grp.getgrgid(acl.qualifier)[0]
- else:
- return
- except (OSError, KeyError):
- err = sys.exc_info()[1]
- self.logger.error("Lookup of %s %s failed: %s" %
- (scope, acl.qualifier, err))
- qual = acl.qualifier
- existing[(atype, acl.tag_type, qual)] = \
- self._norm_acl_perms(acl.permset)
-
- existing = dict()
- for acl in posix1e.ACL(file=entry.get("name")):
- _process_acl(acl, "access")
- if os.path.isdir(entry.get("name")):
- for acl in posix1e.ACL(filedef=entry.get("name")):
- _process_acl(acl, "default")
- return existing
-
- def _verify_acls(self, entry):
- if not has_acls:
- if entry.findall("ACL"):
- self.logger.debug("ACLs listed for %s but no pylibacl library "
- "installed" % entry.get('name'))
- return True
-
- # create lists of normalized representations of the ACLs we want
- # and the ACLs we have. this will make them easier to compare
- # than trying to mine that data out of the ACL objects and XML
- # objects and compare it at the same time.
- wanted = self._list_entry_acls(entry)
- existing = self._list_file_acls(entry)
-
- missing = []
- extra = []
- wrong = []
- for aclkey, perms in wanted.items():
- acl_str = self._acl2string(aclkey, perms)
- if aclkey not in existing:
- missing.append(acl_str)
- elif existing[aclkey] != perms:
- wrong.append((acl_str,
- self._acl2string(aclkey, existing[aclkey])))
-
- for aclkey, perms in existing.items():
- if aclkey not in wanted:
- extra.append(self._acl2string(aclkey, perms))
-
- msg = []
- if missing:
- msg.append("%s ACLs are missing: %s" % (len(missing),
- ", ".join(missing)))
- if wrong:
- msg.append("%s ACLs are wrong: %s" %
- (len(wrong),
- "; ".join(["%s should be %s" % (e, w)
- for w, e in wrong])))
- if extra:
- msg.append("%s extra ACLs: %s" % (len(extra), ", ".join(extra)))
-
- if msg:
- msg.insert(0,
- "POSIX ACLs for path %s are incorrect." %
- entry.get("name"))
- self.logger.debug(msg[0])
- for line in msg[1:]:
- self.logger.debug(" " + line)
- entry.set('qtext', "\n".join([entry.get("qtext", '')] + msg))
- return False
- return True
-
- def _verify_secontext(self, entry):
- if not self._secontext_matches(entry):
- path = entry.get("name")
- if entry.get("secontext") == "__default__":
- configContext = selinux.matchpathcon(path, 0)[1]
- else:
- configContext = entry.get("secontext")
- pcontext = selinux.getfilecon(path)[1]
- entry.set('current_secontext', pcontext)
- msg = ("SELinux context for path %s is incorrect. "
- "Current context is %s but should be %s" %
- (path, pcontext, configContext))
- self.logger.debug("POSIX: " + msg)
- entry.set('qtext', "\n".join([entry.get("qtext", ''), msg]))
- return False
- return True
-
- def _diff(self, content1, content2, difffunc, filename=None):
- rv = []
- start = time.time()
- longtime = False
- for diffline in difffunc(content1.split('\n'),
- content2.split('\n')):
- now = time.time()
- rv.append(diffline)
- if now - start > 5 and not longtime:
- if filename:
- self.logger.info("Diff of %s taking a long time" %
- filename)
- else:
- self.logger.info("Diff taking a long time")
- longtime = True
- elif now - start > 30:
- if filename:
- self.logger.error("Diff of %s took too long; giving up" %
- filename)
- else:
- self.logger.error("Diff took too long; giving up")
- return False
- return rv