1 # Copyright Jelmer Vernooij 2008
3 # Based on the original in EJS:
4 # Copyright Andrew Tridgell 2005
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import samba.getopt as options
20 from samba.netcmd import Command, SuperCommand, CommandError, Option
22 from samba.ndr import ndr_unpack
23 from samba.dcerpc import security
25 from samba.auth import system_session
26 from samba.samdb import SamDB
27 from samba.dsdb import (
28 ATYPE_SECURITY_GLOBAL_GROUP,
29 GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
30 GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
31 GTYPE_SECURITY_GLOBAL_GROUP,
32 GTYPE_SECURITY_UNIVERSAL_GROUP,
33 GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
34 GTYPE_DISTRIBUTION_GLOBAL_GROUP,
35 GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
37 from collections import defaultdict
38 from subprocess import check_call, CalledProcessError
39 from samba.compat import get_bytes
44 security_group = dict({"Builtin": GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
45 "Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
46 "Global": GTYPE_SECURITY_GLOBAL_GROUP,
47 "Universal": GTYPE_SECURITY_UNIVERSAL_GROUP})
48 distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
49 "Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP,
50 "Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP})
53 class cmd_group_add(Command):
54 """Creates a new AD group.
56 This command creates a new Active Directory group. The groupname specified on the command is a unique sAMAccountName.
58 An Active Directory group may contain user and computer accounts as well as other groups. An administrator creates a group and adds members to that group so they can be managed as a single entity. This helps to simplify security and system administration.
60 Groups may also be used to establish email distribution lists, using --group-type=Distribution.
62 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.
64 The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
66 The command may be run from the root userid or another authorized userid. The
67 -H or --URL= option can be used to execute the command on a remote server.
70 samba-tool group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
72 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.
75 sudo samba-tool group add Group2 --group-type=Distribution
77 Example2 adds a new distribution group to the local server. The command is run under root using the sudo command.
80 samba-tool group add Group3 --nis-domain=samdom --gid-number=12345
82 Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (both options are required to enable this feature).
85 synopsis = "%prog <groupname> [options]"
87 takes_optiongroups = {
88 "sambaopts": options.SambaOptions,
89 "versionopts": options.VersionOptions,
90 "credopts": options.CredentialsOptions,
94 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
95 metavar="URL", dest="H"),
97 help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
99 Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
100 help="Group scope (Domain | Global | Universal)"),
101 Option("--group-type", type="choice", choices=["Security", "Distribution"],
102 help="Group type (Security | Distribution)"),
103 Option("--description", help="Group's description", type=str),
104 Option("--mail-address", help="Group's email address", type=str),
105 Option("--notes", help="Groups's notes", type=str),
106 Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
107 Option("--nis-domain", help="SFU30 NIS Domain", type=str),
110 takes_args = ["groupname"]
112 def run(self, groupname, credopts=None, sambaopts=None,
113 versionopts=None, H=None, groupou=None, group_scope=None,
114 group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None):
116 if (group_type or "Security") == "Security":
117 gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
119 gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
121 if (gid_number is None and nis_domain is not None) or (gid_number is not None and nis_domain is None):
122 raise CommandError('Both --gid-number and --nis-domain have to be set for a RFC2307-enabled group. Operation cancelled.')
124 lp = sambaopts.get_loadparm()
125 creds = credopts.get_credentials(lp, fallback_machine=True)
128 samdb = SamDB(url=H, session_info=system_session(),
129 credentials=creds, lp=lp)
130 samdb.newgroup(groupname, groupou=groupou, grouptype=gtype,
131 description=description, mailaddress=mail_address, notes=notes,
132 gidnumber=gid_number, nisdomain=nis_domain)
133 except Exception as e:
134 # FIXME: catch more specific exception
135 raise CommandError('Failed to create group "%s"' % groupname, e)
136 self.outf.write("Added group %s\n" % groupname)
139 class cmd_group_delete(Command):
140 """Deletes an AD group.
142 The command deletes an existing AD group from the Active Directory domain. The groupname specified on the command is the sAMAccountName.
144 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.
146 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.
149 samba-tool group delete Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
151 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.
154 sudo samba-tool group delete Group2
156 Example2 deletes group Group2 from the local server. The command is run under root using the sudo command.
159 synopsis = "%prog <groupname> [options]"
161 takes_optiongroups = {
162 "sambaopts": options.SambaOptions,
163 "versionopts": options.VersionOptions,
164 "credopts": options.CredentialsOptions,
168 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
169 metavar="URL", dest="H"),
172 takes_args = ["groupname"]
174 def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
176 lp = sambaopts.get_loadparm()
177 creds = credopts.get_credentials(lp, fallback_machine=True)
178 samdb = SamDB(url=H, session_info=system_session(),
179 credentials=creds, lp=lp)
181 filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
185 res = samdb.search(base=samdb.domain_dn(),
186 scope=ldb.SCOPE_SUBTREE,
191 raise CommandError('Unable to find group "%s"' % (groupname))
194 samdb.delete(group_dn)
195 except Exception as e:
196 # FIXME: catch more specific exception
197 raise CommandError('Failed to remove group "%s"' % groupname, e)
198 self.outf.write("Deleted group %s\n" % groupname)
201 class cmd_group_add_members(Command):
202 """Add members to an AD group.
204 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.
206 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.
208 The member names specified on the command must be the sAMaccountName.
211 samba-tool group addmembers supergroup Group1,Group2,User1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
213 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.
216 sudo samba-tool group addmembers supergroup User2
218 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.
221 synopsis = "%prog <groupname> <listofmembers> [options]"
223 takes_optiongroups = {
224 "sambaopts": options.SambaOptions,
225 "versionopts": options.VersionOptions,
226 "credopts": options.CredentialsOptions,
230 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
231 metavar="URL", dest="H"),
234 takes_args = ["groupname", "listofmembers"]
236 def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
237 versionopts=None, H=None):
239 lp = sambaopts.get_loadparm()
240 creds = credopts.get_credentials(lp, fallback_machine=True)
243 samdb = SamDB(url=H, session_info=system_session(),
244 credentials=creds, lp=lp)
245 groupmembers = listofmembers.split(',')
246 samdb.add_remove_group_members(groupname, groupmembers,
247 add_members_operation=True)
248 except Exception as e:
249 # FIXME: catch more specific exception
250 raise CommandError('Failed to add members "%s" to group "%s"' % (
251 listofmembers, groupname), e)
252 self.outf.write("Added members to group %s\n" % groupname)
255 class cmd_group_remove_members(Command):
256 """Remove members from an AD group.
258 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.
260 When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
263 samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
265 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.
268 sudo samba-tool group removemembers supergroup User1
270 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.
273 synopsis = "%prog <groupname> <listofmembers> [options]"
275 takes_optiongroups = {
276 "sambaopts": options.SambaOptions,
277 "versionopts": options.VersionOptions,
278 "credopts": options.CredentialsOptions,
282 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
283 metavar="URL", dest="H"),
286 takes_args = ["groupname", "listofmembers"]
288 def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
289 versionopts=None, H=None):
291 lp = sambaopts.get_loadparm()
292 creds = credopts.get_credentials(lp, fallback_machine=True)
295 samdb = SamDB(url=H, session_info=system_session(),
296 credentials=creds, lp=lp)
297 samdb.add_remove_group_members(groupname, listofmembers.split(","),
298 add_members_operation=False)
299 except Exception as e:
300 # FIXME: Catch more specific exception
301 raise CommandError('Failed to remove members "%s" from group "%s"' % (listofmembers, groupname), e)
302 self.outf.write("Removed members from group %s\n" % groupname)
305 class cmd_group_list(Command):
306 """List all groups."""
308 synopsis = "%prog [options]"
311 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
312 metavar="URL", dest="H"),
313 Option("-v", "--verbose",
314 help="Verbose output, showing group type and group scope.",
315 action="store_true"),
316 Option("--full-dn", dest="full_dn",
319 help="Display DN instead of the sAMAccountName."),
322 takes_optiongroups = {
323 "sambaopts": options.SambaOptions,
324 "credopts": options.CredentialsOptions,
325 "versionopts": options.VersionOptions,
335 lp = sambaopts.get_loadparm()
336 creds = credopts.get_credentials(lp, fallback_machine=True)
338 samdb = SamDB(url=H, session_info=system_session(),
339 credentials=creds, lp=lp)
340 attrs=["samaccountname"]
343 attrs += ["grouptype", "member"]
344 domain_dn = samdb.domain_dn()
345 res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
346 expression=("(objectClass=group)"),
352 self.outf.write("Group Name Group Type Group Scope Members\n")
353 self.outf.write("--------------------------------------------------------------------------------\n")
356 self.outf.write("%-44s" % msg.get("samaccountname", idx=0))
357 hgtype = hex(int("%s" % msg["grouptype"]) & 0x00000000FFFFFFFF)
358 if (hgtype == hex(int(security_group.get("Builtin")))):
359 self.outf.write("Security Builtin ")
360 elif (hgtype == hex(int(security_group.get("Domain")))):
361 self.outf.write("Security Domain ")
362 elif (hgtype == hex(int(security_group.get("Global")))):
363 self.outf.write("Security Global ")
364 elif (hgtype == hex(int(security_group.get("Universal")))):
365 self.outf.write("Security Universal")
366 elif (hgtype == hex(int(distribution_group.get("Global")))):
367 self.outf.write("Distribution Global ")
368 elif (hgtype == hex(int(distribution_group.get("Domain")))):
369 self.outf.write("Distribution Domain ")
370 elif (hgtype == hex(int(distribution_group.get("Universal")))):
371 self.outf.write("Distribution Universal")
374 num_members = len(msg.get("member", default=[]))
375 self.outf.write(" %6u\n" % num_members)
379 self.outf.write("%s\n" % msg.get("dn"))
382 self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
385 class cmd_group_list_members(Command):
386 """List all members of an AD group.
388 This command lists members from an existing Active Directory group. The command accepts one group name.
391 samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
394 synopsis = "%prog <groupname> [options]"
397 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
398 metavar="URL", dest="H"),
399 Option("--full-dn", dest="full_dn",
402 help="Display DN instead of the sAMAccountName.")
405 takes_optiongroups = {
406 "sambaopts": options.SambaOptions,
407 "credopts": options.CredentialsOptions,
408 "versionopts": options.VersionOptions,
411 takes_args = ["groupname"]
420 lp = sambaopts.get_loadparm()
421 creds = credopts.get_credentials(lp, fallback_machine=True)
424 samdb = SamDB(url=H, session_info=system_session(),
425 credentials=creds, lp=lp)
427 search_filter = "(&(objectClass=group)(samaccountname=%s))" % groupname
428 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
429 expression=(search_filter),
435 group_dn = res[0].get('dn', idx=0)
436 object_sid = res[0].get('objectSid', idx=0)
438 object_sid = ndr_unpack(security.dom_sid, object_sid)
439 (group_dom_sid, rid) = object_sid.split()
441 search_filter = "(|(primaryGroupID=%s)(memberOf=%s))" % (rid, group_dn)
442 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
443 expression=(search_filter),
444 attrs=["samAccountName", "cn"])
451 self.outf.write("%s\n" % msg.get("dn"))
454 member_name = msg.get("samAccountName", idx=0)
455 if member_name is None:
456 member_name = msg.get("cn", idx=0)
457 self.outf.write("%s\n" % member_name)
459 except Exception as e:
460 raise CommandError('Failed to list members of "%s" group ' % groupname, e)
463 class cmd_group_move(Command):
464 """Move a group to an organizational unit/container.
466 This command moves a group object into the specified organizational unit
468 The groupname specified on the command is the sAMAccountName.
469 The name of the organizational unit or container can be specified as a
470 full DN or without the domainDN component.
472 The command may be run from the root userid or another authorized userid.
474 The -H or --URL= option can be used to execute the command against a remote
478 samba-tool group move Group1 'OU=OrgUnit,DC=samdom.DC=example,DC=com' \\
479 -H ldap://samba.samdom.example.com -U administrator
481 Example1 shows how to move a group Group1 into the 'OrgUnit' organizational
482 unit on a remote LDAP server.
484 The -H parameter is used to specify the remote target server.
487 samba-tool group move Group1 CN=Users
489 Example2 shows how to move a group Group1 back into the CN=Users container
493 synopsis = "%prog <groupname> <new_parent_dn> [options]"
496 Option("-H", "--URL", help="LDB URL for database or target server",
497 type=str, metavar="URL", dest="H"),
500 takes_args = ["groupname", "new_parent_dn"]
501 takes_optiongroups = {
502 "sambaopts": options.SambaOptions,
503 "credopts": options.CredentialsOptions,
504 "versionopts": options.VersionOptions,
507 def run(self, groupname, new_parent_dn, credopts=None, sambaopts=None,
508 versionopts=None, H=None):
509 lp = sambaopts.get_loadparm()
510 creds = credopts.get_credentials(lp, fallback_machine=True)
511 samdb = SamDB(url=H, session_info=system_session(),
512 credentials=creds, lp=lp)
513 domain_dn = ldb.Dn(samdb, samdb.domain_dn())
515 filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
518 res = samdb.search(base=domain_dn,
520 scope=ldb.SCOPE_SUBTREE)
523 raise CommandError('Unable to find group "%s"' % (groupname))
526 full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn)
527 except Exception as e:
528 raise CommandError('Invalid new_parent_dn "%s": %s' %
529 (new_parent_dn, e.message))
531 full_new_group_dn = ldb.Dn(samdb, str(group_dn))
532 full_new_group_dn.remove_base_components(len(group_dn) - 1)
533 full_new_group_dn.add_base(full_new_parent_dn)
536 samdb.rename(group_dn, full_new_group_dn)
537 except Exception as e:
538 raise CommandError('Failed to move group "%s"' % groupname, e)
539 self.outf.write('Moved group "%s" into "%s"\n' %
540 (groupname, full_new_parent_dn))
543 class cmd_group_show(Command):
544 """Display a group AD object.
546 This command displays a group object and it's attributes in the Active
548 The group name specified on the command is the sAMAccountName of the group.
550 The command may be run from the root userid or another authorized userid.
552 The -H or --URL= option can be used to execute the command against a remote
556 samba-tool group show Group1 -H ldap://samba.samdom.example.com \\
557 -U administrator --password=passw1rd
559 Example1 shows how to display a group's attributes in the domain against a
562 The -H parameter is used to specify the remote target server.
565 samba-tool group show Group2
567 Example2 shows how to display a group's attributes in the domain against a local
571 samba-tool group show Group3 --attributes=member,objectGUID
573 Example3 shows how to display a groups objectGUID and member attributes.
575 synopsis = "%prog <group name> [options]"
578 Option("-H", "--URL", help="LDB URL for database or target server",
579 type=str, metavar="URL", dest="H"),
580 Option("--attributes",
581 help=("Comma separated list of attributes, "
582 "which will be printed."),
583 type=str, dest="group_attrs"),
586 takes_args = ["groupname"]
587 takes_optiongroups = {
588 "sambaopts": options.SambaOptions,
589 "credopts": options.CredentialsOptions,
590 "versionopts": options.VersionOptions,
593 def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
594 H=None, group_attrs=None):
596 lp = sambaopts.get_loadparm()
597 creds = credopts.get_credentials(lp, fallback_machine=True)
598 samdb = SamDB(url=H, session_info=system_session(),
599 credentials=creds, lp=lp)
603 attrs = group_attrs.split(",")
605 filter = ("(&(sAMAccountType=%d)(sAMAccountName=%s))" %
606 (ATYPE_SECURITY_GLOBAL_GROUP,
607 ldb.binary_encode(groupname)))
609 domaindn = samdb.domain_dn()
612 res = samdb.search(base=domaindn, expression=filter,
613 scope=ldb.SCOPE_SUBTREE, attrs=attrs)
616 raise CommandError('Unable to find group "%s"' % (groupname))
619 group_ldif = common.get_ldif_for_editor(samdb, msg)
620 self.outf.write(group_ldif)
623 class cmd_group_stats(Command):
624 """Summary statistics about group memberships."""
626 synopsis = "%prog [options]"
629 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
630 metavar="URL", dest="H"),
633 takes_optiongroups = {
634 "sambaopts": options.SambaOptions,
635 "credopts": options.CredentialsOptions,
636 "versionopts": options.VersionOptions,
639 def num_in_range(self, range_min, range_max, group_freqs):
641 for members, count in group_freqs.items():
642 if range_min <= members and members <= range_max:
647 def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
648 lp = sambaopts.get_loadparm()
649 creds = credopts.get_credentials(lp, fallback_machine=True)
651 samdb = SamDB(url=H, session_info=system_session(),
652 credentials=creds, lp=lp)
654 domain_dn = samdb.domain_dn()
655 res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
656 expression=("(objectClass=group)"),
657 attrs=["samaccountname", "member"])
659 # first count up how many members each group has
660 group_assignments = {}
661 total_memberships = 0
664 name = str(msg.get("samaccountname"))
665 num_members = len(msg.get("member", default=[]))
666 group_assignments[name] = num_members
667 total_memberships += num_members
669 num_groups = res.count
670 self.outf.write("Group membership statistics*\n")
671 self.outf.write("-------------------------------------------------\n")
672 self.outf.write("Total groups: {0}\n".format(num_groups))
673 self.outf.write("Total memberships: {0}\n".format(total_memberships))
674 average = total_memberships / float(num_groups)
675 self.outf.write("Average members per group: %.2f\n" % average)
677 # find the max and median memberships (note that some default groups
678 # always have zero members, so displaying the min is not very helpful)
679 group_names = list(group_assignments.keys())
680 group_members = list(group_assignments.values())
681 idx = group_members.index(max(group_members))
682 max_members = group_members[idx]
683 self.outf.write("Max members: {0} ({1})\n".format(max_members,
686 midpoint = num_groups // 2
687 median = group_members[midpoint]
688 if num_groups % 2 == 0:
689 median = (median + group_members[midpoint - 1]) / 2
690 self.outf.write("Median members per group: {0}\n\n".format(median))
692 # convert this to the frequency of group membership, i.e. how many
693 # groups have 5 members, how many have 6 members, etc
694 group_freqs = defaultdict(int)
695 for group, num_members in group_assignments.items():
696 group_freqs[num_members] += 1
698 # now squash this down even further, so that we just display the number
699 # of groups that fall into one of the following membership bands
700 bands = [(0, 1), (2, 4), (5, 9), (10, 14), (15, 19), (20, 24),
701 (25, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79),
702 (80, 89), (90, 99), (100, 149), (150, 199), (200, 249),
703 (250, 299), (300, 399), (400, 499), (500, 999), (1000, 1999),
704 (2000, 2999), (3000, 3999), (4000, 4999), (5000, 9999),
705 (10000, max_members)]
707 self.outf.write("Members Number of Groups\n")
708 self.outf.write("-------------------------------------------------\n")
713 if band_start > max_members:
716 num_groups = self.num_in_range(band_start, band_end, group_freqs)
719 band_str = "{0}-{1}".format(band_start, band_end)
720 self.outf.write("%13s %u\n" % (band_str, num_groups))
722 self.outf.write("\n* Note this does not include nested group memberships\n")
725 class cmd_group_edit(Command):
726 """Modify Group AD object.
728 This command will allow editing of a group account in the Active Directory
729 domain. You will then be able to add or change attributes and their values.
731 The groupname specified on the command is the sAMAccountName.
733 The command may be run from the root userid or another authorized userid.
735 The -H or --URL= option can be used to execute the command against a remote
739 samba-tool group edit Group1 -H ldap://samba.samdom.example.com \\
740 -U administrator --password=passw1rd
742 Example1 shows how to edit a groups attributes in the domain against a
745 The -H parameter is used to specify the remote target server.
748 samba-tool group edit Group2
750 Example2 shows how to edit a groups attributes in the domain against a local
754 samba-tool group edit Group3 --editor=nano
756 Example3 shows how to edit a groups attributes in the domain against a local
757 server using the 'nano' editor.
759 synopsis = "%prog <groupname> [options]"
762 Option("-H", "--URL", help="LDB URL for database or target server",
763 type=str, metavar="URL", dest="H"),
764 Option("--editor", help="Editor to use instead of the system default,"
765 " or 'vi' if no system default is set.", type=str),
768 takes_args = ["groupname"]
769 takes_optiongroups = {
770 "sambaopts": options.SambaOptions,
771 "credopts": options.CredentialsOptions,
772 "versionopts": options.VersionOptions,
775 def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
776 H=None, editor=None):
777 lp = sambaopts.get_loadparm()
778 creds = credopts.get_credentials(lp, fallback_machine=True)
779 samdb = SamDB(url=H, session_info=system_session(),
780 credentials=creds, lp=lp)
782 filter = ("(&(sAMAccountName=%s)(objectClass=group))" % groupname)
784 domaindn = samdb.domain_dn()
787 res = samdb.search(base=domaindn,
789 scope=ldb.SCOPE_SUBTREE)
792 raise CommandError('Unable to find group "%s"' % (groupname))
795 raise CommandError('Invalid number of results: for "%s": %d' %
796 ((groupname), len(res)))
799 result_ldif = common.get_ldif_for_editor(samdb, msg)
802 editor = os.environ.get('EDITOR')
806 with tempfile.NamedTemporaryFile(suffix=".tmp") as t_file:
807 t_file.write(get_bytes(result_ldif))
810 check_call([editor, t_file.name])
811 except CalledProcessError as e:
812 raise CalledProcessError("ERROR: ", e)
813 with open(t_file.name) as edited_file:
814 edited_message = edited_file.read()
816 msgs_edited = samdb.parse_ldif(edited_message)
817 msg_edited = next(msgs_edited)[1]
819 res_msg_diff = samdb.msg_diff(msg, msg_edited)
820 if len(res_msg_diff) == 0:
821 self.outf.write("Nothing to do\n")
825 samdb.modify(res_msg_diff)
826 except Exception as e:
827 raise CommandError("Failed to modify group '%s': " % groupname, e)
829 self.outf.write("Modified group '%s' successfully\n" % groupname)
832 class cmd_group_add_unix_attrs(Command):
833 """Add RFC2307 attributes to a group.
835 This command adds Unix attributes to a group account in the Active
837 The groupname specified on the command is the sAMaccountName.
839 Unix (RFC2307) attributes will be added to the group account.
841 Add 'idmap_ldb:use rfc2307 = Yes' to smb.conf to use these attributes for
844 The command may be run from the root userid or another authorized userid.
845 The -H or --URL= option can be used to execute the command against a
849 samba-tool group addunixattrs Group1 10000
851 Example1 shows how to add RFC2307 attributes to a domain enabled group
854 The groups Unix ID will be set to '10000', provided this ID isn't already
858 synopsis = "%prog <groupname> <gidnumber> [options]"
861 Option("-H", "--URL", help="LDB URL for database or target server",
862 type=str, metavar="URL", dest="H"),
865 takes_args = ["groupname", "gidnumber"]
867 takes_optiongroups = {
868 "sambaopts": options.SambaOptions,
869 "credopts": options.CredentialsOptions,
870 "versionopts": options.VersionOptions,
873 def run(self, groupname, gidnumber, credopts=None, sambaopts=None,
874 versionopts=None, H=None):
876 lp = sambaopts.get_loadparm()
877 creds = credopts.get_credentials(lp)
879 samdb = SamDB(url=H, session_info=system_session(),
880 credentials=creds, lp=lp)
882 domaindn = samdb.domain_dn()
884 # Check group exists and doesn't have a gidNumber
885 filter = "(samaccountname={})".format(ldb.binary_encode(groupname))
886 res = samdb.search(domaindn,
887 scope=ldb.SCOPE_SUBTREE,
890 raise CommandError("Unable to find group '{}'".format(groupname))
894 if "gidNumber" in res[0]:
895 raise CommandError("Group {} is a Unix group.".format(groupname))
897 # Check if supplied gidnumber isn't already being used
898 filter = "(&(objectClass=group)(gidNumber={}))".format(gidnumber)
899 res = samdb.search(domaindn,
900 scope=ldb.SCOPE_SUBTREE,
903 raise CommandError('gidNumber {} already used.'.format(gidnumber))
905 if not lp.get("idmap_ldb:use rfc2307"):
906 self.outf.write("You are setting a Unix/RFC2307 GID. "
907 "You may want to set 'idmap_ldb:use rfc2307 = Yes'"
908 " in smb.conf to use the attributes for "
909 "XID/SID-mapping.\n")
916 """.format(group_dn, gidnumber)
919 samdb.modify_ldif(group_mod)
920 except ldb.LdbError as e:
921 raise CommandError("Failed to modify group '{0}': {1}"
922 .format(groupname, e))
924 self.outf.write("Modified Group '{}' successfully\n".format(groupname))
927 class cmd_group(SuperCommand):
928 """Group management."""
931 subcommands["add"] = cmd_group_add()
932 subcommands["delete"] = cmd_group_delete()
933 subcommands["edit"] = cmd_group_edit()
934 subcommands["addmembers"] = cmd_group_add_members()
935 subcommands["removemembers"] = cmd_group_remove_members()
936 subcommands["list"] = cmd_group_list()
937 subcommands["listmembers"] = cmd_group_list_members()
938 subcommands["move"] = cmd_group_move()
939 subcommands["show"] = cmd_group_show()
940 subcommands["stats"] = cmd_group_stats()
941 subcommands["addunixattrs"] = cmd_group_add_unix_attrs()