# implement samba-tool ou commands # # Copyright Bjoern Baumbach 2018-2019 # # 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 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) try: full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn) except Exception as e: raise CommandError('Invalid old_ou_dn "%s": %s' % (old_ou_dn, e)) try: full_new_ou_dn = samdb.normalize_dn_in_domain(new_ou_dn) except Exception as e: raise CommandError('Invalid new_ou_dn "%s": %s' % (new_ou_dn, e)) 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 as 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) try: full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn) except Exception as e: raise CommandError('Invalid old_ou_dn "%s": %s' % (old_ou_dn, e)) try: full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn) except Exception as e: raise CommandError('Invalid new_parent_dn "%s": %s' % (new_parent_dn, e)) 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 as 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_add(Command): """Add a new organizational unit. The name of the new ou can be specified as a full DN or without the domainDN component. Examples: samba-tool ou add 'OU=OrgUnit' samba-tool ou add 'OU=SubOU,OU=OrgUnit,DC=samdom,DC=example,DC=com' The examples show how an administrator would add 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 as e: raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e)) try: samdb.create_ou(full_ou_dn, description=description) except Exception as e: raise CommandError('Failed to add ou "%s"' % full_ou_dn, e) self.outf.write('Added 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 as e: raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e)) minchildren = 0 scope = ldb.SCOPE_ONELEVEL if recursive: minchildren = 1 scope = ldb.SCOPE_SUBTREE try: children = samdb.search(base=full_ou_dn, expression="(objectclass=*)", scope=scope, attrs=[]) if len(children) <= minchildren: self.outf.write('ou "%s" is empty\n' % ou_dn) return for child in sorted(children, 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 as 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("-b", "--base-dn", help="Specify base DN to use.", type=str), 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, base_dn=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) search_dn = ldb.Dn(samdb, samdb.domain_dn()) if base_dn: search_dn = samdb.normalize_dn_in_domain(base_dn) res = samdb.search(search_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: domain_dn = ldb.Dn(samdb, samdb.domain_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 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, 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) try: full_ou_dn = samdb.normalize_dn_in_domain(ou_dn) except Exception as e: raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e)) 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 as 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["add"] = cmd_add() subcommands["create"] = cmd_add() subcommands["delete"] = cmd_delete() subcommands["move"] = cmd_move() subcommands["rename"] = cmd_rename() subcommands["list"] = cmd_list() subcommands["listobjects"] = cmd_listobjects()