mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
bf07a97931
Everywhere else it is using Group's except for one place which makes it obvious this was incorrect. Same goes for Computers's vs Computer's Signed-off-by: Rob van der Linde <rob@catalyst.net.nz> Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
1417 lines
56 KiB
Python
1417 lines
56 KiB
Python
# Copyright Jelmer Vernooij 2008
|
|
#
|
|
# Based on the original in EJS:
|
|
# Copyright Andrew Tridgell 2005
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
import samba.getopt as options
|
|
from samba.netcmd import Command, SuperCommand, CommandError, Option
|
|
import ldb
|
|
from samba.ndr import ndr_pack, ndr_unpack
|
|
from samba.dcerpc import security
|
|
|
|
from samba.auth import system_session
|
|
from samba.samdb import SamDB
|
|
from samba.dsdb import (
|
|
ATYPE_SECURITY_GLOBAL_GROUP,
|
|
DS_GUID_USERS_CONTAINER,
|
|
GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
|
|
GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
|
|
GTYPE_SECURITY_GLOBAL_GROUP,
|
|
GTYPE_SECURITY_UNIVERSAL_GROUP,
|
|
GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
|
|
GTYPE_DISTRIBUTION_GLOBAL_GROUP,
|
|
GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
|
|
SYSTEM_FLAG_DISALLOW_DELETE,
|
|
SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE,
|
|
SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME,
|
|
UF_ACCOUNTDISABLE,
|
|
)
|
|
from collections import defaultdict
|
|
from subprocess import check_call, CalledProcessError
|
|
from samba.common import get_bytes, normalise_int32
|
|
import os
|
|
import tempfile
|
|
from . import common
|
|
|
|
security_group = dict({"Builtin": GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
|
|
"Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
|
|
"Global": GTYPE_SECURITY_GLOBAL_GROUP,
|
|
"Universal": GTYPE_SECURITY_UNIVERSAL_GROUP})
|
|
distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
|
|
"Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP,
|
|
"Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP})
|
|
|
|
|
|
class cmd_group_add(Command):
|
|
"""Creates a new AD group.
|
|
|
|
This command adds a new Active Directory group. The groupname specified on the command is a unique sAMAccountName.
|
|
|
|
An Active Directory group may contain user and computer accounts as well as other groups. An administrator adds a new group and adds members to that group so they can be managed as a single entity. This helps to simplify security and system administration.
|
|
|
|
Groups may also be used to establish email distribution lists, using --group-type=Distribution.
|
|
|
|
Groups are located in domains in organizational units (OUs). The group's scope is a characteristic of the group that designates the extent to which the group is applied within the domain tree or forest.
|
|
|
|
The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
|
|
|
|
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 on a remote server.
|
|
|
|
Example1:
|
|
samba-tool group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
|
|
|
|
Example1 adds a new group with the name Group1 added to the Users container on a remote LDAP server. The -U parameter is used to pass the userid and password of a user that exists on the remote server and is authorized to issue the command on that server. It defaults to the security type and global scope.
|
|
|
|
Example2:
|
|
sudo samba-tool group add Group2 --group-type=Distribution
|
|
|
|
Example2 adds a new distribution group to the local server. The command is run under root using the sudo command.
|
|
|
|
Example3:
|
|
samba-tool group add Group3 --nis-domain=samdom --gid-number=12345
|
|
|
|
Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (both options are required to enable this feature).
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> [options]"
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"versionopts": options.VersionOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
}
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
Option("--groupou",
|
|
help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
|
|
type=str),
|
|
Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
|
|
help="Group scope (Domain | Global | Universal)"),
|
|
Option("--group-type", type="choice", choices=["Security", "Distribution"],
|
|
help="Group type (Security | Distribution)"),
|
|
Option("--description", help="Group's description", type=str),
|
|
Option("--mail-address", help="Group's email address", type=str),
|
|
Option("--notes", help="Group's notes", type=str),
|
|
Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
|
|
Option("--nis-domain", help="SFU30 NIS Domain", type=str),
|
|
Option("--special", help="Add a special predefined group", action="store_true", default=False),
|
|
]
|
|
|
|
takes_args = ["groupname"]
|
|
|
|
def run(self, groupname, credopts=None, sambaopts=None,
|
|
versionopts=None, H=None, groupou=None, group_scope=None,
|
|
group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None,
|
|
special=False):
|
|
|
|
if (group_type or "Security") == "Security":
|
|
gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
|
|
else:
|
|
gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
|
|
|
|
if (gid_number is None and nis_domain is not None) or (gid_number is not None and nis_domain is None):
|
|
raise CommandError('Both --gid-number and --nis-domain have to be set for a RFC2307-enabled group. Operation cancelled.')
|
|
|
|
lp = sambaopts.get_loadparm()
|
|
creds = credopts.get_credentials(lp, fallback_machine=True)
|
|
|
|
try:
|
|
samdb = SamDB(url=H, session_info=system_session(),
|
|
credentials=creds, lp=lp)
|
|
except Exception as e:
|
|
# FIXME: catch more specific exception
|
|
raise CommandError(f'Failed to add group "{groupname}"', e)
|
|
|
|
if special:
|
|
invalid_option = None
|
|
if group_scope is not None:
|
|
invalid_option = 'group-scope'
|
|
elif group_type is not None:
|
|
invalid_option = 'group-type'
|
|
elif description is not None:
|
|
invalid_option = 'description'
|
|
elif mail_address is not None:
|
|
invalid_option = 'mail-address'
|
|
elif notes is not None:
|
|
invalid_option = 'notes'
|
|
elif gid_number is not None:
|
|
invalid_option = 'gid-number'
|
|
elif nis_domain is not None:
|
|
invalid_option = 'nis-domain'
|
|
|
|
if invalid_option is not None:
|
|
raise CommandError(f'Superfluous option --{invalid_option} '
|
|
f'specified with --special')
|
|
|
|
if not samdb.am_pdc():
|
|
raise CommandError('Adding special groups is only permitted '
|
|
'against the PDC!')
|
|
|
|
special_groups = {
|
|
# On Windows, this group is added automatically when the PDC
|
|
# role is held by a DC running Windows Server 2012 R2 or later.
|
|
# https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/protected-users-security-group#BKMK_Requirements
|
|
'Protected Users'.lower(): (
|
|
'Protected Users',
|
|
GTYPE_SECURITY_GLOBAL_GROUP,
|
|
security.DOMAIN_RID_PROTECTED_USERS,
|
|
'Members of this group are afforded additional '
|
|
'protections against authentication security threats'),
|
|
}
|
|
|
|
special_group = special_groups.get(groupname.lower())
|
|
if special_group is None:
|
|
raise CommandError(f'Unknown special group "{groupname}".')
|
|
|
|
groupname, gtype, rid, description = special_group
|
|
group_type = normalise_int32(gtype)
|
|
|
|
group_dn = samdb.get_default_basedn()
|
|
|
|
if gtype == GTYPE_SECURITY_GLOBAL_GROUP:
|
|
object_sid = security.dom_sid(
|
|
f'{samdb.get_domain_sid()}-{rid}')
|
|
system_flags = None
|
|
|
|
if not groupou:
|
|
group_dn = samdb.get_wellknown_dn(group_dn,
|
|
DS_GUID_USERS_CONTAINER)
|
|
|
|
elif gtype == GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
|
|
object_sid = security.dom_sid(f'S-1-5-32-{rid}')
|
|
system_flags = (SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE |
|
|
SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME |
|
|
SYSTEM_FLAG_DISALLOW_DELETE)
|
|
|
|
if not groupou:
|
|
try:
|
|
group_dn.add_child('CN=Builtin')
|
|
except ldb.LdbError:
|
|
raise RuntimeError('Error getting Builtin objects DN')
|
|
else:
|
|
raise RuntimeError(f'Unknown group type {gtype}')
|
|
|
|
if groupou:
|
|
try:
|
|
group_dn.add_child(groupou)
|
|
except ldb.LdbError:
|
|
raise CommandError(f'Invalid group OU "{groupou}"')
|
|
|
|
try:
|
|
group_dn.add_child(f'CN={groupname}')
|
|
except ldb.LdbError:
|
|
raise CommandError(f'Invalid group name "{groupname}"')
|
|
|
|
msg = {
|
|
'dn': group_dn,
|
|
'sAMAccountName': groupname,
|
|
'objectClass': 'group',
|
|
'groupType': group_type,
|
|
'description': description,
|
|
'objectSid': ndr_pack(object_sid),
|
|
'isCriticalSystemObject': 'TRUE',
|
|
}
|
|
|
|
if system_flags is not None:
|
|
msg['systemFlags'] = system_flags
|
|
|
|
try:
|
|
samdb.add(msg, controls=['relax:0'])
|
|
except ldb.LdbError as e:
|
|
num, estr = e.args
|
|
if num == ldb.ERR_CONSTRAINT_VIOLATION:
|
|
try:
|
|
res = samdb.search(
|
|
expression=f'(objectSid={object_sid})',
|
|
attrs=['sAMAccountName'])
|
|
except ldb.LdbError:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}"', e)
|
|
|
|
if len(res) != 1:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}"', e)
|
|
|
|
name = res[0].get('sAMAccountName', idx=0)
|
|
if name:
|
|
with_name = f' with name "{name}"'
|
|
else:
|
|
with_name = ''
|
|
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}" - Special group '
|
|
f'already exists{with_name} at "{res[0].dn}".')
|
|
|
|
elif num == ldb.ERR_ENTRY_ALREADY_EXISTS:
|
|
try:
|
|
res = samdb.search(base=group_dn,
|
|
scope=ldb.SCOPE_BASE,
|
|
attrs=['sAMAccountName',
|
|
'objectSid',
|
|
'groupType'])
|
|
except ldb.LdbError:
|
|
try:
|
|
res = samdb.search(
|
|
expression=f'(sAMAccountName={groupname})',
|
|
attrs=['sAMAccountName',
|
|
'objectSid',
|
|
'groupType'])
|
|
except ldb.LdbError:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}"', e)
|
|
|
|
if len(res) != 1:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}"', e)
|
|
|
|
got_name = res[0].get('sAMAccountName', idx=0)
|
|
if got_name:
|
|
named = f'named "{got_name}"'
|
|
else:
|
|
named = 'with no name'
|
|
|
|
got_group_type = res[0].get('groupType',
|
|
idx=0).decode('utf-8')
|
|
if group_type != got_group_type:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}" - An object '
|
|
f'{named} at "{res[0].dn}" already exists, but it '
|
|
f'is not a security group. Rename or remove this '
|
|
f'existing object before attempting to add this '
|
|
f'special group.')
|
|
|
|
sid = res[0].get('objectSid', idx=0)
|
|
if sid is None:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}" - A security '
|
|
f'group {named} at "{res[0].dn}" already exists, '
|
|
f'but it lacks a SID. Rename or remove this '
|
|
f'existing object before attempting to add this '
|
|
f'special group.')
|
|
else:
|
|
sid = ndr_unpack(security.dom_sid, sid)
|
|
if sid == object_sid:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}" - The '
|
|
f'security group {named} at "{res[0].dn}" '
|
|
f'already exists.')
|
|
else:
|
|
raise CommandError(
|
|
f'Failed to add group "{groupname}" - A '
|
|
f'security group {named} at "{res[0].dn}" '
|
|
f'already exists, but it has the wrong SID, '
|
|
f'and will not function as expected. Rename '
|
|
f'or remove this existing object before '
|
|
f'attempting to add this special group.')
|
|
else:
|
|
raise CommandError(f'Failed to add group "{groupname}"', e)
|
|
else:
|
|
self.outf.write(f'Added group {groupname}\n')
|
|
|
|
return
|
|
|
|
try:
|
|
samdb.newgroup(groupname, groupou=groupou, grouptype=gtype,
|
|
description=description, mailaddress=mail_address, notes=notes,
|
|
gidnumber=gid_number, nisdomain=nis_domain)
|
|
except Exception as e:
|
|
# FIXME: catch more specific exception
|
|
raise CommandError('Failed to add group "%s"' % groupname, e)
|
|
self.outf.write("Added group %s\n" % groupname)
|
|
|
|
|
|
class cmd_group_delete(Command):
|
|
"""Deletes an AD group.
|
|
|
|
The command deletes an existing AD group from the Active Directory domain. The groupname specified on the command is the sAMAccountName.
|
|
|
|
Deleting a group is a permanent operation. When a group is deleted, all permissions and rights that users in the group had inherited from the group account are deleted as well.
|
|
|
|
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 on a remote server.
|
|
|
|
Example1:
|
|
samba-tool group delete Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
|
|
|
|
Example1 shows how to delete an AD group from a remote LDAP server. The -U parameter is used to pass the userid and password of a user that exists on the remote server and is authorized to issue the command on that server.
|
|
|
|
Example2:
|
|
sudo samba-tool group delete Group2
|
|
|
|
Example2 deletes group Group2 from the local server. The command is run under root using the sudo command.
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> [options]"
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"versionopts": options.VersionOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
}
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
]
|
|
|
|
takes_args = ["groupname"]
|
|
|
|
def run(self, groupname, 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)
|
|
|
|
filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
|
|
ldb.binary_encode(groupname))
|
|
|
|
try:
|
|
res = samdb.search(base=samdb.domain_dn(),
|
|
scope=ldb.SCOPE_SUBTREE,
|
|
expression=filter,
|
|
attrs=["dn"])
|
|
group_dn = res[0].dn
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
try:
|
|
samdb.delete(group_dn)
|
|
except Exception as e:
|
|
# FIXME: catch more specific exception
|
|
raise CommandError('Failed to remove group "%s"' % groupname, e)
|
|
self.outf.write("Deleted group %s\n" % groupname)
|
|
|
|
|
|
class cmd_group_add_members(Command):
|
|
"""Add members to an AD group.
|
|
|
|
This command adds one or more members to an existing Active Directory group. The command accepts one or more group member names separated by commas. A group member may be a user or computer account or another Active Directory group.
|
|
|
|
When a member is added to a group the member may inherit permissions and rights from the group. Likewise, when permission or rights of a group are changed, the changes may reflect in the members through inheritance.
|
|
|
|
The member names specified on the command must be the sAMaccountName.
|
|
|
|
Example1:
|
|
samba-tool group addmembers supergroup Group1,Group2,User1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
|
|
|
|
Example1 shows how to add two groups, Group1 and Group2 and one user account, User1, to the existing AD group named supergroup. The command will be run on a remote server specified with the -H. The -U parameter is used to pass the userid and password of a user authorized to issue the command on the remote server.
|
|
|
|
Example2:
|
|
sudo samba-tool group addmembers supergroup User2
|
|
|
|
Example2 shows how to add a single user account, User2, to the supergroup AD group. It uses the sudo command to run as root when issuing the command.
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> (<listofmembers>]|--member-dn=<member-dn>) [options]"
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"versionopts": options.VersionOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
}
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
Option("--member-dn",
|
|
help=("DN of the new group member to be added.\n"
|
|
"The --object-types option will be ignored."),
|
|
type=str,
|
|
action="append"),
|
|
Option("--object-types",
|
|
help=("Comma separated list of object types.\n"
|
|
"The types are used to filter the search for the "
|
|
"specified members.\n"
|
|
"Valid values are: user, group, computer, serviceaccount, "
|
|
"contact and all.\n"
|
|
"Default: user,group,computer"),
|
|
default="user,group,computer",
|
|
type=str),
|
|
Option("--member-base-dn",
|
|
help=("Base DN for group member search.\n"
|
|
"Default is the domain DN."),
|
|
type=str),
|
|
]
|
|
|
|
takes_args = ["groupname", "listofmembers?"]
|
|
|
|
def run(self,
|
|
groupname,
|
|
listofmembers=None,
|
|
credopts=None,
|
|
sambaopts=None,
|
|
versionopts=None,
|
|
H=None,
|
|
member_base_dn=None,
|
|
member_dn=None,
|
|
object_types="user,group,computer"):
|
|
|
|
lp = sambaopts.get_loadparm()
|
|
creds = credopts.get_credentials(lp, fallback_machine=True)
|
|
|
|
if member_dn is None and listofmembers is None:
|
|
self.usage()
|
|
raise CommandError(
|
|
'Either listofmembers or --member-dn must be specified.')
|
|
|
|
try:
|
|
samdb = SamDB(url=H, session_info=system_session(),
|
|
credentials=creds, lp=lp)
|
|
groupmembers = []
|
|
if member_dn is not None:
|
|
groupmembers += member_dn
|
|
if listofmembers is not None:
|
|
groupmembers += listofmembers.split(',')
|
|
group_member_types = object_types.split(',')
|
|
|
|
if member_base_dn is not None:
|
|
member_base_dn = samdb.normalize_dn_in_domain(member_base_dn)
|
|
|
|
samdb.add_remove_group_members(groupname, groupmembers,
|
|
add_members_operation=True,
|
|
member_types=group_member_types,
|
|
member_base_dn=member_base_dn)
|
|
except Exception as e:
|
|
# FIXME: catch more specific exception
|
|
raise CommandError('Failed to add members %r to group "%s" - %s' % (
|
|
groupmembers, groupname, e))
|
|
self.outf.write("Added members to group %s\n" % groupname)
|
|
|
|
|
|
class cmd_group_remove_members(Command):
|
|
"""Remove members from an AD group.
|
|
|
|
This command removes one or more members from an existing Active Directory group. The command accepts one or more group member names separated by commas. A group member may be a user or computer account or another Active Directory group that is a member of the group specified on the command.
|
|
|
|
When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
|
|
|
|
Example1:
|
|
samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
|
|
|
|
Example1 shows how to remove Group1 from supergroup. The command will run on the remote server specified on the -H parameter. The -U parameter is used to pass the userid and password of a user authorized to issue the command on the remote server.
|
|
|
|
Example2:
|
|
sudo samba-tool group removemembers supergroup User1
|
|
|
|
Example2 shows how to remove a single user account, User2, from the supergroup AD group. It uses the sudo command to run as root when issuing the command.
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> (<listofmembers>]|--member-dn=<member-dn>) [options]"
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"versionopts": options.VersionOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
}
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
Option("--member-dn",
|
|
help=("DN of the group member to be removed.\n"
|
|
"The --object-types option will be ignored."),
|
|
type=str,
|
|
action="append"),
|
|
Option("--object-types",
|
|
help=("Comma separated list of object types.\n"
|
|
"The types are used to filter the search for the "
|
|
"specified members.\n"
|
|
"Valid values are: user, group, computer, serviceaccount, "
|
|
"contact and all.\n"
|
|
"Default: user,group,computer"),
|
|
default="user,group,computer",
|
|
type=str),
|
|
Option("--member-base-dn",
|
|
help=("Base DN for group member search.\n"
|
|
"Default is the domain DN."),
|
|
type=str),
|
|
]
|
|
|
|
takes_args = ["groupname", "listofmembers?"]
|
|
|
|
def run(self,
|
|
groupname,
|
|
listofmembers=None,
|
|
credopts=None,
|
|
sambaopts=None,
|
|
versionopts=None,
|
|
H=None,
|
|
member_base_dn=None,
|
|
member_dn=None,
|
|
object_types="user,group,computer"):
|
|
|
|
lp = sambaopts.get_loadparm()
|
|
creds = credopts.get_credentials(lp, fallback_machine=True)
|
|
|
|
if member_dn is None and listofmembers is None:
|
|
self.usage()
|
|
raise CommandError(
|
|
'Either listofmembers or --member-dn must be specified.')
|
|
|
|
try:
|
|
samdb = SamDB(url=H, session_info=system_session(),
|
|
credentials=creds, lp=lp)
|
|
groupmembers = []
|
|
if member_dn is not None:
|
|
groupmembers += member_dn
|
|
if listofmembers is not None:
|
|
groupmembers += listofmembers.split(',')
|
|
group_member_types = object_types.split(',')
|
|
|
|
if member_base_dn is not None:
|
|
member_base_dn = samdb.normalize_dn_in_domain(member_base_dn)
|
|
|
|
samdb.add_remove_group_members(groupname,
|
|
groupmembers,
|
|
add_members_operation=False,
|
|
member_types=group_member_types,
|
|
member_base_dn=member_base_dn)
|
|
except Exception as e:
|
|
# FIXME: Catch more specific exception
|
|
raise CommandError('Failed to remove members %r from group "%s"' % (listofmembers, groupname), e)
|
|
self.outf.write("Removed members from group %s\n" % groupname)
|
|
|
|
|
|
class cmd_group_list(Command):
|
|
"""List all groups."""
|
|
|
|
synopsis = "%prog [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
Option("-v", "--verbose",
|
|
help="Verbose output, showing group type and group scope.",
|
|
action="store_true"),
|
|
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 DN instead of the sAMAccountName."),
|
|
]
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def run(self,
|
|
sambaopts=None,
|
|
credopts=None,
|
|
versionopts=None,
|
|
H=None,
|
|
verbose=False,
|
|
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)
|
|
attrs=["samaccountname"]
|
|
|
|
if verbose:
|
|
attrs += ["grouptype", "member"]
|
|
domain_dn = samdb.domain_dn()
|
|
if base_dn:
|
|
domain_dn = samdb.normalize_dn_in_domain(base_dn)
|
|
res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
|
|
expression=("(objectClass=group)"),
|
|
attrs=attrs)
|
|
if (len(res) == 0):
|
|
return
|
|
|
|
if verbose:
|
|
self.outf.write("Group Name Group Type Group Scope Members\n")
|
|
self.outf.write("--------------------------------------------------------------------------------\n")
|
|
|
|
for msg in res:
|
|
self.outf.write("%-44s" % msg.get("samaccountname", idx=0))
|
|
hgtype = hex(int("%s" % msg["grouptype"]) & 0x00000000FFFFFFFF)
|
|
if (hgtype == hex(int(security_group.get("Builtin")))):
|
|
self.outf.write("Security Builtin ")
|
|
elif (hgtype == hex(int(security_group.get("Domain")))):
|
|
self.outf.write("Security Domain ")
|
|
elif (hgtype == hex(int(security_group.get("Global")))):
|
|
self.outf.write("Security Global ")
|
|
elif (hgtype == hex(int(security_group.get("Universal")))):
|
|
self.outf.write("Security Universal")
|
|
elif (hgtype == hex(int(distribution_group.get("Global")))):
|
|
self.outf.write("Distribution Global ")
|
|
elif (hgtype == hex(int(distribution_group.get("Domain")))):
|
|
self.outf.write("Distribution Domain ")
|
|
elif (hgtype == hex(int(distribution_group.get("Universal")))):
|
|
self.outf.write("Distribution Universal")
|
|
else:
|
|
self.outf.write(" ")
|
|
num_members = len(msg.get("member", default=[]))
|
|
self.outf.write(" %6u\n" % num_members)
|
|
else:
|
|
for msg in res:
|
|
if full_dn:
|
|
self.outf.write("%s\n" % msg.get("dn"))
|
|
continue
|
|
|
|
self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
|
|
|
|
|
|
class cmd_group_list_members(Command):
|
|
"""List all members of an AD group.
|
|
|
|
This command lists members from an existing Active Directory group. The command accepts one group name.
|
|
|
|
Example1:
|
|
samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
Option("--hide-expired",
|
|
help="Do not list expired group members",
|
|
default=False,
|
|
action='store_true'),
|
|
Option("--hide-disabled",
|
|
default=False,
|
|
action='store_true',
|
|
help="Do not list disabled group members"),
|
|
Option("--full-dn", dest="full_dn",
|
|
default=False,
|
|
action='store_true',
|
|
help="Display DN instead of the sAMAccountName.")
|
|
]
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
takes_args = ["groupname"]
|
|
|
|
def run(self,
|
|
groupname,
|
|
credopts=None,
|
|
sambaopts=None,
|
|
versionopts=None,
|
|
H=None,
|
|
hide_expired=False,
|
|
hide_disabled=False,
|
|
full_dn=False):
|
|
lp = sambaopts.get_loadparm()
|
|
creds = credopts.get_credentials(lp, fallback_machine=True)
|
|
|
|
try:
|
|
samdb = SamDB(url=H, session_info=system_session(),
|
|
credentials=creds, lp=lp)
|
|
|
|
search_filter = ("(&(objectClass=group)(sAMAccountName=%s))" %
|
|
ldb.binary_encode(groupname))
|
|
try:
|
|
res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
|
|
expression=(search_filter),
|
|
attrs=["objectSid"])
|
|
group_sid_binary = res[0].get('objectSid', idx=0)
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
group_sid = ndr_unpack(security.dom_sid, group_sid_binary)
|
|
(group_dom_sid, rid) = group_sid.split()
|
|
group_sid_dn = "<SID=%s>" % (group_sid)
|
|
|
|
filter_expires = ""
|
|
if hide_expired is True:
|
|
current_nttime = samdb.get_nttime()
|
|
filter_expires = ("(|"
|
|
"(!(accountExpires=*))"
|
|
"(accountExpires=0)"
|
|
"(accountExpires>=%u)"
|
|
")" % (current_nttime))
|
|
|
|
filter_disabled = ""
|
|
if hide_disabled is True:
|
|
filter_disabled = "(!(userAccountControl:%s:=%u))" % (
|
|
ldb.OID_COMPARATOR_AND, UF_ACCOUNTDISABLE)
|
|
|
|
filter = "(&(|(primaryGroupID=%s)(memberOf=%s))%s%s)" % (
|
|
rid, group_sid_dn, filter_disabled, filter_expires)
|
|
|
|
res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
|
|
expression=filter,
|
|
attrs=["samAccountName", "cn"])
|
|
|
|
if (len(res) == 0):
|
|
return
|
|
|
|
for msg in res:
|
|
if full_dn:
|
|
self.outf.write("%s\n" % msg.get("dn"))
|
|
continue
|
|
|
|
member_name = msg.get("samAccountName", idx=0)
|
|
if member_name is None:
|
|
member_name = msg.get("cn", idx=0)
|
|
self.outf.write("%s\n" % member_name)
|
|
|
|
except Exception as e:
|
|
raise CommandError('Failed to list members of "%s" group - %s' %
|
|
(groupname, e))
|
|
|
|
|
|
class cmd_group_move(Command):
|
|
"""Move a group to an organizational unit/container.
|
|
|
|
This command moves a group object into the specified organizational unit
|
|
or container.
|
|
The groupname specified on the command is the sAMAccountName.
|
|
The name of the organizational unit or container can be specified as a
|
|
full DN or without the domainDN component.
|
|
|
|
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 move Group1 'OU=OrgUnit,DC=samdom.DC=example,DC=com' \\
|
|
-H ldap://samba.samdom.example.com -U administrator
|
|
|
|
Example1 shows how to move a group Group1 into the 'OrgUnit' organizational
|
|
unit on a remote LDAP server.
|
|
|
|
The -H parameter is used to specify the remote target server.
|
|
|
|
Example2:
|
|
samba-tool group move Group1 CN=Users
|
|
|
|
Example2 shows how to move a group Group1 back into the CN=Users container
|
|
on the local server.
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> <new_parent_dn> [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server",
|
|
type=str, metavar="URL", dest="H"),
|
|
]
|
|
|
|
takes_args = ["groupname", "new_parent_dn"]
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def run(self, groupname, 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())
|
|
|
|
filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
|
|
ldb.binary_encode(groupname))
|
|
try:
|
|
res = samdb.search(base=domain_dn,
|
|
expression=filter,
|
|
scope=ldb.SCOPE_SUBTREE)
|
|
group_dn = res[0].dn
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
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.message))
|
|
|
|
full_new_group_dn = ldb.Dn(samdb, str(group_dn))
|
|
full_new_group_dn.remove_base_components(len(group_dn) - 1)
|
|
full_new_group_dn.add_base(full_new_parent_dn)
|
|
|
|
try:
|
|
samdb.rename(group_dn, full_new_group_dn)
|
|
except Exception as e:
|
|
raise CommandError('Failed to move group "%s"' % groupname, e)
|
|
self.outf.write('Moved group "%s" into "%s"\n' %
|
|
(groupname, full_new_parent_dn))
|
|
|
|
|
|
class cmd_group_show(Command):
|
|
"""Display a group AD object.
|
|
|
|
This command displays a group object and it's attributes in the Active
|
|
Directory domain.
|
|
The group name specified on the command is the sAMAccountName of the group.
|
|
|
|
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 show Group1 -H ldap://samba.samdom.example.com \\
|
|
-U administrator --password=passw1rd
|
|
|
|
Example1 shows how to display a group's attributes in the domain against a
|
|
remote LDAP server.
|
|
|
|
The -H parameter is used to specify the remote target server.
|
|
|
|
Example2:
|
|
samba-tool group show Group2
|
|
|
|
Example2 shows how to display a group's attributes in the domain against a local
|
|
LDAP server.
|
|
|
|
Example3:
|
|
samba-tool group show Group3 --attributes=member,objectGUID
|
|
|
|
Example3 shows how to display a groups objectGUID and member attributes.
|
|
"""
|
|
synopsis = "%prog <group name> [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server",
|
|
type=str, metavar="URL", dest="H"),
|
|
Option("--attributes",
|
|
help=("Comma separated list of attributes, "
|
|
"which will be printed."),
|
|
type=str, dest="group_attrs"),
|
|
]
|
|
|
|
takes_args = ["groupname"]
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
|
|
H=None, group_attrs=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)
|
|
|
|
attrs = None
|
|
if group_attrs:
|
|
attrs = group_attrs.split(",")
|
|
|
|
filter = ("(&(objectCategory=group)(sAMAccountName=%s))" %
|
|
ldb.binary_encode(groupname))
|
|
|
|
domaindn = samdb.domain_dn()
|
|
|
|
try:
|
|
res = samdb.search(base=domaindn, expression=filter,
|
|
scope=ldb.SCOPE_SUBTREE, attrs=attrs)
|
|
user_dn = res[0].dn
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
for msg in res:
|
|
group_ldif = common.get_ldif_for_editor(samdb, msg)
|
|
self.outf.write(group_ldif)
|
|
|
|
|
|
class cmd_group_stats(Command):
|
|
"""Summary statistics about group memberships."""
|
|
|
|
synopsis = "%prog [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server", type=str,
|
|
metavar="URL", dest="H"),
|
|
]
|
|
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def num_in_range(self, range_min, range_max, group_freqs):
|
|
total_count = 0
|
|
for members, count in group_freqs.items():
|
|
if range_min <= members and members <= range_max:
|
|
total_count += count
|
|
|
|
return total_count
|
|
|
|
def run(self, sambaopts=None, credopts=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 = samdb.domain_dn()
|
|
res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
|
|
expression=("(objectClass=group)"),
|
|
attrs=["samaccountname", "member"])
|
|
|
|
# first count up how many members each group has
|
|
group_assignments = {}
|
|
total_memberships = 0
|
|
|
|
for msg in res:
|
|
name = str(msg.get("samaccountname"))
|
|
num_members = len(msg.get("member", default=[]))
|
|
group_assignments[name] = num_members
|
|
total_memberships += num_members
|
|
|
|
num_groups = res.count
|
|
self.outf.write("Group membership statistics*\n")
|
|
self.outf.write("-------------------------------------------------\n")
|
|
self.outf.write("Total groups: {0}\n".format(num_groups))
|
|
self.outf.write("Total memberships: {0}\n".format(total_memberships))
|
|
average = total_memberships / float(num_groups)
|
|
self.outf.write("Average members per group: %.2f\n" % average)
|
|
|
|
# find the max and median memberships (note that some default groups
|
|
# always have zero members, so displaying the min is not very helpful)
|
|
group_names = list(group_assignments.keys())
|
|
group_members = list(group_assignments.values())
|
|
idx = group_members.index(max(group_members))
|
|
max_members = group_members[idx]
|
|
self.outf.write("Max members: {0} ({1})\n".format(max_members,
|
|
group_names[idx]))
|
|
group_members.sort()
|
|
midpoint = num_groups // 2
|
|
median = group_members[midpoint]
|
|
if num_groups % 2 == 0:
|
|
median = (median + group_members[midpoint - 1]) / 2
|
|
self.outf.write("Median members per group: {0}\n\n".format(median))
|
|
|
|
# convert this to the frequency of group membership, i.e. how many
|
|
# groups have 5 members, how many have 6 members, etc
|
|
group_freqs = defaultdict(int)
|
|
for group, num_members in group_assignments.items():
|
|
group_freqs[num_members] += 1
|
|
|
|
# now squash this down even further, so that we just display the number
|
|
# of groups that fall into one of the following membership bands
|
|
bands = [(0, 1), (2, 4), (5, 9), (10, 14), (15, 19), (20, 24),
|
|
(25, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79),
|
|
(80, 89), (90, 99), (100, 149), (150, 199), (200, 249),
|
|
(250, 299), (300, 399), (400, 499), (500, 999), (1000, 1999),
|
|
(2000, 2999), (3000, 3999), (4000, 4999), (5000, 9999),
|
|
(10000, max_members)]
|
|
|
|
self.outf.write("Members Number of Groups\n")
|
|
self.outf.write("-------------------------------------------------\n")
|
|
|
|
for band in bands:
|
|
band_start = band[0]
|
|
band_end = band[1]
|
|
if band_start > max_members:
|
|
break
|
|
|
|
num_groups = self.num_in_range(band_start, band_end, group_freqs)
|
|
|
|
if num_groups != 0:
|
|
band_str = "{0}-{1}".format(band_start, band_end)
|
|
self.outf.write("%13s %u\n" % (band_str, num_groups))
|
|
|
|
self.outf.write("\n* Note this does not include nested group memberships\n")
|
|
|
|
|
|
class cmd_group_edit(Command):
|
|
"""Modify Group AD object.
|
|
|
|
This command will allow editing of a group account in the Active Directory
|
|
domain. You will then be able to add or change attributes and their values.
|
|
|
|
The groupname specified on the command is the sAMAccountName.
|
|
|
|
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 edit Group1 -H ldap://samba.samdom.example.com \\
|
|
-U administrator --password=passw1rd
|
|
|
|
Example1 shows how to edit a groups attributes in the domain against a
|
|
remote LDAP server.
|
|
|
|
The -H parameter is used to specify the remote target server.
|
|
|
|
Example2:
|
|
samba-tool group edit Group2
|
|
|
|
Example2 shows how to edit a groups attributes in the domain against a local
|
|
server.
|
|
|
|
Example3:
|
|
samba-tool group edit Group3 --editor=nano
|
|
|
|
Example3 shows how to edit a groups attributes in the domain against a local
|
|
server using the 'nano' editor.
|
|
"""
|
|
synopsis = "%prog <groupname> [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL", help="LDB URL for database or target server",
|
|
type=str, metavar="URL", dest="H"),
|
|
Option("--editor", help="Editor to use instead of the system default,"
|
|
" or 'vi' if no system default is set.", type=str),
|
|
]
|
|
|
|
takes_args = ["groupname"]
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
|
|
H=None, editor=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)
|
|
|
|
filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
|
|
ldb.binary_encode(groupname))
|
|
|
|
domaindn = samdb.domain_dn()
|
|
|
|
try:
|
|
res = samdb.search(base=domaindn,
|
|
expression=filter,
|
|
scope=ldb.SCOPE_SUBTREE)
|
|
group_dn = res[0].dn
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
if len(res) != 1:
|
|
raise CommandError('Invalid number of results: for "%s": %d' %
|
|
((groupname), len(res)))
|
|
|
|
msg = res[0]
|
|
result_ldif = common.get_ldif_for_editor(samdb, msg)
|
|
|
|
if editor is None:
|
|
editor = os.environ.get('EDITOR')
|
|
if editor is None:
|
|
editor = 'vi'
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".tmp") as t_file:
|
|
t_file.write(get_bytes(result_ldif))
|
|
t_file.flush()
|
|
try:
|
|
check_call([editor, t_file.name])
|
|
except CalledProcessError as e:
|
|
raise CalledProcessError("ERROR: ", e)
|
|
with open(t_file.name) as edited_file:
|
|
edited_message = edited_file.read()
|
|
|
|
msgs_edited = samdb.parse_ldif(edited_message)
|
|
msg_edited = next(msgs_edited)[1]
|
|
|
|
res_msg_diff = samdb.msg_diff(msg, msg_edited)
|
|
if len(res_msg_diff) == 0:
|
|
self.outf.write("Nothing to do\n")
|
|
return
|
|
|
|
try:
|
|
samdb.modify(res_msg_diff)
|
|
except Exception as e:
|
|
raise CommandError("Failed to modify group '%s': " % groupname, e)
|
|
|
|
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 <groupname> <gidnumber> [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_rename(Command):
|
|
"""Rename a group and related attributes.
|
|
|
|
This command allows to set the group's name related attributes. The
|
|
group's CN will be renamed automatically.
|
|
|
|
The group's CN will be the sAMAccountName.
|
|
Use the --force-new-cn option to specify the new CN manually and the
|
|
--reset-cn to reset this change.
|
|
|
|
Use an empty attribute value to remove the specified attribute.
|
|
|
|
The groupname specified on the command is the sAMAccountName.
|
|
|
|
The command may be run locally 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 rename employees --samaccountname=staff
|
|
|
|
Example1 shows how to change the samaccountname of a group 'employees' to
|
|
'staff'. The CN of the group employees will also be changed to 'staff',
|
|
if the previous CN was the previous sAMAccountName.
|
|
|
|
Example2:
|
|
samba-tool group rename employees --mail-address='staff@company.com' \\
|
|
-H ldap://samba.samdom.example.com -U administrator
|
|
|
|
Example2 shows how to rename the mail address of a group 'employees' to
|
|
'staff@company.com'.
|
|
The -H parameter is used to specify the remote target server.
|
|
"""
|
|
|
|
synopsis = "%prog <groupname> [options]"
|
|
|
|
takes_options = [
|
|
Option("-H", "--URL",
|
|
help="LDB URL for database or target server",
|
|
type=str, metavar="URL", dest="H"),
|
|
Option("--force-new-cn",
|
|
help="Specify a new CN (RND) instead of using the sAMAccountName.",
|
|
type=str),
|
|
Option("--reset-cn",
|
|
help="Set the CN (RDN) to the sAMAccountName. Use this option "
|
|
"to reset the changes made with the --force-new-cn option.",
|
|
action="store_true"),
|
|
Option("--mail-address",
|
|
help="New mail address",
|
|
type=str),
|
|
Option("--samaccountname",
|
|
help="New account name (sAMAccountName/logon name)",
|
|
type=str)
|
|
]
|
|
|
|
takes_args = ["groupname"]
|
|
takes_optiongroups = {
|
|
"sambaopts": options.SambaOptions,
|
|
"credopts": options.CredentialsOptions,
|
|
"versionopts": options.VersionOptions,
|
|
}
|
|
|
|
def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
|
|
H=None, mail_address=None, samaccountname=None, force_new_cn=None,
|
|
reset_cn=None):
|
|
# illegal options
|
|
if force_new_cn and reset_cn:
|
|
raise CommandError("It is not allowed to specify --force-new-cn "
|
|
"together with --reset-cn.")
|
|
if force_new_cn == "":
|
|
raise CommandError("Failed to rename group - delete protected "
|
|
"attribute 'CN'")
|
|
if samaccountname == "":
|
|
raise CommandError("Failed to rename group - delete protected "
|
|
"attribute 'sAMAccountName'")
|
|
|
|
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())
|
|
|
|
filter = ("(&(objectClass=group)(samaccountname=%s))" %
|
|
ldb.binary_encode(groupname))
|
|
try:
|
|
res = samdb.search(base=domain_dn,
|
|
scope=ldb.SCOPE_SUBTREE,
|
|
expression=filter,
|
|
attrs=["sAMAccountName",
|
|
"cn",
|
|
"mail"]
|
|
)
|
|
old_group = res[0]
|
|
group_dn = old_group.dn
|
|
except IndexError:
|
|
raise CommandError('Unable to find group "%s"' % (groupname))
|
|
|
|
group_parent_dn = group_dn.parent()
|
|
old_cn = old_group["cn"][0]
|
|
|
|
# get the actual and the new group cn and the new dn
|
|
if force_new_cn is not None:
|
|
new_cn = force_new_cn
|
|
elif samaccountname is not None:
|
|
new_cn = samaccountname
|
|
else:
|
|
new_cn = old_group["sAMAccountName"]
|
|
|
|
# CN must change, if the new CN is different and the old CN is the
|
|
# standard CN or the change is forced with force-new-cn or reset-cn
|
|
expected_cn = old_group["sAMAccountName"]
|
|
must_change_cn = str(old_cn) != str(new_cn) and \
|
|
(str(old_cn) == str(expected_cn) or \
|
|
reset_cn or bool(force_new_cn))
|
|
|
|
new_group_dn = ldb.Dn(samdb, "CN=%s" % new_cn)
|
|
new_group_dn.add_base(group_parent_dn)
|
|
|
|
# format given attributes
|
|
group_attrs = ldb.Message()
|
|
group_attrs.dn = group_dn
|
|
samdb.prepare_attr_replace(group_attrs, old_group, "sAMAccountName",
|
|
samaccountname)
|
|
samdb.prepare_attr_replace(group_attrs, old_group, "mail", mail_address)
|
|
|
|
group_attributes_changed = len(group_attrs) > 0
|
|
|
|
# update the group with formatted attributes
|
|
samdb.transaction_start()
|
|
try:
|
|
if group_attributes_changed:
|
|
samdb.modify(group_attrs)
|
|
if must_change_cn:
|
|
samdb.rename(group_dn, new_group_dn)
|
|
except Exception as e:
|
|
samdb.transaction_cancel()
|
|
raise CommandError('Failed to rename group "%s"' % groupname, e)
|
|
samdb.transaction_commit()
|
|
|
|
if must_change_cn:
|
|
self.outf.write('Renamed CN of group "%s" from "%s" to "%s" '
|
|
'successfully\n' % (groupname, old_cn, new_cn))
|
|
|
|
if group_attributes_changed:
|
|
self.outf.write('Following attributes of group "%s" have been '
|
|
'changed successfully:\n' % (groupname))
|
|
for attr in group_attrs.keys():
|
|
if attr == "dn":
|
|
continue
|
|
self.outf.write('%s: %s\n' % (attr, group_attrs[attr]
|
|
if group_attrs[attr] else '[removed]'))
|
|
|
|
class cmd_group(SuperCommand):
|
|
"""Group management."""
|
|
|
|
subcommands = {}
|
|
subcommands["add"] = cmd_group_add()
|
|
subcommands["create"] = cmd_group_add()
|
|
subcommands["delete"] = cmd_group_delete()
|
|
subcommands["edit"] = cmd_group_edit()
|
|
subcommands["addmembers"] = cmd_group_add_members()
|
|
subcommands["removemembers"] = cmd_group_remove_members()
|
|
subcommands["list"] = cmd_group_list()
|
|
subcommands["listmembers"] = cmd_group_list_members()
|
|
subcommands["move"] = cmd_group_move()
|
|
subcommands["show"] = cmd_group_show()
|
|
subcommands["stats"] = cmd_group_stats()
|
|
subcommands["addunixattrs"] = cmd_group_add_unix_attrs()
|
|
subcommands["rename"] = cmd_group_rename()
|