From 41d1d29b8b545e0f636ebf26795eecd1a46bc9fb Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Mon, 3 Dec 2012 08:45:48 -0600 Subject: SELinux: Split up selinux entries This commit splits up the all-in-one SELinux tag into various entries (formerly done using a type attribute). This helps prevent ambiguation when entries of different SELinux types have the same name. Note that there is still some ambiguation for File Context entries since there can be duplicates involved. Signed-off-by: Sol Jerome --- src/lib/Bcfg2/Client/Tools/SELinux.py | 117 ++++++++++++++++-------------- src/lib/Bcfg2/Server/Plugins/SEModules.py | 6 +- 2 files changed, 64 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py index fc47883c9..7aa0e8a20 100644 --- a/src/lib/Bcfg2/Client/Tools/SELinux.py +++ b/src/lib/Bcfg2/Client/Tools/SELinux.py @@ -58,36 +58,44 @@ def netmask_itoa(netmask, proto="ipv4"): class SELinux(Bcfg2.Client.Tools.Tool): """ SELinux entry support """ name = 'SELinux' - __handles__ = [('SELinux', 'boolean'), - ('SELinux', 'port'), - ('SELinux', 'fcontext'), - ('SELinux', 'node'), - ('SELinux', 'login'), - ('SELinux', 'user'), - ('SELinux', 'interface'), - ('SELinux', 'permissive'), - ('SELinux', 'module')] - __req__ = dict(SELinux=dict(boolean=['name', 'value'], - module=['name'], - port=['name', 'selinuxtype'], - fcontext=['name', 'selinuxtype'], - node=['name', 'selinuxtype', 'proto'], - login=['name', 'selinuxuser'], - user=['name', 'roles', 'prefix'], - interface=['name', 'selinuxtype'], - permissive=['name'])) + __handles__ = [('SEBoolean', None), + ('SEFcontext', None), + ('SEInterface', None), + ('SELogin', None), + ('SEModule', None), + ('SENode', None), + ('SEPermissive', None), + ('SEPort', None), + ('SEUser', None)] + __req__ = dict(SEBoolean=['name', 'value'], + SEFcontext=['name', 'selinuxtype'], + SEInterface=['name', 'selinuxtype'], + SELogin=['name', 'selinuxuser'], + SEModule=['name'], + SENode=['name', 'selinuxtype', 'proto'], + SEPermissive=['name'], + SEPort=['name', 'selinuxtype'], + SEUser=['name', 'roles', 'prefix']) def __init__(self, logger, setup, config): Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) self.handlers = {} - for handles in self.__handles__: - etype = handles[1] + for handler in self.__handles__: + etype = handler[0] self.handlers[etype] = \ globals()["SELinux%sHandler" % etype.title()](self, logger, setup, config) self.txn = False self.post_txn_queue = [] + def __getattr__(self, attr): + if attr.startswith("VerifySE"): + return self.GenericSEVerify + elif attr.startswith("InstallSE"): + return self.GenericSEInstall + else: + return object.__getattr__(self, attr) + def BundleUpdated(self, _, states): for handler in self.handlers.values(): handler.BundleUpdated(states) @@ -100,12 +108,12 @@ class SELinux(Bcfg2.Client.Tools.Tool): def canInstall(self, entry): return (Bcfg2.Client.Tools.Tool.canInstall(self, entry) and - self.handlers[entry.get('type')].canInstall(entry)) + self.handlers[entry.tag].canInstall(entry)) def primarykey(self, entry): """ return a string that should be unique amongst all entries in the specification """ - return self.handlers[entry.get('type')].primarykey(entry) + return self.handlers[entry.tag].primarykey(entry) def Install(self, entries, states): # start a transaction @@ -125,32 +133,32 @@ class SELinux(Bcfg2.Client.Tools.Tool): for func, arg, kwargs in self.post_txn_queue: states[arg] = func(*arg, **kwargs) - def InstallSELinux(self, entry): - """Dispatch install to the proper method according to type""" - return self.handlers[entry.get('type')].Install(entry) + def GenericSEInstall(self, entry): + """Dispatch install to the proper method according to entry tag""" + return self.handlers[entry.tag].Install(entry) - def VerifySELinux(self, entry, _): - """Dispatch verify to the proper method according to type""" - rv = self.handlers[entry.get('type')].Verify(entry) + def GenericSEVerify(self, entry, _): + """Dispatch verify to the proper method according to entry tag""" + rv = self.handlers[entry.tag].Verify(entry) if entry.get('qtext') and self.setup['interactive']: entry.set('qtext', - '%s\nInstall SELinux %s %s: (y/N) ' % + '%s\nInstall %s: (y/N) ' % (entry.get('qtext'), - entry.get('type'), - self.handlers[entry.get('type')].tostring(entry))) + self.handlers[entry.tag].tostring(entry))) return rv def Remove(self, entries): - """Dispatch verify to the proper removal method according to type""" + """Dispatch verify to the proper removal + method according to entry tag""" # sort by type types = list() for entry in entries: - if entry.get('type') not in types: - types.append(entry.get('type')) + if entry.tag not in types: + types.append(entry.tag) for etype in types: self.handlers[etype].Remove([e for e in entries - if e.get('type') == etype]) + if e.tag == etype]) class SELinuxEntryHandler(object): @@ -253,8 +261,7 @@ class SELinuxEntryHandler(object): def key2entry(self, key): """ Generate an XML entry from an SELinux record key """ attrs = self._key2attrs(key) - attrs["type"] = self.etype - return Bcfg2.Client.XML.Element("SELinux", **attrs) + return Bcfg2.Client.XML.Element(self.etype, **attrs) def _args(self, entry, method): """ Get the argument list for invoking _modify or _add, or @@ -279,7 +286,7 @@ class SELinuxEntryHandler(object): """ return a string that should be unique amongst all entries in the specification. some entry types are not universally disambiguated by tag:type:name alone """ - return ":".join([entry.tag, entry.get("type"), entry.get("name")]) + return ":".join([entry.tag, entry.get("name")]) def exists(self, entry): """ return True if the entry already exists in the record list """ @@ -303,8 +310,8 @@ class SELinuxEntryHandler(object): continue if current_attrs[attr] != desired_attrs[attr]: entry.set('current_%s' % attr, current_attrs[attr]) - errors.append("SELinux %s %s has wrong %s: %s, should be %s" % - (self.etype, self.tostring(entry), attr, + errors.append("%s %s has wrong %s: %s, should be %s" % + (entry.tag, entry.get('name'), attr, current_attrs[attr], desired_attrs[attr])) if errors: @@ -331,8 +338,8 @@ class SELinuxEntryHandler(object): return True except ValueError: err = sys.exc_info()[1] - self.logger.debug("Failed to %s SELinux %s %s: %s" % - (method, self.etype, self.tostring(entry), err)) + self.logger.info("Failed to %s SELinux %s %s: %s" % + (method, self.etype, self.tostring(entry), err)) return False def Remove(self, entries): @@ -365,7 +372,7 @@ class SELinuxEntryHandler(object): pass -class SELinuxBooleanHandler(SELinuxEntryHandler): +class SELinuxSebooleanHandler(SELinuxEntryHandler): """ handle SELinux boolean entries """ etype = "boolean" value_format = ("value",) @@ -414,7 +421,7 @@ class SELinuxBooleanHandler(SELinuxEntryHandler): SELinuxEntryHandler.canInstall(self, entry)) -class SELinuxPortHandler(SELinuxEntryHandler): +class SELinuxSeportHandler(SELinuxEntryHandler): """ handle SELinux port entries """ etype = "port" value_format = ('selinuxtype', None) @@ -486,7 +493,7 @@ class SELinuxPortHandler(SELinuxEntryHandler): return tuple(entry.get("name").split("/")) -class SELinuxFcontextHandler(SELinuxEntryHandler): +class SELinuxSefcontextHandler(SELinuxEntryHandler): """ handle SELinux file context entries """ etype = "fcontext" @@ -556,11 +563,11 @@ class SELinuxFcontextHandler(SELinuxEntryHandler): '', '') def primarykey(self, entry): - return ":".join([entry.tag, entry.get("type"), entry.get("name"), + return ":".join([entry.tag, entry.get("name"), entry.get("filetype", "all")]) -class SELinuxNodeHandler(SELinuxEntryHandler): +class SELinuxSenodeHandler(SELinuxEntryHandler): """ handle SELinux node entries """ etype = "node" @@ -592,7 +599,7 @@ class SELinuxNodeHandler(SELinuxEntryHandler): entry.get("selinuxtype")) -class SELinuxLoginHandler(SELinuxEntryHandler): +class SELinuxSeloginHandler(SELinuxEntryHandler): """ handle SELinux login entries """ etype = "login" @@ -603,7 +610,7 @@ class SELinuxLoginHandler(SELinuxEntryHandler): return (entry.get("name"), entry.get("selinuxuser"), "") -class SELinuxUserHandler(SELinuxEntryHandler): +class SELinuxSeuserHandler(SELinuxEntryHandler): """ handle SELinux user entries """ etype = "user" @@ -652,7 +659,7 @@ class SELinuxUserHandler(SELinuxEntryHandler): return tuple(rv) -class SELinuxInterfaceHandler(SELinuxEntryHandler): +class SELinuxSeinterfaceHandler(SELinuxEntryHandler): """ handle SELinux interface entries """ etype = "interface" @@ -663,7 +670,7 @@ class SELinuxInterfaceHandler(SELinuxEntryHandler): return (entry.get("name"), '', entry.get("selinuxtype")) -class SELinuxPermissiveHandler(SELinuxEntryHandler): +class SELinuxSepermissiveHandler(SELinuxEntryHandler): """ handle SELinux permissive domain entries """ etype = "permissive" @@ -695,7 +702,7 @@ class SELinuxPermissiveHandler(SELinuxEntryHandler): return (entry.get("name"),) -class SELinuxModuleHandler(SELinuxEntryHandler): +class SELinuxSemoduleHandler(SELinuxEntryHandler): """ handle SELinux module entries """ etype = "module" @@ -808,10 +815,9 @@ class SELinuxModuleHandler(SELinuxEntryHandler): def Install(self, entry, _=None): if not self.filetool.install(self._pathentry(entry)): return False - if hasattr(self, 'records'): + if hasattr(seobject, 'moduleRecords'): # if seobject has the moduleRecords attribute, install the # module using the seobject library - self.records # pylint: disable=W0104 return self._install_seobject(entry) else: # seobject doesn't have the moduleRecords attribute, so @@ -891,8 +897,7 @@ class SELinuxModuleHandler(SELinuxEntryHandler): def FindExtra(self): specified = [self._key(e) - for e in self.tool.getSupportedEntries() - if e.get("type") == self.etype] + for e in self.tool.getSupportedEntries()] rv = [] for module in self._all_records_from_filesystem().keys(): if module not in specified: diff --git a/src/lib/Bcfg2/Server/Plugins/SEModules.py b/src/lib/Bcfg2/Server/Plugins/SEModules.py index 3edfb72a3..fa47f9496 100644 --- a/src/lib/Bcfg2/Server/Plugins/SEModules.py +++ b/src/lib/Bcfg2/Server/Plugins/SEModules.py @@ -40,8 +40,8 @@ class SEModules(Bcfg2.Server.Plugin.GroupSpool): #: objects as its EntrySet children. es_child_cls = SEModuleData - #: SEModules manages ``SELinux`` entries - entry_type = 'SELinux' + #: SEModules manages ``SEModule`` entries + entry_type = 'SEModule' #: The SEModules plugin is experimental experimental = True @@ -68,7 +68,7 @@ class SEModules(Bcfg2.Server.Plugin.GroupSpool): return name.lstrip("/") def HandlesEntry(self, entry, metadata): - if entry.tag in self.Entries and entry.get('type') == 'module': + if entry.tag in self.Entries: return self._get_module_filename(entry) in self.Entries[entry.tag] return Bcfg2.Server.Plugin.GroupSpool.HandlesEntry(self, entry, metadata) -- cgit v1.2.3-1-g7c22 From 30a50cfe1959fa33cb2561ba96106e5eaa4f8ac9 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 30 Nov 2012 08:11:38 -0500 Subject: POSIX: Fixed handling of empty files that don't exist on client in interactive mode --- src/lib/Bcfg2/Client/Tools/POSIX/File.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/File.py b/src/lib/Bcfg2/Client/Tools/POSIX/File.py index 5842c4e1f..9b95d2234 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/File.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/File.py @@ -188,6 +188,10 @@ class POSIXFile(POSIXTool): prompt.append(udiff) except UnicodeEncodeError: prompt.append("Could not encode diff") + elif entry.get("empty", "true"): + # the file doesn't exist on disk, but there's no + # expected content + prompt.append("%s does not exist" % entry.get("name")) else: prompt.append("Diff took too long to compute, no " "printable diff") -- cgit v1.2.3-1-g7c22 From 33234d5dae565e6520bbdb65d67fbaed03df4d43 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 3 Dec 2012 10:51:34 -0600 Subject: added builtin support for creating users and groups --- src/lib/Bcfg2/Client/Client.py | 4 +- src/lib/Bcfg2/Client/Frame.py | 11 +- src/lib/Bcfg2/Client/Tools/POSIXUsers.py | 300 +++++++++++++++++++++++++++++++ src/lib/Bcfg2/Client/Tools/__init__.py | 1 + 4 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 src/lib/Bcfg2/Client/Tools/POSIXUsers.py (limited to 'src') diff --git a/src/lib/Bcfg2/Client/Client.py b/src/lib/Bcfg2/Client/Client.py index f197a9074..45e0b64e6 100644 --- a/src/lib/Bcfg2/Client/Client.py +++ b/src/lib/Bcfg2/Client/Client.py @@ -56,8 +56,8 @@ class Client(object): self.logger.error("Service removal is nonsensical; " "removed services will only be disabled") if (self.setup['remove'] and - self.setup['remove'].lower() not in ['all', 'services', - 'packages']): + self.setup['remove'].lower() not in ['all', 'services', 'packages', + 'users']): self.logger.error("Got unknown argument %s for -r" % self.setup['remove']) if self.setup["file"] and self.setup["cache"]: diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index 53180ab68..4f3ff1820 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -105,6 +105,10 @@ class Frame(object): if deprecated: self.logger.warning("Loaded deprecated tool drivers:") self.logger.warning(deprecated) + experimental = [tool.name for tool in self.tools if tool.experimental] + if experimental: + self.logger.warning("Loaded experimental tool drivers:") + self.logger.warning(experimental) # find entries not handled by any tools self.unhandled = [entry for struct in config @@ -281,12 +285,15 @@ class Frame(object): if self.setup['remove']: if self.setup['remove'] == 'all': self.removal = self.extra - elif self.setup['remove'] in ['services', 'Services']: + elif self.setup['remove'].lower() == 'services': self.removal = [entry for entry in self.extra if entry.tag == 'Service'] - elif self.setup['remove'] in ['packages', 'Packages']: + elif self.setup['remove'].lower() == 'packages': self.removal = [entry for entry in self.extra if entry.tag == 'Package'] + elif self.setup['remove'].lower() == 'users': + self.removal = [entry for entry in self.extra + if entry.tag in ['POSIXUser', 'POSIXGroup']] candidates = [entry for entry in self.states if not self.states[entry]] diff --git a/src/lib/Bcfg2/Client/Tools/POSIXUsers.py b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py new file mode 100644 index 000000000..78734f5c2 --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/POSIXUsers.py @@ -0,0 +1,300 @@ +""" A tool to handle creating users and groups with useradd/mod/del +and groupadd/mod/del """ + +import sys +import pwd +import grp +import Bcfg2.Client.XML +import subprocess +import Bcfg2.Client.Tools + + +class ExecutionError(Exception): + """ Raised when running an external command fails """ + + def __init__(self, msg, retval=None): + Exception.__init__(self, msg) + self.retval = retval + + def __str__(self): + return "%s (rv: %s)" % (Exception.__str__(self), + self.retval) + + +class Executor(object): + """ A better version of Bcfg2.Client.Tool.Executor, which captures + stderr, raises exceptions on error, and doesn't use the shell to + execute by default """ + + def __init__(self, logger): + self.logger = logger + self.stdout = None + self.stderr = None + self.retval = None + + def run(self, command, inputdata=None, shell=False): + """ Run a command, given as a list, optionally giving it the + specified input data """ + self.logger.debug("Running: %s" % " ".join(command)) + proc = subprocess.Popen(command, shell=shell, bufsize=16384, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + if inputdata: + for line in inputdata.splitlines(): + self.logger.debug('> %s' % line) + (self.stdout, self.stderr) = proc.communicate(inputdata) + else: + (self.stdout, self.stderr) = proc.communicate() + for line in self.stdout.splitlines(): # pylint: disable=E1103 + self.logger.debug('< %s' % line) + self.retval = proc.wait() + if self.retval == 0: + for line in self.stderr.splitlines(): # pylint: disable=E1103 + self.logger.warning(line) + return True + else: + raise ExecutionError(self.stderr, self.retval) + + +class POSIXUsers(Bcfg2.Client.Tools.Tool): + """ A tool to handle creating users and groups with + useradd/mod/del and groupadd/mod/del """ + __execs__ = ['/usr/sbin/useradd', '/usr/sbin/usermod', '/usr/sbin/userdel', + '/usr/sbin/groupadd', '/usr/sbin/groupmod', + '/usr/sbin/groupdel'] + __handles__ = [('POSIXUser', None), + ('POSIXGroup', None)] + __req__ = dict(POSIXUser=['name'], + POSIXGroup=['name']) + experimental = True + + # A mapping of XML entry attributes to the indexes of + # corresponding values in the get*ent data structures + attr_mapping = dict(POSIXUser=dict(name=0, uid=2, gecos=4, home=5, + shell=6), + POSIXGroup=dict(name=0, gid=2)) + + def __init__(self, logger, setup, config): + Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + self.set_defaults = dict(POSIXUser=self.populate_user_entry, + POSIXGroup=lambda g: g) + self.cmd = Executor(logger) + self._existing = None + + @property + def existing(self): + """ Get a dict of existing users and groups """ + if self._existing is None: + self._existing = dict(POSIXUser=dict([(u[0], u) + for u in pwd.getpwall()]), + POSIXGroup=dict([(g[0], g) + for g in grp.getgrall()])) + return self._existing + + def Inventory(self, states, structures=None): + if not structures: + structures = self.config.getchildren() + # we calculate a list of all POSIXUser and POSIXGroup entries, + # and then add POSIXGroup entries that are required to create + # the primary group for each user to the structures. this is + # sneaky and possibly evil, but it works great. + groups = [] + for struct in structures: + groups.extend([e.get("name") + for e in struct.findall("POSIXGroup")]) + for struct in structures: + for entry in struct.findall("POSIXUser"): + group = self.set_defaults[entry.tag](entry).get('group') + if group and group not in groups: + self.logger.debug("POSIXUsers: Adding POSIXGroup entry " + "'%s' for user '%s'" % + (group, entry.get("name"))) + struct.append(Bcfg2.Client.XML.Element("POSIXGroup", + name=group)) + return Bcfg2.Client.Tools.Tool.Inventory(self, states, structures) + + def FindExtra(self): + extra = [] + for handles in self.__handles__: + tag = handles[0] + specified = [] + for entry in self.getSupportedEntries(): + if entry.tag == tag: + specified.append(entry.get("name")) + extra.extend([Bcfg2.Client.XML.Element(tag, name=e) + for e in self.existing[tag].keys() + if e not in specified]) + return extra + + def populate_user_entry(self, entry): + """ Given a POSIXUser entry, set all of the 'missing' attributes + with their defaults """ + defaults = dict(group=entry.get('name'), + gecos=entry.get('name'), + shell='/bin/bash') + if entry.get('name') == 'root': + defaults['home'] = '/root' + else: + defaults['home'] = '/home/%s' % entry.get('name') + for key, val in defaults.items(): + if entry.get(key) is None: + entry.set(key, val) + if entry.get('group') in self.existing['POSIXGroup']: + entry.set('gid', + str(self.existing['POSIXGroup'][entry.get('group')][2])) + return entry + + def user_supplementary_groups(self, entry): + """ Get a list of supplmentary groups that the user in the + given entry is a member of """ + return [g for g in self.existing['POSIXGroup'].values() + if entry.get("name") in g[3] and g[0] != entry.get("group")] + + def VerifyPOSIXUser(self, entry, _): + """ Verify a POSIXUser entry """ + rv = self._verify(self.populate_user_entry(entry)) + if entry.get("current_exists", "true") == "true": + # verify supplemental groups + actual = [g[0] for g in self.user_supplementary_groups(entry)] + expected = [e.text for e in entry.findall("MemberOf")] + if set(expected) != set(actual): + entry.set('qtext', + "\n".join([entry.get('qtext', '')] + + ["%s %s has incorrect supplemental group " + "membership. Currently: %s. Should be: %s" + % (entry.tag, entry.get("name"), + actual, expected)])) + rv = False + if self.setup['interactive'] and not rv: + entry.set('qtext', + '%s\nInstall %s %s: (y/N) ' % + (entry.get('qtext', ''), entry.tag, entry.get('name'))) + return rv + + def VerifyPOSIXGroup(self, entry, _): + """ Verify a POSIXGroup entry """ + rv = self._verify(entry) + if self.setup['interactive'] and not rv: + entry.set('qtext', + '%s\nInstall %s %s: (y/N) ' % + (entry.get('qtext', ''), entry.tag, entry.get('name'))) + return rv + + def _verify(self, entry): + """ Perform most of the actual work of verification """ + errors = [] + if entry.get("name") not in self.existing[entry.tag]: + entry.set('current_exists', 'false') + errors.append("%s %s does not exist" % (entry.tag, + entry.get("name"))) + else: + for attr, idx in self.attr_mapping[entry.tag].items(): + val = str(self.existing[entry.tag][entry.get("name")][idx]) + entry.set("current_%s" % attr, val) + if attr in ["uid", "gid"]: + if entry.get(attr) is None: + # no uid/gid specified, so we let the tool + # automatically determine one -- i.e., it always + # verifies + continue + if val != entry.get(attr): + errors.append("%s for %s %s is incorrect. Current %s is " + "%s, but should be %s" % + (attr.title(), entry.tag, entry.get("name"), + attr, entry.get(attr), val)) + + if errors: + for error in errors: + self.logger.debug("%s: %s" % (self.name, error)) + entry.set('qtext', "\n".join([entry.get('qtext', '')] + errors)) + return len(errors) == 0 + + def Install(self, entries, states): + for entry in entries: + # install groups first, so that all groups exist for + # users that might need them + if entry.tag == 'POSIXGroup': + states[entry] = self._install(entry) + for entry in entries: + if entry.tag == 'POSIXUser': + states[entry] = self._install(entry) + self._existing = None + + def _install(self, entry): + """ add or modify a user or group using the appropriate command """ + if entry.get("name") not in self.existing[entry.tag]: + action = "add" + else: + action = "mod" + try: + self.cmd.run(self._get_cmd(action, + self.set_defaults[entry.tag](entry))) + self.modified.append(entry) + return True + except ExecutionError: + self.logger.error("POSIXUsers: Error creating %s %s: %s" % + (entry.tag, entry.get("name"), + sys.exc_info()[1])) + return False + + def _get_cmd(self, action, entry): + """ Get a command to perform the appropriate action (add, mod, + del) on the given entry. The command is always the same; we + set all attributes on a given user or group when modifying it + rather than checking which ones need to be changed. This + makes things fail as a unit (e.g., if a user is logged in, you + can't change its home dir, but you could change its GECOS, but + the whole operation fails), but it also makes this function a + lot, lot easier and simpler.""" + cmd = ["/usr/sbin/%s%s" % (entry.tag[5:].lower(), action)] + if action != 'del': + if entry.tag == 'POSIXGroup': + if entry.get('gid'): + cmd.extend(['-g', entry.get('gid')]) + elif entry.tag == 'POSIXUser': + cmd.append('-m') + if entry.get('uid'): + cmd.extend(['-u', entry.get('uid')]) + cmd.extend(['-g', entry.get('group')]) + extras = [e.text for e in entry.findall("MemberOf")] + if extras: + cmd.extend(['-G', ",".join(extras)]) + cmd.extend(['-d', entry.get('home')]) + cmd.extend(['-s', entry.get('shell')]) + cmd.extend(['-c', entry.get('gecos')]) + cmd.append(entry.get('name')) + return cmd + + def Remove(self, entries): + for entry in entries: + # remove users first, so that all users have been removed + # from groups before we remove them + if entry.tag == 'POSIXUser': + self._remove(entry) + for entry in entries: + if entry.tag == 'POSIXGroup': + try: + grp.getgrnam(entry.get("name")) + self._remove(entry) + except KeyError: + # at least some versions of userdel automatically + # remove the primary group for a user if the group + # name is the same as the username, and no other + # users are in the group + self.logger.info("POSIXUsers: Group %s does not exist. " + "It may have already been removed when " + "its users were deleted" % + entry.get("name")) + self._existing = None + self.extra = self.FindExtra() + + def _remove(self, entry): + """ Remove an entry """ + try: + self.cmd.run(self._get_cmd("del", entry)) + return True + except ExecutionError: + self.logger.error("POSIXUsers: Error deleting %s %s: %s" % + (entry.tag, entry.get("name"), + sys.exc_info()[1])) + return False diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index 927b25ba8..d5f55759f 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -61,6 +61,7 @@ class Tool(object): __req__ = {} __important__ = [] deprecated = False + experimental = False def __init__(self, logger, setup, config): self.setup = setup -- cgit v1.2.3-1-g7c22