bug 12293: stop group.py throwing errors if group is unknown
[samba.git] / python / samba / netcmd / group.py
1 # Copyright Jelmer Vernooij 2008
2 #
3 # Based on the original in EJS:
4 # Copyright Andrew Tridgell 2005
5 #
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.
10 #
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.
15 #
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/>.
18
19 import samba.getopt as options
20 from samba.netcmd import Command, SuperCommand, CommandError, Option
21 import ldb
22 from samba.ndr import ndr_unpack
23 from samba.dcerpc import security
24
25 from getpass import getpass
26 from samba.auth import system_session
27 from samba.samdb import SamDB
28 from samba.dsdb import (
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,
36 )
37
38 security_group = dict({"Builtin": GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
39                        "Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
40                        "Global": GTYPE_SECURITY_GLOBAL_GROUP,
41                        "Universal": GTYPE_SECURITY_UNIVERSAL_GROUP})
42 distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
43                            "Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP,
44                            "Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP})
45
46
47 class cmd_group_add(Command):
48     """Creates a new AD group.
49
50 This command creates a new Active Directory group.  The groupname specified on the command is a unique sAMAccountName.
51
52 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.
53
54 Groups may also be used to establish email distribution lists, using --group-type=Distribution.
55
56 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.
57
58 The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
59
60 The command may be run from the root userid or another authorized userid.  The
61 -H or --URL= option can be used to execute the command on a remote server.
62
63 Example1:
64 samba-tool group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
65
66 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.
67
68 Example2:
69 sudo samba-tool group add Group2 --group-type=Distribution
70
71 Example2 adds a new distribution group to the local server.  The command is run under root using the sudo command.
72
73 Example3:
74 samba-tool group add Group3 --nis-domain=samdom --gid-number=12345
75
76 Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (both options are required to enable this feature).
77 """
78
79     synopsis = "%prog <groupname> [options]"
80
81     takes_optiongroups = {
82         "sambaopts": options.SambaOptions,
83         "versionopts": options.VersionOptions,
84         "credopts": options.CredentialsOptions,
85     }
86
87     takes_options = [
88         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
89                metavar="URL", dest="H"),
90         Option("--groupou",
91            help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
92            type=str),
93         Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
94             help="Group scope (Domain | Global | Universal)"),
95         Option("--group-type", type="choice", choices=["Security", "Distribution"],
96             help="Group type (Security | Distribution)"),
97         Option("--description", help="Group's description", type=str),
98         Option("--mail-address", help="Group's email address", type=str),
99         Option("--notes", help="Groups's notes", type=str),
100         Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
101         Option("--nis-domain", help="SFU30 NIS Domain", type=str),
102     ]
103
104     takes_args = ["groupname"]
105
106     def run(self, groupname, credopts=None, sambaopts=None,
107             versionopts=None, H=None, groupou=None, group_scope=None,
108             group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None):
109
110         if (group_type or "Security") == "Security":
111             gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
112         else:
113             gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
114
115         if (gid_number is None and nis_domain is not None) or (gid_number is not None and nis_domain is None):
116             raise CommandError('Both --gid-number and --nis-domain have to be set for a RFC2307-enabled group. Operation cancelled.')
117
118         lp = sambaopts.get_loadparm()
119         creds = credopts.get_credentials(lp, fallback_machine=True)
120
121         try:
122             samdb = SamDB(url=H, session_info=system_session(),
123                           credentials=creds, lp=lp)
124             samdb.newgroup(groupname, groupou=groupou, grouptype = gtype,
125                           description=description, mailaddress=mail_address, notes=notes,
126                           gidnumber=gid_number, nisdomain=nis_domain)
127         except Exception, e:
128             # FIXME: catch more specific exception
129             raise CommandError('Failed to create group "%s"' % groupname, e)
130         self.outf.write("Added group %s\n" % groupname)
131
132
133 class cmd_group_delete(Command):
134     """Deletes an AD group.
135
136 The command deletes an existing AD group from the Active Directory domain.  The groupname specified on the command is the sAMAccountName.
137
138 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.
139
140 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.
141
142 Example1:
143 samba-tool group delete Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
144
145 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.
146
147 Example2:
148 sudo samba-tool group delete Group2
149
150 Example2 deletes group Group2 from the local server.  The command is run under root using the sudo command.
151 """
152
153     synopsis = "%prog <groupname> [options]"
154
155     takes_optiongroups = {
156         "sambaopts": options.SambaOptions,
157         "versionopts": options.VersionOptions,
158         "credopts": options.CredentialsOptions,
159     }
160
161     takes_options = [
162         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
163                metavar="URL", dest="H"),
164     ]
165
166     takes_args = ["groupname"]
167
168     def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
169
170         lp = sambaopts.get_loadparm()
171         creds = credopts.get_credentials(lp, fallback_machine=True)
172         samdb = SamDB(url=H, session_info=system_session(),
173                       credentials=creds, lp=lp)
174
175         filter = ("(&(sAMAccountName=%s)(objectClass=group))" %
176                   groupname)
177
178         try:
179             res = samdb.search(base=samdb.domain_dn(),
180                                scope=ldb.SCOPE_SUBTREE,
181                                expression=filter,
182                                attrs=["dn"])
183             group_dn = res[0].dn
184         except IndexError:
185             raise CommandError('Unable to find group "%s"' % (groupname))
186
187         try:
188             samdb.delete(group_dn)
189         except Exception, e:
190             # FIXME: catch more specific exception
191             raise CommandError('Failed to remove group "%s"' % groupname, e)
192         self.outf.write("Deleted group %s\n" % groupname)
193
194
195 class cmd_group_add_members(Command):
196     """Add members to an AD group.
197
198 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.
199
200 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.
201
202 Example1:
203 samba-tool group addmembers supergroup Group1,Group2,User1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
204
205 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.
206
207 Example2:
208 sudo samba-tool group addmembers supergroup User2
209
210 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.
211 """
212
213     synopsis = "%prog <groupname> <listofmembers> [options]"
214
215     takes_optiongroups = {
216         "sambaopts": options.SambaOptions,
217         "versionopts": options.VersionOptions,
218         "credopts": options.CredentialsOptions,
219     }
220
221     takes_options = [
222         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
223                metavar="URL", dest="H"),
224     ]
225
226     takes_args = ["groupname", "listofmembers"]
227
228     def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
229             versionopts=None, H=None):
230
231         lp = sambaopts.get_loadparm()
232         creds = credopts.get_credentials(lp, fallback_machine=True)
233
234         try:
235             samdb = SamDB(url=H, session_info=system_session(),
236                           credentials=creds, lp=lp)
237             groupmembers = listofmembers.split(',')
238             samdb.add_remove_group_members(groupname, groupmembers,
239                     add_members_operation=True)
240         except Exception, e:
241             # FIXME: catch more specific exception
242             raise CommandError('Failed to add members "%s" to group "%s"' % (
243                 listofmembers, groupname), e)
244         self.outf.write("Added members to group %s\n" % groupname)
245
246
247 class cmd_group_remove_members(Command):
248     """Remove members from an AD group.
249
250 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.
251
252 When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
253
254 Example1:
255 samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
256
257 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.
258
259 Example2:
260 sudo samba-tool group removemembers supergroup User1
261
262 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.
263 """
264
265     synopsis = "%prog <groupname> <listofmembers> [options]"
266
267     takes_optiongroups = {
268         "sambaopts": options.SambaOptions,
269         "versionopts": options.VersionOptions,
270         "credopts": options.CredentialsOptions,
271     }
272
273     takes_options = [
274         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
275                metavar="URL", dest="H"),
276     ]
277
278     takes_args = ["groupname", "listofmembers"]
279
280     def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
281             versionopts=None, H=None):
282
283         lp = sambaopts.get_loadparm()
284         creds = credopts.get_credentials(lp, fallback_machine=True)
285
286         try:
287             samdb = SamDB(url=H, session_info=system_session(),
288                           credentials=creds, lp=lp)
289             samdb.add_remove_group_members(groupname, listofmembers.split(","),
290                     add_members_operation=False)
291         except Exception, e:
292             # FIXME: Catch more specific exception
293             raise CommandError('Failed to remove members "%s" from group "%s"' % (listofmembers, groupname), e)
294         self.outf.write("Removed members from group %s\n" % groupname)
295
296
297 class cmd_group_list(Command):
298     """List all groups."""
299
300     synopsis = "%prog [options]"
301
302     takes_options = [
303         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
304                metavar="URL", dest="H"),
305         Option("-v", "--verbose",
306                help="Verbose output, showing group type and group scope.",
307                action="store_true"),
308
309         ]
310
311     takes_optiongroups = {
312         "sambaopts": options.SambaOptions,
313         "credopts": options.CredentialsOptions,
314         "versionopts": options.VersionOptions,
315         }
316
317     def run(self, sambaopts=None, credopts=None, versionopts=None, H=None,
318             verbose=False):
319         lp = sambaopts.get_loadparm()
320         creds = credopts.get_credentials(lp, fallback_machine=True)
321
322         samdb = SamDB(url=H, session_info=system_session(),
323             credentials=creds, lp=lp)
324
325         domain_dn = samdb.domain_dn()
326         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
327                     expression=("(objectClass=group)"),
328                     attrs=["samaccountname", "grouptype"])
329         if (len(res) == 0):
330             return
331
332         if verbose:
333             self.outf.write("Group Name                                  Group Type      Group Scope\n")
334             self.outf.write("-----------------------------------------------------------------------------\n")
335
336             for msg in res:
337                 self.outf.write("%-44s" % msg.get("samaccountname", idx=0))
338                 hgtype = hex(int("%s" % msg["grouptype"]) & 0x00000000FFFFFFFF)
339                 if (hgtype == hex(int(security_group.get("Builtin")))):
340                     self.outf.write("Security         Builtin\n")
341                 elif (hgtype == hex(int(security_group.get("Domain")))):
342                     self.outf.write("Security         Domain\n")
343                 elif (hgtype == hex(int(security_group.get("Global")))):
344                     self.outf.write("Security         Global\n")
345                 elif (hgtype == hex(int(security_group.get("Universal")))):
346                     self.outf.write("Security         Universal\n")
347                 elif (hgtype == hex(int(distribution_group.get("Global")))):
348                     self.outf.write("Distribution     Global\n")
349                 elif (hgtype == hex(int(distribution_group.get("Domain")))):
350                     self.outf.write("Distribution     Domain\n")
351                 elif (hgtype == hex(int(distribution_group.get("Universal")))):
352                     self.outf.write("Distribution     Universal\n")
353                 else:
354                     self.outf.write("\n")
355         else:
356             for msg in res:
357                 self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
358
359 class cmd_group_list_members(Command):
360     """List all members of an AD group.
361
362 This command lists members from an existing Active Directory group. The command accepts one group name.
363
364 Example1:
365 samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
366 """
367
368     synopsis = "%prog <groupname> [options]"
369
370     takes_options = [
371         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
372                metavar="URL", dest="H"),
373         ]
374
375     takes_optiongroups = {
376         "sambaopts": options.SambaOptions,
377         "credopts": options.CredentialsOptions,
378         "versionopts": options.VersionOptions,
379         }
380
381     takes_args = ["groupname"]
382
383     def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
384         lp = sambaopts.get_loadparm()
385         creds = credopts.get_credentials(lp, fallback_machine=True)
386
387         try:
388             samdb = SamDB(url=H, session_info=system_session(),
389                           credentials=creds, lp=lp)
390
391             search_filter = "(&(objectClass=group)(samaccountname=%s))" % groupname
392             res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
393                                expression=(search_filter),
394                                attrs=["objectSid"])
395
396             if (len(res) != 1):
397                 return
398
399             group_dn = res[0].get('dn', idx=0)
400             object_sid = res[0].get('objectSid', idx=0)
401
402             object_sid = ndr_unpack(security.dom_sid, object_sid)
403             (group_dom_sid, rid) = object_sid.split()
404
405             search_filter = "(|(primaryGroupID=%s)(memberOf=%s))" % (rid, group_dn)
406             res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
407                                expression=(search_filter),
408                                attrs=["samAccountName", "cn"])
409
410             if (len(res) == 0):
411                 return
412
413             for msg in res:
414                 member_name = msg.get("samAccountName", idx=0)
415                 if member_name is None:
416                     member_name = msg.get("cn", idx=0)
417                 self.outf.write("%s\n" % member_name)
418
419         except Exception, e:
420             raise CommandError('Failed to list members of "%s" group ' % groupname, e)
421
422
423 class cmd_group(SuperCommand):
424     """Group management."""
425
426     subcommands = {}
427     subcommands["add"] = cmd_group_add()
428     subcommands["delete"] = cmd_group_delete()
429     subcommands["addmembers"] = cmd_group_add_members()
430     subcommands["removemembers"] = cmd_group_remove_members()
431     subcommands["list"] = cmd_group_list()
432     subcommands["listmembers"] = cmd_group_list_members()