From 2e0f33d8420a4c4beecb28a85576e54a4694046c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B6rn=20Baumbach?= Date: Thu, 16 Nov 2017 12:31:11 +0100 Subject: [PATCH] samba-tool: implement ou management commands MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Available subcommands: create - Create an organizational unit. delete - Delete an organizational unit. list - List all organizational units listobjects - List all objects in an organizational unit. move - Move an organizational unit. rename - Rename an organizational unit. Signed-off-by: Björn Baumbach Reviewed-by: Douglas Bagnall --- python/samba/netcmd/main.py | 1 + python/samba/netcmd/ou.py | 395 ++++++++++++++++++++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 python/samba/netcmd/ou.py diff --git a/python/samba/netcmd/main.py b/python/samba/netcmd/main.py index 7f94f897897..a9cf176a8c1 100644 --- a/python/samba/netcmd/main.py +++ b/python/samba/netcmd/main.py @@ -75,5 +75,6 @@ class cmd_sambatool(SuperCommand): subcommands["testparm"] = None subcommands["time"] = None subcommands["user"] = None + subcommands["ou"] = None subcommands["processes"] = None subcommands["visualize"] = None diff --git a/python/samba/netcmd/ou.py b/python/samba/netcmd/ou.py new file mode 100644 index 00000000000..16b7f653f71 --- /dev/null +++ b/python/samba/netcmd/ou.py @@ -0,0 +1,395 @@ +# implement samba_tool ou commands +# +# Copyright Bjoern Baumbach 2018 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import samba.getopt as options +import ldb + +from samba.auth import system_session +from samba.netcmd import ( + Command, + CommandError, + Option, + SuperCommand, + ) +from samba.samdb import SamDB +from samba import dsdb +from operator import attrgetter + +class cmd_rename(Command): + """Rename an organizational unit. + + The name of the organizational units can be specified as a full DN + or without the domainDN component. + + Examples: + samba-tool ou rename 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \ + 'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com' + samba-tool ou rename 'OU=OrgUnit' 'OU=NewNameOfOrgUnit' + + The examples show how an administrator would rename an ou 'OrgUnit' + to 'NewNameOfOrgUnit'. The new DN would be + 'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com' + """ + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["old_ou_dn", "new_ou_dn"] + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, old_ou_dn, new_ou_dn, credopts=None, sambaopts=None, + versionopts=None, H=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + domain_dn = ldb.Dn(samdb, samdb.domain_dn()) + + try: + full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn) + except Exception, e: + raise CommandError('Invalid old_ou_dn "%s": %s' % + (old_ou_dn, e.message)) + try: + full_new_ou_dn = samdb.normalize_dn_in_domain(new_ou_dn) + except Exception, e: + raise CommandError('Invalid new_ou_dn "%s": %s' % + (new_ou_dn, e.message)) + + try: + res = samdb.search(base=full_old_ou_dn, + expression="(objectclass=organizationalUnit)", + scope=ldb.SCOPE_BASE, attrs=[]) + if len(res) == 0: + self.outf.write('Unable to find ou "%s"\n' % old_ou_dn) + return + + samdb.rename(full_old_ou_dn, full_new_ou_dn) + except Exception, e: + raise CommandError('Failed to rename ou "%s"' % full_old_ou_dn, e) + self.outf.write('Renamed ou "%s" to "%s"\n' % (full_old_ou_dn, + full_new_ou_dn)) + +class cmd_move(Command): + """Move an organizational unit. + + The name of the organizational units can be specified as a full DN + or without the domainDN component. + + Examples: + samba-tool ou move 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \ + 'OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com' + samba-tool ou rename 'OU=OrgUnit' 'OU=NewParentOfOrgUnit' + + The examples show how an administrator would move an ou 'OrgUnit' + into the ou 'NewParentOfOrgUnit'. The ou 'OrgUnit' would become + a child of the 'NewParentOfOrgUnit' ou. The new DN would be + 'OU=OrgUnit,OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com' + """ + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + ] + + takes_args = ["old_ou_dn", "new_parent_dn"] + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, old_ou_dn, new_parent_dn, credopts=None, sambaopts=None, + versionopts=None, H=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = ldb.Dn(samdb, samdb.domain_dn()) + try: + full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn) + except Exception, e: + raise CommandError('Invalid old_ou_dn "%s": %s' % + (old_ou_dn, e.message)) + try: + full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn) + except Exception, e: + raise CommandError('Invalid new_parent_dn "%s": %s' % + (new_parent_dn, e.message)) + + full_new_ou_dn = ldb.Dn(samdb, str(full_old_ou_dn)) + full_new_ou_dn.remove_base_components(len(full_old_ou_dn)-1) + full_new_ou_dn.add_base(full_new_parent_dn) + + try: + res = samdb.search(base=full_old_ou_dn, + expression="(objectclass=organizationalUnit)", + scope=ldb.SCOPE_BASE, attrs=[]) + if len(res) == 0: + self.outf.write('Unable to find ou "%s"\n' % full_old_ou_dn) + return + samdb.rename(full_old_ou_dn, full_new_ou_dn) + except Exception, e: + raise CommandError('Failed to move ou "%s"' % full_old_ou_dn, e) + self.outf.write('Moved ou "%s" into "%s"\n' % + (full_old_ou_dn, full_new_parent_dn)) + +class cmd_create(Command): + """Create an organizational unit. + + The name of the new ou can be specified as a full DN or without the + domainDN component. + + Examples: + samba-tool ou create 'OU=OrgUnit' + samba-tool ou create 'OU=SubOU,OU=OrgUnit,DC=samdom,DC=example,DC=com' + + The examples show how an administrator would create a new ou 'OrgUnit' + and a new ou 'SubOU' as a child of the ou 'OrgUnit'. + """ + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + Option("--description", help="OU's description", + type=str, dest="description"), + ] + + takes_args = ["ou_dn"] + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None, + H=None, description=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + try: + full_ou_dn = samdb.normalize_dn_in_domain(ou_dn) + except Exception, e: + raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message)) + + try: + samdb.create_ou(full_ou_dn, description=description) + except Exception, e: + raise CommandError('Failed to create ou "%s"' % full_ou_dn, e) + + self.outf.write('Created ou "%s"\n' % full_ou_dn) + +class cmd_listobjects(Command): + """List all objects in an organizational unit. + + The name of the organizational unit can be specified as a full DN + or without the domainDN component. + + Examples: + samba-tool ou listobjects 'OU=OrgUnit,DC=samdom,DC=example,DC=com' + samba-tool ou listobjects 'OU=OrgUnit' + + The examples show how an administrator would list all child objects + of the ou 'OrgUnit'. + """ + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + Option("--full-dn", dest="full_dn", default=False, action='store_true', + help="Display DNs including the base DN."), + Option("-r", "--recursive", dest="recursive", default=False, + action='store_true', help="List objects recursively."), + ] + + takes_args = [ "ou_dn" ] + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None, + H=None, full_dn=False, recursive=False): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + domain_dn = ldb.Dn(samdb, samdb.domain_dn()) + + try: + full_ou_dn = samdb.normalize_dn_in_domain(ou_dn) + except Exception, e: + raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message)) + + minchilds = 0 + scope = ldb.SCOPE_ONELEVEL + if recursive: + minchilds = 1 + scope = ldb.SCOPE_SUBTREE + + try: + childs = samdb.search(base=full_ou_dn, + expression="(objectclass=*)", + scope=scope, attrs=[]) + if len(childs) <= minchilds: + self.outf.write('ou "%s" is empty\n' % ou_dn) + return + + for child in sorted(childs, key=attrgetter('dn')): + if child.dn == full_ou_dn: + continue + if not full_dn: + child.dn.remove_base_components(len(domain_dn)) + self.outf.write("%s\n" % child.dn) + + except Exception, e: + raise CommandError('Failed to list contents of ou "%s"' % + full_ou_dn, e) + +class cmd_list(Command): + """List all organizational units. + + Example: + samba-tool ou listobjects + + The example shows how an administrator would list all organizational + units. + """ + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + Option("--full-dn", dest="full_dn", default=False, action='store_true', + help="Display DNs including the base DN."), + ] + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, sambaopts=None, credopts=None, versionopts=None, H=None, + full_dn=False): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = ldb.Dn(samdb, samdb.domain_dn()) + res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE, + expression="(objectClass=organizationalUnit)", + attrs=[]) + if (len(res) == 0): + return + + for msg in sorted(res, key=attrgetter('dn')): + if not full_dn: + msg.dn.remove_base_components(len(domain_dn)) + self.outf.write("%s\n" % str(msg.dn)) + +class cmd_delete(Command): + """Delete an organizational unit. + + The name of the organizational unit can be specified as a full DN + or without the domainDN component. + + Examples: + samba-tool ou delete 'OU=OrgUnit,DC=samdom,DC=example,DC=com' + samba-tool ou delete 'OU=OrgUnit' + + The examples show how an administrator would delete the ou 'OrgUnit'. + """ + + synopsis = "%prog [options]" + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", + type=str, metavar="URL", dest="H"), + Option("--force-subtree-delete", dest="force_subtree_delete", + default=False, action='store_true', + help="Delete organizational unit and all children reclusively"), + ] + + takes_args = ["ou_dn"] + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None, + H=None, force_subtree_delete=False): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp, fallback_machine=True) + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + domain_dn = ldb.Dn(samdb, samdb.domain_dn()) + + try: + full_ou_dn = samdb.normalize_dn_in_domain(ou_dn) + except Exception, e: + raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message)) + + controls = [] + if force_subtree_delete: + controls = ["tree_delete:1"] + + try: + res = samdb.search(base=full_ou_dn, + expression="(objectclass=organizationalUnit)", + scope=ldb.SCOPE_BASE, attrs=[]) + if len(res) == 0: + self.outf.write('Unable to find ou "%s"\n' % ou_dn) + return + samdb.delete(full_ou_dn, controls) + except Exception, e: + raise CommandError('Failed to delete ou "%s"' % full_ou_dn, e) + + self.outf.write('Deleted ou "%s"\n' % full_ou_dn) + + +class cmd_ou(SuperCommand): + """Organizational Units (OU) management""" + + subcommands = {} + subcommands["create"] = cmd_create() + subcommands["delete"] = cmd_delete() + subcommands["move"] = cmd_move() + subcommands["rename"] = cmd_rename() + subcommands["list"] = cmd_list() + subcommands["listobjects"] = cmd_listobjects() -- 2.47.3