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))" %
182 ldb.binary_encode(groupname))
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>]|--member-dn=<member-dn>) [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"),
232 Option("--member-dn",
233 help=("DN of the new group member to be added.\n"
234 "The --object-types option will be ignored."),
237 Option("--object-types",
238 help=("Comma separated list of object types.\n"
239 "The types are used to filter the search for the "
240 "specified members.\n"
241 "Valid values are: user, group, computer, serviceaccount, "
243 "Default: user,group,computer"),
244 default="user,group,computer",
246 Option("--member-base-dn",
247 help=("Base DN for group member search.\n"
248 "Default is the domain DN."),
252 takes_args = ["groupname", "listofmembers?"]
263 object_types="user,group,computer"):
265 lp = sambaopts.get_loadparm()
266 creds = credopts.get_credentials(lp, fallback_machine=True)
268 if member_dn is None and listofmembers is None:
271 'Either listofmembers or --member-dn must be specified.')
274 samdb = SamDB(url=H, session_info=system_session(),
275 credentials=creds, lp=lp)
277 if member_dn is not None:
278 groupmembers += member_dn
279 if listofmembers is not None:
280 groupmembers += listofmembers.split(',')
281 group_member_types = object_types.split(',')
283 if member_base_dn is not None:
284 member_base_dn = samdb.normalize_dn_in_domain(member_base_dn)
286 samdb.add_remove_group_members(groupname, groupmembers,
287 add_members_operation=True,
288 member_types=group_member_types,
289 member_base_dn=member_base_dn)
290 except Exception as e:
291 # FIXME: catch more specific exception
292 raise CommandError('Failed to add members %r to group "%s" - %s' % (
293 groupmembers, groupname, e))
294 self.outf.write("Added members to group %s\n" % groupname)
297 class cmd_group_remove_members(Command):
298 """Remove members from an AD group.
300 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.
302 When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
305 samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
307 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.
310 sudo samba-tool group removemembers supergroup User1
312 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.
315 synopsis = "%prog <groupname> (<listofmembers>]|--member-dn=<member-dn>) [options]"
317 takes_optiongroups = {
318 "sambaopts": options.SambaOptions,
319 "versionopts": options.VersionOptions,
320 "credopts": options.CredentialsOptions,
324 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
325 metavar="URL", dest="H"),
326 Option("--member-dn",
327 help=("DN of the group member to be removed.\n"
328 "The --object-types option will be ignored."),
331 Option("--object-types",
332 help=("Comma separated list of object types.\n"
333 "The types are used to filter the search for the "
334 "specified members.\n"
335 "Valid values are: user, group, computer, serviceaccount, "
337 "Default: user,group,computer"),
338 default="user,group,computer",
340 Option("--member-base-dn",
341 help=("Base DN for group member search.\n"
342 "Default is the domain DN."),
346 takes_args = ["groupname", "listofmembers?"]
357 object_types="user,group,computer"):
359 lp = sambaopts.get_loadparm()
360 creds = credopts.get_credentials(lp, fallback_machine=True)
362 if member_dn is None and listofmembers is None:
365 'Either listofmembers or --member-dn must be specified.')
368 samdb = SamDB(url=H, session_info=system_session(),
369 credentials=creds, lp=lp)
371 if member_dn is not None:
372 groupmembers += member_dn
373 if listofmembers is not None:
374 groupmembers += listofmembers.split(',')
375 group_member_types = object_types.split(',')
377 if member_base_dn is not None:
378 member_base_dn = samdb.normalize_dn_in_domain(member_base_dn)
380 samdb.add_remove_group_members(groupname,
382 add_members_operation=False,
383 member_types=group_member_types,
384 member_base_dn=member_base_dn)
385 except Exception as e:
386 # FIXME: Catch more specific exception
387 raise CommandError('Failed to remove members %r from group "%s"' % (listofmembers, groupname), e)
388 self.outf.write("Removed members from group %s\n" % groupname)
391 class cmd_group_list(Command):
392 """List all groups."""
394 synopsis = "%prog [options]"
397 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
398 metavar="URL", dest="H"),
399 Option("-v", "--verbose",
400 help="Verbose output, showing group type and group scope.",
401 action="store_true"),
402 Option("-b", "--base-dn",
403 help="Specify base DN to use.",
405 Option("--full-dn", dest="full_dn",
408 help="Display DN instead of the sAMAccountName."),
411 takes_optiongroups = {
412 "sambaopts": options.SambaOptions,
413 "credopts": options.CredentialsOptions,
414 "versionopts": options.VersionOptions,
425 lp = sambaopts.get_loadparm()
426 creds = credopts.get_credentials(lp, fallback_machine=True)
428 samdb = SamDB(url=H, session_info=system_session(),
429 credentials=creds, lp=lp)
430 attrs=["samaccountname"]
433 attrs += ["grouptype", "member"]
434 domain_dn = samdb.domain_dn()
436 domain_dn = samdb.normalize_dn_in_domain(base_dn)
437 res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
438 expression=("(objectClass=group)"),
444 self.outf.write("Group Name Group Type Group Scope Members\n")
445 self.outf.write("--------------------------------------------------------------------------------\n")
448 self.outf.write("%-44s" % msg.get("samaccountname", idx=0))
449 hgtype = hex(int("%s" % msg["grouptype"]) & 0x00000000FFFFFFFF)
450 if (hgtype == hex(int(security_group.get("Builtin")))):
451 self.outf.write("Security Builtin ")
452 elif (hgtype == hex(int(security_group.get("Domain")))):
453 self.outf.write("Security Domain ")
454 elif (hgtype == hex(int(security_group.get("Global")))):
455 self.outf.write("Security Global ")
456 elif (hgtype == hex(int(security_group.get("Universal")))):
457 self.outf.write("Security Universal")
458 elif (hgtype == hex(int(distribution_group.get("Global")))):
459 self.outf.write("Distribution Global ")
460 elif (hgtype == hex(int(distribution_group.get("Domain")))):
461 self.outf.write("Distribution Domain ")
462 elif (hgtype == hex(int(distribution_group.get("Universal")))):
463 self.outf.write("Distribution Universal")
466 num_members = len(msg.get("member", default=[]))
467 self.outf.write(" %6u\n" % num_members)
471 self.outf.write("%s\n" % msg.get("dn"))
474 self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
477 class cmd_group_list_members(Command):
478 """List all members of an AD group.
480 This command lists members from an existing Active Directory group. The command accepts one group name.
483 samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
486 synopsis = "%prog <groupname> [options]"
489 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
490 metavar="URL", dest="H"),
491 Option("--full-dn", dest="full_dn",
494 help="Display DN instead of the sAMAccountName.")
497 takes_optiongroups = {
498 "sambaopts": options.SambaOptions,
499 "credopts": options.CredentialsOptions,
500 "versionopts": options.VersionOptions,
503 takes_args = ["groupname"]
512 lp = sambaopts.get_loadparm()
513 creds = credopts.get_credentials(lp, fallback_machine=True)
516 samdb = SamDB(url=H, session_info=system_session(),
517 credentials=creds, lp=lp)
519 search_filter = ("(&(objectClass=group)(sAMAccountName=%s))" %
520 ldb.binary_encode(groupname))
522 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
523 expression=(search_filter),
525 group_sid_binary = res[0].get('objectSid', idx=0)
527 raise CommandError('Unable to find group "%s"' % (groupname))
529 group_sid = ndr_unpack(security.dom_sid, group_sid_binary)
530 (group_dom_sid, rid) = group_sid.split()
531 group_sid_dn = "<SID=%s>" % (group_sid)
533 search_filter = ("(|(primaryGroupID=%s)(memberOf=%s))" %
535 res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
536 expression=(search_filter),
537 attrs=["samAccountName", "cn"])
544 self.outf.write("%s\n" % msg.get("dn"))
547 member_name = msg.get("samAccountName", idx=0)
548 if member_name is None:
549 member_name = msg.get("cn", idx=0)
550 self.outf.write("%s\n" % member_name)
552 except Exception as e:
553 raise CommandError('Failed to list members of "%s" group - %s' %
557 class cmd_group_move(Command):
558 """Move a group to an organizational unit/container.
560 This command moves a group object into the specified organizational unit
562 The groupname specified on the command is the sAMAccountName.
563 The name of the organizational unit or container can be specified as a
564 full DN or without the domainDN component.
566 The command may be run from the root userid or another authorized userid.
568 The -H or --URL= option can be used to execute the command against a remote
572 samba-tool group move Group1 'OU=OrgUnit,DC=samdom.DC=example,DC=com' \\
573 -H ldap://samba.samdom.example.com -U administrator
575 Example1 shows how to move a group Group1 into the 'OrgUnit' organizational
576 unit on a remote LDAP server.
578 The -H parameter is used to specify the remote target server.
581 samba-tool group move Group1 CN=Users
583 Example2 shows how to move a group Group1 back into the CN=Users container
587 synopsis = "%prog <groupname> <new_parent_dn> [options]"
590 Option("-H", "--URL", help="LDB URL for database or target server",
591 type=str, metavar="URL", dest="H"),
594 takes_args = ["groupname", "new_parent_dn"]
595 takes_optiongroups = {
596 "sambaopts": options.SambaOptions,
597 "credopts": options.CredentialsOptions,
598 "versionopts": options.VersionOptions,
601 def run(self, groupname, new_parent_dn, credopts=None, sambaopts=None,
602 versionopts=None, H=None):
603 lp = sambaopts.get_loadparm()
604 creds = credopts.get_credentials(lp, fallback_machine=True)
605 samdb = SamDB(url=H, session_info=system_session(),
606 credentials=creds, lp=lp)
607 domain_dn = ldb.Dn(samdb, samdb.domain_dn())
609 filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
610 ldb.binary_encode(groupname))
612 res = samdb.search(base=domain_dn,
614 scope=ldb.SCOPE_SUBTREE)
617 raise CommandError('Unable to find group "%s"' % (groupname))
620 full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn)
621 except Exception as e:
622 raise CommandError('Invalid new_parent_dn "%s": %s' %
623 (new_parent_dn, e.message))
625 full_new_group_dn = ldb.Dn(samdb, str(group_dn))
626 full_new_group_dn.remove_base_components(len(group_dn) - 1)
627 full_new_group_dn.add_base(full_new_parent_dn)
630 samdb.rename(group_dn, full_new_group_dn)
631 except Exception as e:
632 raise CommandError('Failed to move group "%s"' % groupname, e)
633 self.outf.write('Moved group "%s" into "%s"\n' %
634 (groupname, full_new_parent_dn))
637 class cmd_group_show(Command):
638 """Display a group AD object.
640 This command displays a group object and it's attributes in the Active
642 The group name specified on the command is the sAMAccountName of the group.
644 The command may be run from the root userid or another authorized userid.
646 The -H or --URL= option can be used to execute the command against a remote
650 samba-tool group show Group1 -H ldap://samba.samdom.example.com \\
651 -U administrator --password=passw1rd
653 Example1 shows how to display a group's attributes in the domain against a
656 The -H parameter is used to specify the remote target server.
659 samba-tool group show Group2
661 Example2 shows how to display a group's attributes in the domain against a local
665 samba-tool group show Group3 --attributes=member,objectGUID
667 Example3 shows how to display a groups objectGUID and member attributes.
669 synopsis = "%prog <group name> [options]"
672 Option("-H", "--URL", help="LDB URL for database or target server",
673 type=str, metavar="URL", dest="H"),
674 Option("--attributes",
675 help=("Comma separated list of attributes, "
676 "which will be printed."),
677 type=str, dest="group_attrs"),
680 takes_args = ["groupname"]
681 takes_optiongroups = {
682 "sambaopts": options.SambaOptions,
683 "credopts": options.CredentialsOptions,
684 "versionopts": options.VersionOptions,
687 def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
688 H=None, group_attrs=None):
690 lp = sambaopts.get_loadparm()
691 creds = credopts.get_credentials(lp, fallback_machine=True)
692 samdb = SamDB(url=H, session_info=system_session(),
693 credentials=creds, lp=lp)
697 attrs = group_attrs.split(",")
699 filter = ("(&(sAMAccountType=%d)(sAMAccountName=%s))" %
700 (ATYPE_SECURITY_GLOBAL_GROUP,
701 ldb.binary_encode(groupname)))
703 domaindn = samdb.domain_dn()
706 res = samdb.search(base=domaindn, expression=filter,
707 scope=ldb.SCOPE_SUBTREE, attrs=attrs)
710 raise CommandError('Unable to find group "%s"' % (groupname))
713 group_ldif = common.get_ldif_for_editor(samdb, msg)
714 self.outf.write(group_ldif)
717 class cmd_group_stats(Command):
718 """Summary statistics about group memberships."""
720 synopsis = "%prog [options]"
723 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
724 metavar="URL", dest="H"),
727 takes_optiongroups = {
728 "sambaopts": options.SambaOptions,
729 "credopts": options.CredentialsOptions,
730 "versionopts": options.VersionOptions,
733 def num_in_range(self, range_min, range_max, group_freqs):
735 for members, count in group_freqs.items():
736 if range_min <= members and members <= range_max:
741 def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
742 lp = sambaopts.get_loadparm()
743 creds = credopts.get_credentials(lp, fallback_machine=True)
745 samdb = SamDB(url=H, session_info=system_session(),
746 credentials=creds, lp=lp)
748 domain_dn = samdb.domain_dn()
749 res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
750 expression=("(objectClass=group)"),
751 attrs=["samaccountname", "member"])
753 # first count up how many members each group has
754 group_assignments = {}
755 total_memberships = 0
758 name = str(msg.get("samaccountname"))
759 num_members = len(msg.get("member", default=[]))
760 group_assignments[name] = num_members
761 total_memberships += num_members
763 num_groups = res.count
764 self.outf.write("Group membership statistics*\n")
765 self.outf.write("-------------------------------------------------\n")
766 self.outf.write("Total groups: {0}\n".format(num_groups))
767 self.outf.write("Total memberships: {0}\n".format(total_memberships))
768 average = total_memberships / float(num_groups)
769 self.outf.write("Average members per group: %.2f\n" % average)
771 # find the max and median memberships (note that some default groups
772 # always have zero members, so displaying the min is not very helpful)
773 group_names = list(group_assignments.keys())
774 group_members = list(group_assignments.values())
775 idx = group_members.index(max(group_members))
776 max_members = group_members[idx]
777 self.outf.write("Max members: {0} ({1})\n".format(max_members,
780 midpoint = num_groups // 2
781 median = group_members[midpoint]
782 if num_groups % 2 == 0:
783 median = (median + group_members[midpoint - 1]) / 2
784 self.outf.write("Median members per group: {0}\n\n".format(median))
786 # convert this to the frequency of group membership, i.e. how many
787 # groups have 5 members, how many have 6 members, etc
788 group_freqs = defaultdict(int)
789 for group, num_members in group_assignments.items():
790 group_freqs[num_members] += 1
792 # now squash this down even further, so that we just display the number
793 # of groups that fall into one of the following membership bands
794 bands = [(0, 1), (2, 4), (5, 9), (10, 14), (15, 19), (20, 24),
795 (25, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79),
796 (80, 89), (90, 99), (100, 149), (150, 199), (200, 249),
797 (250, 299), (300, 399), (400, 499), (500, 999), (1000, 1999),
798 (2000, 2999), (3000, 3999), (4000, 4999), (5000, 9999),
799 (10000, max_members)]
801 self.outf.write("Members Number of Groups\n")
802 self.outf.write("-------------------------------------------------\n")
807 if band_start > max_members:
810 num_groups = self.num_in_range(band_start, band_end, group_freqs)
813 band_str = "{0}-{1}".format(band_start, band_end)
814 self.outf.write("%13s %u\n" % (band_str, num_groups))
816 self.outf.write("\n* Note this does not include nested group memberships\n")
819 class cmd_group_edit(Command):
820 """Modify Group AD object.
822 This command will allow editing of a group account in the Active Directory
823 domain. You will then be able to add or change attributes and their values.
825 The groupname specified on the command is the sAMAccountName.
827 The command may be run from the root userid or another authorized userid.
829 The -H or --URL= option can be used to execute the command against a remote
833 samba-tool group edit Group1 -H ldap://samba.samdom.example.com \\
834 -U administrator --password=passw1rd
836 Example1 shows how to edit a groups attributes in the domain against a
839 The -H parameter is used to specify the remote target server.
842 samba-tool group edit Group2
844 Example2 shows how to edit a groups attributes in the domain against a local
848 samba-tool group edit Group3 --editor=nano
850 Example3 shows how to edit a groups attributes in the domain against a local
851 server using the 'nano' editor.
853 synopsis = "%prog <groupname> [options]"
856 Option("-H", "--URL", help="LDB URL for database or target server",
857 type=str, metavar="URL", dest="H"),
858 Option("--editor", help="Editor to use instead of the system default,"
859 " or 'vi' if no system default is set.", type=str),
862 takes_args = ["groupname"]
863 takes_optiongroups = {
864 "sambaopts": options.SambaOptions,
865 "credopts": options.CredentialsOptions,
866 "versionopts": options.VersionOptions,
869 def run(self, groupname, credopts=None, sambaopts=None, versionopts=None,
870 H=None, editor=None):
871 lp = sambaopts.get_loadparm()
872 creds = credopts.get_credentials(lp, fallback_machine=True)
873 samdb = SamDB(url=H, session_info=system_session(),
874 credentials=creds, lp=lp)
876 filter = ("(&(sAMAccountName=%s)(objectClass=group))" % groupname)
878 domaindn = samdb.domain_dn()
881 res = samdb.search(base=domaindn,
883 scope=ldb.SCOPE_SUBTREE)
886 raise CommandError('Unable to find group "%s"' % (groupname))
889 raise CommandError('Invalid number of results: for "%s": %d' %
890 ((groupname), len(res)))
893 result_ldif = common.get_ldif_for_editor(samdb, msg)
896 editor = os.environ.get('EDITOR')
900 with tempfile.NamedTemporaryFile(suffix=".tmp") as t_file:
901 t_file.write(get_bytes(result_ldif))
904 check_call([editor, t_file.name])
905 except CalledProcessError as e:
906 raise CalledProcessError("ERROR: ", e)
907 with open(t_file.name) as edited_file:
908 edited_message = edited_file.read()
910 msgs_edited = samdb.parse_ldif(edited_message)
911 msg_edited = next(msgs_edited)[1]
913 res_msg_diff = samdb.msg_diff(msg, msg_edited)
914 if len(res_msg_diff) == 0:
915 self.outf.write("Nothing to do\n")
919 samdb.modify(res_msg_diff)
920 except Exception as e:
921 raise CommandError("Failed to modify group '%s': " % groupname, e)
923 self.outf.write("Modified group '%s' successfully\n" % groupname)
926 class cmd_group_add_unix_attrs(Command):
927 """Add RFC2307 attributes to a group.
929 This command adds Unix attributes to a group account in the Active
931 The groupname specified on the command is the sAMaccountName.
933 Unix (RFC2307) attributes will be added to the group account.
935 Add 'idmap_ldb:use rfc2307 = Yes' to smb.conf to use these attributes for
938 The command may be run from the root userid or another authorized userid.
939 The -H or --URL= option can be used to execute the command against a
943 samba-tool group addunixattrs Group1 10000
945 Example1 shows how to add RFC2307 attributes to a domain enabled group
948 The groups Unix ID will be set to '10000', provided this ID isn't already
952 synopsis = "%prog <groupname> <gidnumber> [options]"
955 Option("-H", "--URL", help="LDB URL for database or target server",
956 type=str, metavar="URL", dest="H"),
959 takes_args = ["groupname", "gidnumber"]
961 takes_optiongroups = {
962 "sambaopts": options.SambaOptions,
963 "credopts": options.CredentialsOptions,
964 "versionopts": options.VersionOptions,
967 def run(self, groupname, gidnumber, credopts=None, sambaopts=None,
968 versionopts=None, H=None):
970 lp = sambaopts.get_loadparm()
971 creds = credopts.get_credentials(lp)
973 samdb = SamDB(url=H, session_info=system_session(),
974 credentials=creds, lp=lp)
976 domaindn = samdb.domain_dn()
978 # Check group exists and doesn't have a gidNumber
979 filter = "(samaccountname={})".format(ldb.binary_encode(groupname))
980 res = samdb.search(domaindn,
981 scope=ldb.SCOPE_SUBTREE,
984 raise CommandError("Unable to find group '{}'".format(groupname))
988 if "gidNumber" in res[0]:
989 raise CommandError("Group {} is a Unix group.".format(groupname))
991 # Check if supplied gidnumber isn't already being used
992 filter = "(&(objectClass=group)(gidNumber={}))".format(gidnumber)
993 res = samdb.search(domaindn,
994 scope=ldb.SCOPE_SUBTREE,
997 raise CommandError('gidNumber {} already used.'.format(gidnumber))
999 if not lp.get("idmap_ldb:use rfc2307"):
1000 self.outf.write("You are setting a Unix/RFC2307 GID. "
1001 "You may want to set 'idmap_ldb:use rfc2307 = Yes'"
1002 " in smb.conf to use the attributes for "
1003 "XID/SID-mapping.\n")
1010 """.format(group_dn, gidnumber)
1013 samdb.modify_ldif(group_mod)
1014 except ldb.LdbError as e:
1015 raise CommandError("Failed to modify group '{0}': {1}"
1016 .format(groupname, e))
1018 self.outf.write("Modified Group '{}' successfully\n".format(groupname))
1021 class cmd_group(SuperCommand):
1022 """Group management."""
1025 subcommands["add"] = cmd_group_add()
1026 subcommands["delete"] = cmd_group_delete()
1027 subcommands["edit"] = cmd_group_edit()
1028 subcommands["addmembers"] = cmd_group_add_members()
1029 subcommands["removemembers"] = cmd_group_remove_members()
1030 subcommands["list"] = cmd_group_list()
1031 subcommands["listmembers"] = cmd_group_list_members()
1032 subcommands["move"] = cmd_group_move()
1033 subcommands["show"] = cmd_group_show()
1034 subcommands["stats"] = cmd_group_stats()
1035 subcommands["addunixattrs"] = cmd_group_add_unix_attrs()