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
43 security_group
= dict({"Builtin": GTYPE_SECURITY_BUILTIN_LOCAL_GROUP
,
44 "Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP
,
45 "Global": GTYPE_SECURITY_GLOBAL_GROUP
,
46 "Universal": GTYPE_SECURITY_UNIVERSAL_GROUP
})
47 distribution_group
= dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP
,
48 "Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP
,
49 "Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP
})
52 class cmd_group_add(Command
):
53 """Creates a new AD group.
55 This command creates a new Active Directory group. The groupname specified on the command is a unique sAMAccountName.
57 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.
59 Groups may also be used to establish email distribution lists, using --group-type=Distribution.
61 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.
63 The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
65 The command may be run from the root userid or another authorized userid. The
66 -H or --URL= option can be used to execute the command on a remote server.
69 samba-tool group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
71 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.
74 sudo samba-tool group add Group2 --group-type=Distribution
76 Example2 adds a new distribution group to the local server. The command is run under root using the sudo command.
79 samba-tool group add Group3 --nis-domain=samdom --gid-number=12345
81 Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (both options are required to enable this feature).
84 synopsis
= "%prog <groupname> [options]"
86 takes_optiongroups
= {
87 "sambaopts": options
.SambaOptions
,
88 "versionopts": options
.VersionOptions
,
89 "credopts": options
.CredentialsOptions
,
93 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
94 metavar
="URL", dest
="H"),
96 help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
98 Option("--group-scope", type="choice", choices
=["Domain", "Global", "Universal"],
99 help="Group scope (Domain | Global | Universal)"),
100 Option("--group-type", type="choice", choices
=["Security", "Distribution"],
101 help="Group type (Security | Distribution)"),
102 Option("--description", help="Group's description", type=str),
103 Option("--mail-address", help="Group's email address", type=str),
104 Option("--notes", help="Groups's notes", type=str),
105 Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
106 Option("--nis-domain", help="SFU30 NIS Domain", type=str),
109 takes_args
= ["groupname"]
111 def run(self
, groupname
, credopts
=None, sambaopts
=None,
112 versionopts
=None, H
=None, groupou
=None, group_scope
=None,
113 group_type
=None, description
=None, mail_address
=None, notes
=None, gid_number
=None, nis_domain
=None):
115 if (group_type
or "Security") == "Security":
116 gtype
= security_group
.get(group_scope
, GTYPE_SECURITY_GLOBAL_GROUP
)
118 gtype
= distribution_group
.get(group_scope
, GTYPE_DISTRIBUTION_GLOBAL_GROUP
)
120 if (gid_number
is None and nis_domain
is not None) or (gid_number
is not None and nis_domain
is None):
121 raise CommandError('Both --gid-number and --nis-domain have to be set for a RFC2307-enabled group. Operation cancelled.')
123 lp
= sambaopts
.get_loadparm()
124 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
127 samdb
= SamDB(url
=H
, session_info
=system_session(),
128 credentials
=creds
, lp
=lp
)
129 samdb
.newgroup(groupname
, groupou
=groupou
, grouptype
=gtype
,
130 description
=description
, mailaddress
=mail_address
, notes
=notes
,
131 gidnumber
=gid_number
, nisdomain
=nis_domain
)
132 except Exception as e
:
133 # FIXME: catch more specific exception
134 raise CommandError('Failed to create group "%s"' % groupname
, e
)
135 self
.outf
.write("Added group %s\n" % groupname
)
138 class cmd_group_delete(Command
):
139 """Deletes an AD group.
141 The command deletes an existing AD group from the Active Directory domain. The groupname specified on the command is the sAMAccountName.
143 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.
145 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.
148 samba-tool group delete Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
150 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.
153 sudo samba-tool group delete Group2
155 Example2 deletes group Group2 from the local server. The command is run under root using the sudo command.
158 synopsis
= "%prog <groupname> [options]"
160 takes_optiongroups
= {
161 "sambaopts": options
.SambaOptions
,
162 "versionopts": options
.VersionOptions
,
163 "credopts": options
.CredentialsOptions
,
167 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
168 metavar
="URL", dest
="H"),
171 takes_args
= ["groupname"]
173 def run(self
, groupname
, credopts
=None, sambaopts
=None, versionopts
=None, H
=None):
175 lp
= sambaopts
.get_loadparm()
176 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
177 samdb
= SamDB(url
=H
, session_info
=system_session(),
178 credentials
=creds
, lp
=lp
)
180 filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
184 res
= samdb
.search(base
=samdb
.domain_dn(),
185 scope
=ldb
.SCOPE_SUBTREE
,
190 raise CommandError('Unable to find group "%s"' % (groupname
))
193 samdb
.delete(group_dn
)
194 except Exception as e
:
195 # FIXME: catch more specific exception
196 raise CommandError('Failed to remove group "%s"' % groupname
, e
)
197 self
.outf
.write("Deleted group %s\n" % groupname
)
200 class cmd_group_add_members(Command
):
201 """Add members to an AD group.
203 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.
205 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.
207 The member names specified on the command must be the sAMaccountName.
210 samba-tool group addmembers supergroup Group1,Group2,User1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
212 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.
215 sudo samba-tool group addmembers supergroup User2
217 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.
220 synopsis
= "%prog <groupname> <listofmembers> [options]"
222 takes_optiongroups
= {
223 "sambaopts": options
.SambaOptions
,
224 "versionopts": options
.VersionOptions
,
225 "credopts": options
.CredentialsOptions
,
229 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
230 metavar
="URL", dest
="H"),
233 takes_args
= ["groupname", "listofmembers"]
235 def run(self
, groupname
, listofmembers
, credopts
=None, sambaopts
=None,
236 versionopts
=None, H
=None):
238 lp
= sambaopts
.get_loadparm()
239 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
242 samdb
= SamDB(url
=H
, session_info
=system_session(),
243 credentials
=creds
, lp
=lp
)
244 groupmembers
= listofmembers
.split(',')
245 samdb
.add_remove_group_members(groupname
, groupmembers
,
246 add_members_operation
=True)
247 except Exception as e
:
248 # FIXME: catch more specific exception
249 raise CommandError('Failed to add members "%s" to group "%s"' % (
250 listofmembers
, groupname
), e
)
251 self
.outf
.write("Added members to group %s\n" % groupname
)
254 class cmd_group_remove_members(Command
):
255 """Remove members from an AD group.
257 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.
259 When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
262 samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
264 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.
267 sudo samba-tool group removemembers supergroup User1
269 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.
272 synopsis
= "%prog <groupname> <listofmembers> [options]"
274 takes_optiongroups
= {
275 "sambaopts": options
.SambaOptions
,
276 "versionopts": options
.VersionOptions
,
277 "credopts": options
.CredentialsOptions
,
281 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
282 metavar
="URL", dest
="H"),
285 takes_args
= ["groupname", "listofmembers"]
287 def run(self
, groupname
, listofmembers
, credopts
=None, sambaopts
=None,
288 versionopts
=None, H
=None):
290 lp
= sambaopts
.get_loadparm()
291 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
294 samdb
= SamDB(url
=H
, session_info
=system_session(),
295 credentials
=creds
, lp
=lp
)
296 samdb
.add_remove_group_members(groupname
, listofmembers
.split(","),
297 add_members_operation
=False)
298 except Exception as e
:
299 # FIXME: Catch more specific exception
300 raise CommandError('Failed to remove members "%s" from group "%s"' % (listofmembers
, groupname
), e
)
301 self
.outf
.write("Removed members from group %s\n" % groupname
)
304 class cmd_group_list(Command
):
305 """List all groups."""
307 synopsis
= "%prog [options]"
310 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
311 metavar
="URL", dest
="H"),
312 Option("-v", "--verbose",
313 help="Verbose output, showing group type and group scope.",
314 action
="store_true"),
318 takes_optiongroups
= {
319 "sambaopts": options
.SambaOptions
,
320 "credopts": options
.CredentialsOptions
,
321 "versionopts": options
.VersionOptions
,
324 def run(self
, sambaopts
=None, credopts
=None, versionopts
=None, H
=None,
326 lp
= sambaopts
.get_loadparm()
327 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
329 samdb
= SamDB(url
=H
, session_info
=system_session(),
330 credentials
=creds
, lp
=lp
)
331 attrs
=["samaccountname"]
334 attrs
+= ["grouptype", "member"]
335 domain_dn
= samdb
.domain_dn()
336 res
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_SUBTREE
,
337 expression
=("(objectClass=group)"),
343 self
.outf
.write("Group Name Group Type Group Scope Members\n")
344 self
.outf
.write("--------------------------------------------------------------------------------\n")
347 self
.outf
.write("%-44s" % msg
.get("samaccountname", idx
=0))
348 hgtype
= hex(int("%s" % msg
["grouptype"]) & 0x00000000FFFFFFFF)
349 if (hgtype
== hex(int(security_group
.get("Builtin")))):
350 self
.outf
.write("Security Builtin ")
351 elif (hgtype
== hex(int(security_group
.get("Domain")))):
352 self
.outf
.write("Security Domain ")
353 elif (hgtype
== hex(int(security_group
.get("Global")))):
354 self
.outf
.write("Security Global ")
355 elif (hgtype
== hex(int(security_group
.get("Universal")))):
356 self
.outf
.write("Security Universal")
357 elif (hgtype
== hex(int(distribution_group
.get("Global")))):
358 self
.outf
.write("Distribution Global ")
359 elif (hgtype
== hex(int(distribution_group
.get("Domain")))):
360 self
.outf
.write("Distribution Domain ")
361 elif (hgtype
== hex(int(distribution_group
.get("Universal")))):
362 self
.outf
.write("Distribution Universal")
365 num_members
= len(msg
.get("member", default
=[]))
366 self
.outf
.write(" %6u\n" % num_members
)
369 self
.outf
.write("%s\n" % msg
.get("samaccountname", idx
=0))
372 class cmd_group_list_members(Command
):
373 """List all members of an AD group.
375 This command lists members from an existing Active Directory group. The command accepts one group name.
378 samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
381 synopsis
= "%prog <groupname> [options]"
384 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
385 metavar
="URL", dest
="H"),
388 takes_optiongroups
= {
389 "sambaopts": options
.SambaOptions
,
390 "credopts": options
.CredentialsOptions
,
391 "versionopts": options
.VersionOptions
,
394 takes_args
= ["groupname"]
396 def run(self
, groupname
, credopts
=None, sambaopts
=None, versionopts
=None, H
=None):
397 lp
= sambaopts
.get_loadparm()
398 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
401 samdb
= SamDB(url
=H
, session_info
=system_session(),
402 credentials
=creds
, lp
=lp
)
404 search_filter
= "(&(objectClass=group)(samaccountname=%s))" % groupname
405 res
= samdb
.search(samdb
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
406 expression
=(search_filter
),
412 group_dn
= res
[0].get('dn', idx
=0)
413 object_sid
= res
[0].get('objectSid', idx
=0)
415 object_sid
= ndr_unpack(security
.dom_sid
, object_sid
)
416 (group_dom_sid
, rid
) = object_sid
.split()
418 search_filter
= "(|(primaryGroupID=%s)(memberOf=%s))" % (rid
, group_dn
)
419 res
= samdb
.search(samdb
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
420 expression
=(search_filter
),
421 attrs
=["samAccountName", "cn"])
427 member_name
= msg
.get("samAccountName", idx
=0)
428 if member_name
is None:
429 member_name
= msg
.get("cn", idx
=0)
430 self
.outf
.write("%s\n" % member_name
)
432 except Exception as e
:
433 raise CommandError('Failed to list members of "%s" group ' % groupname
, e
)
436 class cmd_group_move(Command
):
437 """Move a group to an organizational unit/container.
439 This command moves a group object into the specified organizational unit
441 The groupname specified on the command is the sAMAccountName.
442 The name of the organizational unit or container can be specified as a
443 full DN or without the domainDN component.
445 The command may be run from the root userid or another authorized userid.
447 The -H or --URL= option can be used to execute the command against a remote
451 samba-tool group move Group1 'OU=OrgUnit,DC=samdom.DC=example,DC=com' \\
452 -H ldap://samba.samdom.example.com -U administrator
454 Example1 shows how to move a group Group1 into the 'OrgUnit' organizational
455 unit on a remote LDAP server.
457 The -H parameter is used to specify the remote target server.
460 samba-tool group move Group1 CN=Users
462 Example2 shows how to move a group Group1 back into the CN=Users container
466 synopsis
= "%prog <groupname> <new_parent_dn> [options]"
469 Option("-H", "--URL", help="LDB URL for database or target server",
470 type=str, metavar
="URL", dest
="H"),
473 takes_args
= ["groupname", "new_parent_dn"]
474 takes_optiongroups
= {
475 "sambaopts": options
.SambaOptions
,
476 "credopts": options
.CredentialsOptions
,
477 "versionopts": options
.VersionOptions
,
480 def run(self
, groupname
, new_parent_dn
, credopts
=None, sambaopts
=None,
481 versionopts
=None, H
=None):
482 lp
= sambaopts
.get_loadparm()
483 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
484 samdb
= SamDB(url
=H
, session_info
=system_session(),
485 credentials
=creds
, lp
=lp
)
486 domain_dn
= ldb
.Dn(samdb
, samdb
.domain_dn())
488 filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
491 res
= samdb
.search(base
=domain_dn
,
493 scope
=ldb
.SCOPE_SUBTREE
)
496 raise CommandError('Unable to find group "%s"' % (groupname
))
499 full_new_parent_dn
= samdb
.normalize_dn_in_domain(new_parent_dn
)
500 except Exception as e
:
501 raise CommandError('Invalid new_parent_dn "%s": %s' %
502 (new_parent_dn
, e
.message
))
504 full_new_group_dn
= ldb
.Dn(samdb
, str(group_dn
))
505 full_new_group_dn
.remove_base_components(len(group_dn
) - 1)
506 full_new_group_dn
.add_base(full_new_parent_dn
)
509 samdb
.rename(group_dn
, full_new_group_dn
)
510 except Exception as e
:
511 raise CommandError('Failed to move group "%s"' % groupname
, e
)
512 self
.outf
.write('Moved group "%s" into "%s"\n' %
513 (groupname
, full_new_parent_dn
))
516 class cmd_group_show(Command
):
517 """Display a group AD object.
519 This command displays a group object and it's attributes in the Active
521 The group name specified on the command is the sAMAccountName of the group.
523 The command may be run from the root userid or another authorized userid.
525 The -H or --URL= option can be used to execute the command against a remote
529 samba-tool group show Group1 -H ldap://samba.samdom.example.com \\
530 -U administrator --password=passw1rd
532 Example1 shows how to display a group's attributes in the domain against a
535 The -H parameter is used to specify the remote target server.
538 samba-tool group show Group2
540 Example2 shows how to display a group's attributes in the domain against a local
544 samba-tool group show Group3 --attributes=member,objectGUID
546 Example3 shows how to display a users objectGUID and member attributes.
548 synopsis
= "%prog <group name> [options]"
551 Option("-H", "--URL", help="LDB URL for database or target server",
552 type=str, metavar
="URL", dest
="H"),
553 Option("--attributes",
554 help=("Comma separated list of attributes, "
555 "which will be printed."),
556 type=str, dest
="group_attrs"),
559 takes_args
= ["groupname"]
560 takes_optiongroups
= {
561 "sambaopts": options
.SambaOptions
,
562 "credopts": options
.CredentialsOptions
,
563 "versionopts": options
.VersionOptions
,
566 def run(self
, groupname
, credopts
=None, sambaopts
=None, versionopts
=None,
567 H
=None, group_attrs
=None):
569 lp
= sambaopts
.get_loadparm()
570 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
571 samdb
= SamDB(url
=H
, session_info
=system_session(),
572 credentials
=creds
, lp
=lp
)
576 attrs
= group_attrs
.split(",")
578 filter = ("(&(sAMAccountType=%d)(sAMAccountName=%s))" %
579 (ATYPE_SECURITY_GLOBAL_GROUP
,
580 ldb
.binary_encode(groupname
)))
582 domaindn
= samdb
.domain_dn()
585 res
= samdb
.search(base
=domaindn
, expression
=filter,
586 scope
=ldb
.SCOPE_SUBTREE
, attrs
=attrs
)
589 raise CommandError('Unable to find group "%s"' % (groupname
))
592 user_ldif
= samdb
.write_ldif(msg
, ldb
.CHANGETYPE_NONE
)
593 self
.outf
.write(user_ldif
)
596 class cmd_group_stats(Command
):
597 """Summary statistics about group memberships."""
599 synopsis
= "%prog [options]"
602 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
603 metavar
="URL", dest
="H"),
606 takes_optiongroups
= {
607 "sambaopts": options
.SambaOptions
,
608 "credopts": options
.CredentialsOptions
,
609 "versionopts": options
.VersionOptions
,
612 def num_in_range(self
, range_min
, range_max
, group_freqs
):
614 for members
, count
in group_freqs
.items():
615 if range_min
<= members
and members
<= range_max
:
620 def run(self
, sambaopts
=None, credopts
=None, versionopts
=None, H
=None):
621 lp
= sambaopts
.get_loadparm()
622 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
624 samdb
= SamDB(url
=H
, session_info
=system_session(),
625 credentials
=creds
, lp
=lp
)
627 domain_dn
= samdb
.domain_dn()
628 res
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_SUBTREE
,
629 expression
=("(objectClass=group)"),
630 attrs
=["samaccountname", "member"])
632 # first count up how many members each group has
633 group_assignments
= {}
634 total_memberships
= 0
637 name
= str(msg
.get("samaccountname"))
638 num_members
= len(msg
.get("member", default
=[]))
639 group_assignments
[name
] = num_members
640 total_memberships
+= num_members
642 num_groups
= res
.count
643 self
.outf
.write("Group membership statistics*\n")
644 self
.outf
.write("-------------------------------------------------\n")
645 self
.outf
.write("Total groups: {0}\n".format(num_groups
))
646 self
.outf
.write("Total memberships: {0}\n".format(total_memberships
))
647 average
= total_memberships
/ float(num_groups
)
648 self
.outf
.write("Average members per group: %.2f\n" % average
)
650 # find the max and median memberships (note that some default groups
651 # always have zero members, so displaying the min is not very helpful)
652 group_names
= list(group_assignments
.keys())
653 group_members
= list(group_assignments
.values())
654 idx
= group_members
.index(max(group_members
))
655 max_members
= group_members
[idx
]
656 self
.outf
.write("Max members: {0} ({1})\n".format(max_members
,
659 midpoint
= num_groups
// 2
660 median
= group_members
[midpoint
]
661 if num_groups
% 2 == 0:
662 median
= (median
+ group_members
[midpoint
- 1]) / 2
663 self
.outf
.write("Median members per group: {0}\n\n".format(median
))
665 # convert this to the frequency of group membership, i.e. how many
666 # groups have 5 members, how many have 6 members, etc
667 group_freqs
= defaultdict(int)
668 for group
, num_members
in group_assignments
.items():
669 group_freqs
[num_members
] += 1
671 # now squash this down even further, so that we just display the number
672 # of groups that fall into one of the following membership bands
673 bands
= [(0, 1), (2, 4), (5, 9), (10, 14), (15, 19), (20, 24),
674 (25, 29), (30, 39), (40, 49), (50, 59), (60, 69), (70, 79),
675 (80, 89), (90, 99), (100, 149), (150, 199), (200, 249),
676 (250, 299), (300, 399), (400, 499), (500, 999), (1000, 1999),
677 (2000, 2999), (3000, 3999), (4000, 4999), (5000, 9999),
678 (10000, max_members
)]
680 self
.outf
.write("Members Number of Groups\n")
681 self
.outf
.write("-------------------------------------------------\n")
686 if band_start
> max_members
:
689 num_groups
= self
.num_in_range(band_start
, band_end
, group_freqs
)
692 band_str
= "{0}-{1}".format(band_start
, band_end
)
693 self
.outf
.write("%13s %u\n" % (band_str
, num_groups
))
695 self
.outf
.write("\n* Note this does not include nested group memberships\n")
698 class cmd_group_edit(Command
):
699 """Modify Group AD object.
701 This command will allow editing of a group account in the Active Directory
702 domain. You will then be able to add or change attributes and their values.
704 The groupname specified on the command is the sAMAccountName.
706 The command may be run from the root userid or another authorized userid.
708 The -H or --URL= option can be used to execute the command against a remote
712 samba-tool group edit Group1 -H ldap://samba.samdom.example.com \\
713 -U administrator --password=passw1rd
715 Example1 shows how to edit a groups attributes in the domain against a
718 The -H parameter is used to specify the remote target server.
721 samba-tool group edit Group2
723 Example2 shows how to edit a groups attributes in the domain against a local
727 samba-tool group edit Group3 --editor=nano
729 Example3 shows how to edit a groups attributes in the domain against a local
730 server using the 'nano' editor.
732 synopsis
= "%prog <groupname> [options]"
735 Option("-H", "--URL", help="LDB URL for database or target server",
736 type=str, metavar
="URL", dest
="H"),
737 Option("--editor", help="Editor to use instead of the system default,"
738 " or 'vi' if no system default is set.", type=str),
741 takes_args
= ["groupname"]
742 takes_optiongroups
= {
743 "sambaopts": options
.SambaOptions
,
744 "credopts": options
.CredentialsOptions
,
745 "versionopts": options
.VersionOptions
,
748 def run(self
, groupname
, credopts
=None, sambaopts
=None, versionopts
=None,
749 H
=None, editor
=None):
752 lp
= sambaopts
.get_loadparm()
753 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
754 samdb
= SamDB(url
=H
, session_info
=system_session(),
755 credentials
=creds
, lp
=lp
)
757 filter = ("(&(sAMAccountName=%s)(objectClass=group))" % groupname
)
759 domaindn
= samdb
.domain_dn()
762 res
= samdb
.search(base
=domaindn
,
764 scope
=ldb
.SCOPE_SUBTREE
)
767 raise CommandError('Unable to find group "%s"' % (groupname
))
770 raise CommandError('Invalid number of results: for "%s": %d' %
771 ((groupname
), len(res
)))
774 result_ldif
= common
.get_ldif_for_editor(samdb
, msg
)
777 editor
= os
.environ
.get('EDITOR')
781 with tempfile
.NamedTemporaryFile(suffix
=".tmp") as t_file
:
782 t_file
.write(get_bytes(result_ldif
))
785 check_call([editor
, t_file
.name
])
786 except CalledProcessError
as e
:
787 raise CalledProcessError("ERROR: ", e
)
788 with
open(t_file
.name
) as edited_file
:
789 edited_message
= edited_file
.read()
791 msgs_edited
= samdb
.parse_ldif(edited_message
)
792 msg_edited
= next(msgs_edited
)[1]
794 res_msg_diff
= samdb
.msg_diff(msg
, msg_edited
)
795 if len(res_msg_diff
) == 0:
796 self
.outf
.write("Nothing to do\n")
800 samdb
.modify(res_msg_diff
)
801 except Exception as e
:
802 raise CommandError("Failed to modify group '%s': " % groupname
, e
)
804 self
.outf
.write("Modified group '%s' successfully\n" % groupname
)
807 class cmd_group(SuperCommand
):
808 """Group management."""
811 subcommands
["add"] = cmd_group_add()
812 subcommands
["delete"] = cmd_group_delete()
813 subcommands
["edit"] = cmd_group_edit()
814 subcommands
["addmembers"] = cmd_group_add_members()
815 subcommands
["removemembers"] = cmd_group_remove_members()
816 subcommands
["list"] = cmd_group_list()
817 subcommands
["listmembers"] = cmd_group_list_members()
818 subcommands
["move"] = cmd_group_move()
819 subcommands
["show"] = cmd_group_show()
820 subcommands
["stats"] = cmd_group_stats()