From 68155811aba10a498cc07bf8a1c6f3a092e6147c Mon Sep 17 00:00:00 2001 From: Rowland Penny Date: Tue, 2 Jul 2019 13:41:34 +0100 Subject: [PATCH] samba-tool: Add facility to add rfc2307 attributes to an already created user or group Signed-off-by: Rowland Penny Reviewed-by: David Mulder Reviewed-by: Andrew Bartlet Reviewed-by: Andreas Schneider Autobuild-User(master): Andreas Schneider Autobuild-Date(master): Thu Oct 17 12:21:55 UTC 2019 on sn-devel-184 --- python/samba/netcmd/group.py | 96 +++++++++++ python/samba/netcmd/user.py | 224 +++++++++++++++++++++++++ python/samba/tests/samba_tool/group.py | 100 ++++++++++- python/samba/tests/samba_tool/user.py | 58 ++++++- 4 files changed, 472 insertions(+), 6 deletions(-) diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py index 536c1cba613..4798b773ddf 100644 --- a/python/samba/netcmd/group.py +++ b/python/samba/netcmd/group.py @@ -804,6 +804,101 @@ class cmd_group_edit(Command): self.outf.write("Modified group '%s' successfully\n" % groupname) +class cmd_group_add_unix_attrs(Command): + """Add RFC2307 attributes to a group. + +This command adds Unix attributes to a group account in the Active +Directory domain. +The groupname specified on the command is the sAMaccountName. + +Unix (RFC2307) attributes will be added to the group account. + +Add 'idmap_ldb:use rfc2307 = Yes' to smb.conf to use these attributes for +UID/GID mapping. + +The command may be run from the root userid or another authorized userid. +The -H or --URL= option can be used to execute the command against a +remote server. + +Example1: +samba-tool group addunixattrs Group1 10000 + +Example1 shows how to add RFC2307 attributes to a domain enabled group +account. + +The groups Unix ID will be set to '10000', provided this ID isn't already +in use. + +""" + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["groupname", "gidnumber"] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, groupname, gidnumber, credopts=None, sambaopts=None, + versionopts=None, H=None): + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domaindn = samdb.domain_dn() + + # Check group exists and doesn't have a gidNumber + filter = "(samaccountname={})".format(ldb.binary_encode(groupname)) + res = samdb.search(domaindn, + scope=ldb.SCOPE_SUBTREE, + expression=filter) + if (len(res) == 0): + raise CommandError("Unable to find group '{}'".format(groupname)) + + group_dn = res[0].dn + + if "gidNumber" in res[0]: + raise CommandError("Group {} is a Unix group.".format(groupname)) + + # Check if supplied gidnumber isn't already being used + filter = "(&(objectClass=group)(gidNumber={}))".format(gidnumber) + res = samdb.search(domaindn, + scope=ldb.SCOPE_SUBTREE, + expression=filter) + if (len(res) != 0): + raise CommandError('gidNumber {} already used.'.format(gidnumber)) + + if not lp.get("idmap_ldb:use rfc2307"): + self.outf.write("You are setting a Unix/RFC2307 GID. " + "You may want to set 'idmap_ldb:use rfc2307 = Yes'" + " in smb.conf to use the attributes for " + "XID/SID-mapping.\n") + + group_mod = """ +dn: {0} +changetype: modify +add: gidNumber +gidNumber: {1} +""".format(group_dn, gidnumber) + + try: + samdb.modify_ldif(group_mod) + except ldb.LdbError as e: + raise CommandError("Failed to modify group '{0}': {1}" + .format(groupname, e)) + + self.outf.write("Modified Group '{}' successfully\n".format(groupname)) + + class cmd_group(SuperCommand): """Group management.""" @@ -818,3 +913,4 @@ class cmd_group(SuperCommand): subcommands["move"] = cmd_group_move() subcommands["show"] = cmd_group_show() subcommands["stats"] = cmd_group_stats() + subcommands["addunixattrs"] = cmd_group_add_unix_attrs() diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py index 6730ab718de..affbbf067c1 100644 --- a/python/samba/netcmd/user.py +++ b/python/samba/netcmd/user.py @@ -2603,6 +2603,229 @@ class cmd_user_move(Command): (username, full_new_parent_dn)) +class cmd_user_add_unix_attrs(Command): + """Add RFC2307 attributes to a user. + +This command adds Unix attributes to a user account in the Active +Directory domain. + +The username specified on the command is the sAMaccountName. + +You must supply a unique uidNumber. + +Unix (RFC2307) attributes will be added to the user account. + +If you supply a gidNumber with '--gid-number', this will be used for the +users Unix 'gidNumber' attribute. + +If '--gid-number' is not supplied, the users Unix gidNumber will be set to the +one found in 'Domain Users', this means Domain Users must have a gidNumber +attribute. + +if '--unix-home' is not supplied, the users Unix home directory will be +set to /home/DOMAIN/username + +if '--login-shell' is not supplied, the users Unix login shell will be +set to '/bin/sh' + +if ---gecos' is not supplied, the users Unix gecos field will be set to the +users 'CN' + +Add 'idmap_ldb:use rfc2307 = Yes' to the smb.conf on DCs, to use these +attributes for UID/GID mapping. + +The command may be run from the root userid or another authorised userid. +The -H or --URL= option can be used to execute the command against a +remote server. + +Example1: +samba-tool user addunixattrs User1 10001 + +Example1 shows how to add RFC2307 attributes to a domain enabled user +account, Domain Users will be set as the users gidNumber. + +The users Unix ID will be set to '10001', provided this ID isn't already +in use. + +Example2: +samba-tool user addunixattrs User2 10002 --gid-number=10001 \ +--unix-home=/home/User2 + +Example2 shows how to add RFC2307 attributes to a domain enabled user +account. + +The users Unix ID will be set to '10002', provided this ID isn't already +in use. + +The users gidNumber attribute will be set to '10001' + +The users Unix home directory will be set to '/home/user2' + +Example3: +samba-tool user addunixattrs User3 10003 --gid-number=10001 \ +--login-shell=/bin/false --gecos='User3 test' + +Example3 shows how to add RFC2307 attributes to a domain enabled user +account. + +The users Unix ID will be set to '10003', provided this ID isn't already +in use. + +The users gidNumber attribute will be set to '10001' + +The users Unix login shell will be set to '/bin/false' + +The users gecos field will be set to 'User3 test' + +Example4: +samba-tool user addunixattrs User4 10004 --gid-number=10001 \ +--unix-home=/home/User4 --login-shell=/bin/bash --gecos='User4 test' + +Example4 shows how to add RFC2307 attributes to a domain enabled user +account. + +The users Unix ID will be set to '10004', provided this ID isn't already +in use. + +The users gidNumber attribute will be set to '10001' + +The users Unix home directory will be set to '/home/User4' + +The users Unix login shell will be set to '/bin/bash' + +The users gecos field will be set to 'User4 test' + +""" + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + Option("--gid-number", help="User's Unix/RFC2307 GID", type=str), + Option("--unix-home", help="User's Unix/RFC2307 home directory", + type=str), + Option("--login-shell", help="User's Unix/RFC2307 login shell", + type=str), + Option("--gecos", help="User's Unix/RFC2307 GECOS field", type=str), + Option("--uid", help="User's Unix/RFC2307 username", type=str), + ] + + takes_args = ["username", "uid-number"] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, username, uid_number, credopts=None, sambaopts=None, + versionopts=None, H=None, gid_number=None, unix_home=None, + login_shell=None, gecos=None, uid=None): + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domaindn = samdb.domain_dn() + + # Check that uidNumber supplied isn't already in use + filter = ("(&(objectClass=person)(uidNumber={}))" + .format(uid_number)) + res = samdb.search(domaindn, + scope=ldb.SCOPE_SUBTREE, + expression=filter) + if (len(res) != 0): + raise CommandError("uidNumber {} is already being used." + .format(uid_number)) + + # Check user exists and doesn't have a uidNumber + filter = "(samaccountname={})".format(ldb.binary_encode(username)) + res = samdb.search(domaindn, + scope=ldb.SCOPE_SUBTREE, + expression=filter) + if (len(res) == 0): + raise CommandError("Unable to find user '{}'".format(username)) + + user_dn = res[0].dn + + if "uidNumber" in res[0]: + raise CommandError("User {} is already a Unix user." + .format(username)) + + if gecos is None: + gecos = res[0]["cn"][0] + + if uid is None: + uid = res[0]["cn"][0] + + if gid_number is None: + search_filter = ("(samaccountname={})" + .format(ldb.binary_encode('Domain Users'))) + try: + res = samdb.search(domaindn, + scope=ldb.SCOPE_SUBTREE, + expression=search_filter) + for msg in res: + gid_number=msg.get('gidNumber') + except IndexError: + raise CommandError('Domain Users does not have a' + ' gidNumber attribute') + + if login_shell is None: + login_shell = "/bin/sh" + + if unix_home is None: + # obtain nETBIOS Domain Name + filter = "(&(objectClass=crossRef)(nETBIOSName=*))" + searchdn = ("CN=Partitions,CN=Configuration," + domaindn) + try: + res = samdb.search(searchdn, + scope=ldb.SCOPE_SUBTREE, + expression=filter) + unix_domain = res[0]["nETBIOSName"][0] + except IndexError: + raise CommandError('Unable to find Unix domain') + + unix_home = "/home/{0}/{1}".format(unix_domain, username) + + if not lp.get("idmap_ldb:use rfc2307"): + self.outf.write("You are setting a Unix/RFC2307 UID & GID. " + "You may want to set 'idmap_ldb:use rfc2307 = Yes'" + " in smb.conf to use the attributes for " + "XID/SID-mapping.\n") + + user_mod = """ +dn: {0} +changetype: modify +add: uidNumber +uidNumber: {1} +add: gidnumber +gidNumber: {2} +add: gecos +gecos: {3} +add: uid +uid: {4} +add: loginshell +loginShell: {5} +add: unixHomeDirectory +unixHomeDirectory: {6} +""".format(user_dn, uid_number, gid_number, gecos, uid, login_shell, unix_home) + + samdb.transaction_start() + try: + samdb.modify_ldif(user_mod) + except ldb.LdbError as e: + raise CommandError("Failed to modify user '{0}': {1}" + .format(username, e)) + else: + samdb.transaction_commit() + self.outf.write("Modified User '{}' successfully\n" + .format(username)) + + class cmd_user(SuperCommand): """User management.""" @@ -2621,3 +2844,4 @@ class cmd_user(SuperCommand): subcommands["edit"] = cmd_user_edit() subcommands["show"] = cmd_user_show() subcommands["move"] = cmd_user_move() + subcommands["addunixattrs"] = cmd_user_add_unix_attrs() diff --git a/python/samba/tests/samba_tool/group.py b/python/samba/tests/samba_tool/group.py index e521c720b77..215219c3918 100644 --- a/python/samba/tests/samba_tool/group.py +++ b/python/samba/tests/samba_tool/group.py @@ -39,14 +39,29 @@ class GroupCmdTestCase(SambaToolCmdTest): self.groups.append(self._randomGroup({"name": "testgroup2"})) self.groups.append(self._randomGroup({"name": "testgroup3"})) self.groups.append(self._randomGroup({"name": "testgroup4"})) + self.groups.append(self._randomPosixGroup({"name": "posixgroup1"})) + self.groups.append(self._randomPosixGroup({"name": "posixgroup2"})) + self.groups.append(self._randomPosixGroup({"name": "posixgroup3"})) + self.groups.append(self._randomPosixGroup({"name": "posixgroup4"})) + self.groups.append(self._randomUnixGroup({"name": "unixgroup1"})) + self.groups.append(self._randomUnixGroup({"name": "unixgroup2"})) + self.groups.append(self._randomUnixGroup({"name": "unixgroup3"})) + self.groups.append(self._randomUnixGroup({"name": "unixgroup4"})) - # setup the 4 groups and ensure they are correct + # setup the 12 groups and ensure they are correct for group in self.groups: - (result, out, err) = self._create_group(group) + (result, out, err) = group["createGroupFn"](group) self.assertCmdSuccess(result, out, err) self.assertEquals(err, "", "There shouldn't be any error message") - self.assertIn("Added group %s" % group["name"], out) + + if 'unix' in group["name"]: + self.assertIn("Modified Group '%s' successfully" + % group["name"], out) + else: + self.assertIn("Added group %s" % group["name"], out) + + group["checkGroupFn"](group) found = self._find_group(group["name"]) @@ -221,14 +236,72 @@ class GroupCmdTestCase(SambaToolCmdTest): self.assertIn("dn: CN=Domain Users,CN=Users,DC=samba,DC=example,DC=com", out) def _randomGroup(self, base={}): - """create a group with random attribute values, you can specify base attributes""" + """create a group with random attribute values, you can specify base + attributes""" group = { "name": self.randomName(), "description": self.randomName(count=100), + "createGroupFn": self._create_group, + "checkGroupFn": self._check_group, } group.update(base) return group + def _randomPosixGroup(self, base={}): + """create a group with random attribute values and additional RFC2307 + attributes, you can specify base attributes""" + group = self._randomGroup({}) + group.update(base) + posixAttributes = { + "unixdomain": self.randomName(), + "gidNumber": self.randomXid(), + "createGroupFn": self._create_posix_group, + "checkGroupFn": self._check_posix_group, + } + group.update(posixAttributes) + group.update(base) + return group + + def _randomUnixGroup(self, base={}): + """create a group with random attribute values and additional RFC2307 + attributes, you can specify base attributes""" + group = self._randomGroup({}) + group.update(base) + posixAttributes = { + "gidNumber": self.randomXid(), + "createGroupFn": self._create_unix_group, + "checkGroupFn": self._check_unix_group, + } + group.update(posixAttributes) + group.update(base) + return group + + def _check_group(self, group): + """ check if a group from SamDB has the same attributes as + its template """ + found = self._find_group(group["name"]) + + self.assertEquals("%s" % found.get("name"), group["name"]) + self.assertEquals("%s" % found.get("description"), group["description"]) + + def _check_posix_group(self, group): + """ check if a posix_group from SamDB has the same attributes as + its template """ + found = self._find_group(group["name"]) + + self.assertEquals("%s" % found.get("gidNumber"), "%s" % + group["gidNumber"]) + self._check_group(group) + + def _check_unix_group(self, group): + """ check if a unix_group from SamDB has the same attributes as its +template """ + found = self._find_group(group["name"]) + + self.assertEquals("%s" % found.get("gidNumber"), "%s" % + group["gidNumber"]) + self._check_group(group) + def _create_group(self, group): return self.runsubcmd("group", "add", group["name"], "--description=%s" % group["description"], @@ -236,6 +309,25 @@ class GroupCmdTestCase(SambaToolCmdTest): "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + def _create_posix_group(self, group): + """ create a new group with RFC2307 attributes """ + return self.runsubcmd("group", "add", group["name"], + "--description=%s" % group["description"], + "--nis-domain=%s" % group["unixdomain"], + "--gid-number=%s" % group["gidNumber"], + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + + def _create_unix_group(self, group): + """ Add RFC2307 attributes to a group""" + self._create_group(group) + return self.runsubcmd("group", "addunixattrs", group["name"], + "%s" % group["gidNumber"], + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + def _find_group(self, name): search_filter = ("(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(name), diff --git a/python/samba/tests/samba_tool/user.py b/python/samba/tests/samba_tool/user.py index 7ae80876b2a..4ebc9eb3bab 100644 --- a/python/samba/tests/samba_tool/user.py +++ b/python/samba/tests/samba_tool/user.py @@ -50,14 +50,23 @@ class UserCmdTestCase(SambaToolCmdTest): self.users.append(self._randomPosixUser({"name": "posixuser2"})) self.users.append(self._randomPosixUser({"name": "posixuser3"})) self.users.append(self._randomPosixUser({"name": "posixuser4"})) + self.users.append(self._randomUnixUser({"name": "unixuser1"})) + self.users.append(self._randomUnixUser({"name": "unixuser2"})) + self.users.append(self._randomUnixUser({"name": "unixuser3"})) + self.users.append(self._randomUnixUser({"name": "unixuser4"})) - # setup the 8 users and ensure they are correct + # setup the 12 users and ensure they are correct for user in self.users: (result, out, err) = user["createUserFn"](user) self.assertCmdSuccess(result, out, err) self.assertEquals(err, "", "Shouldn't be any error messages") - self.assertIn("User '%s' created successfully" % user["name"], out) + if 'unix' in user["name"]: + self.assertIn("Modified User '%s' successfully" % user["name"], + out) + else: + self.assertIn("User '%s' created successfully" % user["name"], + out) user["checkUserFn"](user) @@ -539,6 +548,24 @@ sAMAccountName: %s user.update(base) return user + def _randomUnixUser(self, base={}): + """create a user with random attribute values and additional RFC2307 + attributes, you can specify base attributes""" + user = self._randomUser({}) + user.update(base) + posixAttributes = { + "uidNumber": self.randomXid(), + "gidNumber": self.randomXid(), + "uid": self.randomName(), + "loginShell": self.randomName(), + "gecos": self.randomName(), + "createUserFn": self._create_unix_user, + "checkUserFn": self._check_unix_user, + } + user.update(posixAttributes) + user.update(base) + return user + def _check_user(self, user): """ check if a user from SamDB has the same attributes as its template """ found = self._find_user(user["name"]) @@ -560,6 +587,20 @@ sAMAccountName: %s self.assertEquals("%s" % found.get("uid"), user["uid"]) self._check_user(user) + def _check_unix_user(self, user): + """ check if a unix_user from SamDB has the same attributes as its +template """ + found = self._find_user(user["name"]) + + self.assertEquals("%s" % found.get("loginShell"), user["loginShell"]) + self.assertEquals("%s" % found.get("gecos"), user["gecos"]) + self.assertEquals("%s" % found.get("uidNumber"), "%s" % + user["uidNumber"]) + self.assertEquals("%s" % found.get("gidNumber"), "%s" % + user["gidNumber"]) + self.assertEquals("%s" % found.get("uid"), user["uid"]) + self._check_user(user) + def _create_user(self, user): return self.runsubcmd("user", "create", user["name"], user["password"], "--surname=%s" % user["surname"], @@ -588,6 +629,19 @@ sAMAccountName: %s "-H", "ldap://%s" % os.environ["DC_SERVER"], "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + def _create_unix_user(self, user): + """ Add RFC2307 attributes to a user""" + self._create_user(user) + return self.runsubcmd("user", "addunixattrs", user["name"], + "%s" % user["uidNumber"], + "--gid-number=%s" % user["gidNumber"], + "--gecos=%s" % user["gecos"], + "--login-shell=%s" % user["loginShell"], + "--uid=%s" % user["uid"], + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], + os.environ["DC_PASSWORD"])) + def _find_user(self, name): search_filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(name), "CN=Person,CN=Schema,CN=Configuration", self.samdb.domain_dn()) userlist = self.samdb.search(base=self.samdb.domain_dn(),