samba-tool: Unify usage messages.
[samba.git] / source4 / scripting / python / samba / netcmd / gpo.py
1 # implement samba_tool gpo commands
2 #
3 # Copyright Andrew Tridgell 2010
4 # Copyright Amitay Isaacs 2011-2012 <amitay@gmail.com>
5 #
6 # based on C implementation by Guenther Deschner and Wilco Baan Hofman
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21
22 import os
23 import samba.getopt as options
24 import ldb
25
26 from samba.auth import system_session
27 from samba.netcmd import (
28     Command,
29     CommandError,
30     Option,
31     SuperCommand,
32     )
33 from samba.samdb import SamDB
34 from samba import dsdb
35 from samba.dcerpc import security
36 from samba.ndr import ndr_unpack
37 import samba.security
38 import samba.auth
39 from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
40 from samba.netcmd.common import netcmd_finddc
41 from samba import policy
42 from samba import smb
43 import uuid
44 from samba.ntacls import dsacl2fsacl
45
46
47 def samdb_connect(ctx):
48     '''make a ldap connection to the server'''
49     try:
50         ctx.samdb = SamDB(url=ctx.url,
51                           session_info=system_session(),
52                           credentials=ctx.creds, lp=ctx.lp)
53     except Exception, e:
54         raise CommandError("LDAP connection to %s failed " % ctx.url, e)
55
56
57 def attr_default(msg, attrname, default):
58     '''get an attribute from a ldap msg with a default'''
59     if attrname in msg:
60         return msg[attrname][0]
61     return default
62
63
64 def gpo_flags_string(value):
65     '''return gpo flags string'''
66     flags = policy.get_gpo_flags(value)
67     if not flags:
68         ret = 'NONE'
69     else:
70         ret = ' '.join(flags)
71     return ret
72
73
74 def gplink_options_string(value):
75     '''return gplink options string'''
76     options = policy.get_gplink_options(value)
77     if not options:
78         ret = 'NONE'
79     else:
80         ret = ' '.join(options)
81     return ret
82
83
84 def parse_gplink(gplink):
85     '''parse a gPLink into an array of dn and options'''
86     ret = []
87     a = gplink.split(']')
88     for g in a:
89         if not g:
90             continue
91         d = g.split(';')
92         if len(d) != 2 or not d[0].startswith("[LDAP://"):
93             raise RuntimeError("Badly formed gPLink '%s'" % g)
94         ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
95     return ret
96
97
98 def encode_gplink(gplist):
99     '''Encode an array of dn and options into gPLink string'''
100     ret = ''
101     for g in gplist:
102         ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
103     return ret
104
105
106 def dc_url(lp, creds, url=None, dc=None):
107     '''If URL is not specified, return URL for writable DC.
108     If dc is provided, use that to construct ldap URL'''
109
110     if url is None:
111         if dc is None:
112             try:
113                 dc = netcmd_finddc(lp, creds)
114             except Exception, e:
115                 raise RuntimeError("Could not find a DC for domain", e)
116         url = 'ldap://' + dc
117     return url
118
119
120 def get_gpo_dn(samdb, gpo):
121     '''Construct the DN for gpo'''
122
123     dn = samdb.get_default_basedn()
124     dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
125     dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
126     return dn
127
128
129 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None):
130     '''Get GPO information using gpo, displayname or dn'''
131
132     policies_dn = samdb.get_default_basedn()
133     policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
134
135     base_dn = policies_dn
136     search_expr = "(objectClass=groupPolicyContainer)"
137     search_scope = ldb.SCOPE_ONELEVEL
138
139     if gpo is not None:
140         search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
141
142     if displayname is not None:
143         search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
144
145     if dn is not None:
146         base_dn = dn
147         search_scope = ldb.SCOPE_BASE
148
149     try:
150         msg = samdb.search(base=base_dn, scope=search_scope,
151                             expression=search_expr,
152                             attrs=['nTSecurityDescriptor',
153                                     'versionNumber',
154                                     'flags',
155                                     'name',
156                                     'displayName',
157                                     'gPCFileSysPath'])
158     except Exception, e:
159         if gpo is not None:
160             mesg = "Cannot get information for GPO %s" % gpo
161         else:
162             mesg = "Cannot get information for GPOs"
163         raise CommandError(mesg, e)
164
165     return msg
166
167
168 def get_gpo_containers(samdb, gpo):
169     '''lists dn of containers for a GPO'''
170
171     search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
172     try:
173         msg = samdb.search(expression=search_expr, attrs=['gPLink'])
174     except Exception, e:
175         raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
176
177     return msg
178
179
180 def del_gpo_link(samdb, container_dn, gpo):
181     '''delete GPO link for the container'''
182     # Check if valid Container DN and get existing GPlinks
183     try:
184         msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
185                             expression="(objectClass=*)",
186                             attrs=['gPLink'])[0]
187     except Exception, e:
188         raise CommandError("Container '%s' does not exist" % container_dn, e)
189
190     found = False
191     gpo_dn = str(get_gpo_dn(samdb, gpo))
192     if 'gPLink' in msg:
193         gplist = parse_gplink(msg['gPLink'][0])
194         for g in gplist:
195             if g['dn'].lower() == gpo_dn.lower():
196                 gplist.remove(g)
197                 found = True
198                 break
199     else:
200         raise CommandError("No GPO(s) linked to this container")
201
202     if not found:
203         raise CommandError("GPO '%s' not linked to this container" % gpo)
204
205     m = ldb.Message()
206     m.dn = container_dn
207     if gplist:
208         gplink_str = encode_gplink(gplist)
209         m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
210     else:
211         m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
212     try:
213         samdb.modify(m)
214     except Exception, e:
215         raise CommandError("Error removing GPO from container", e)
216
217
218 def parse_unc(unc):
219     '''Parse UNC string into a hostname, a service, and a filepath'''
220     if unc.startswith('\\\\') and unc.startswith('//'):
221         raise ValueError("UNC doesn't start with \\\\ or //")
222     tmp = unc[2:].split('/', 2)
223     if len(tmp) == 3:
224         return tmp
225     tmp = unc[2:].split('\\', 2)
226     if len(tmp) == 3:
227         return tmp
228     raise ValueError("Invalid UNC string: %s" % unc)
229
230
231 def copy_directory_remote_to_local(conn, remotedir, localdir):
232     if not os.path.isdir(localdir):
233         os.mkdir(localdir)
234     r_dirs = [ remotedir ]
235     l_dirs = [ localdir ]
236     while r_dirs:
237         r_dir = r_dirs.pop()
238         l_dir = l_dirs.pop()
239
240         dirlist = conn.list(r_dir)
241         for e in dirlist:
242             r_name = r_dir + '\\' + e['name']
243             l_name = os.path.join(l_dir, e['name'])
244
245             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
246                 r_dirs.append(r_name)
247                 l_dirs.append(l_name)
248                 os.mkdir(l_name)
249             else:
250                 data = conn.loadfile(r_name)
251                 file(l_name, 'w').write(data)
252
253
254 def copy_directory_local_to_remote(conn, localdir, remotedir):
255     if not conn.chkpath(remotedir):
256         conn.mkdir(remotedir)
257     l_dirs = [ localdir ]
258     r_dirs = [ remotedir ]
259     while l_dirs:
260         l_dir = l_dirs.pop()
261         r_dir = r_dirs.pop()
262
263         dirlist = os.listdir(l_dir)
264         for e in dirlist:
265             l_name = os.path.join(l_dir, e)
266             r_name = r_dir + '\\' + e
267
268             if os.path.isdir(l_name):
269                 l_dirs.append(l_name)
270                 r_dirs.append(r_name)
271                 conn.mkdir(r_name)
272             else:
273                 data = file(l_name, 'r').read()
274                 conn.savefile(r_name, data)
275
276
277 def create_directory_hier(conn, remotedir):
278     elems = remotedir.replace('/', '\\').split('\\')
279     path = ""
280     for e in elems:
281         path = path + '\\' + e
282         if not conn.chkpath(path):
283             conn.mkdir(path)
284
285
286 class cmd_listall(Command):
287     """List all GPOs."""
288
289     synopsis = "%prog [options]"
290
291     takes_optiongroups = {
292         "sambaopts": options.SambaOptions,
293         "versionopts": options.VersionOptions,
294         "credopts": options.CredentialsOptions,
295     }
296
297     takes_options = [
298         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
299                metavar="URL", dest="H")
300         ]
301
302     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
303
304         self.lp = sambaopts.get_loadparm()
305         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
306
307         self.url = dc_url(self.lp, self.creds, H)
308
309         samdb_connect(self)
310
311         msg = get_gpo_info(self.samdb, None)
312
313         for m in msg:
314             self.outf.write("GPO          : %s\n" % m['name'][0])
315             self.outf.write("display name : %s\n" % m['displayName'][0])
316             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
317             self.outf.write("dn           : %s\n" % m.dn)
318             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
319             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
320             self.outf.write("\n")
321
322
323 class cmd_list(Command):
324     """List GPOs for an account."""
325
326     synopsis = "%prog <username> [options]"
327
328     takes_args = ['username']
329     takes_optiongroups = {
330         "sambaopts": options.SambaOptions,
331         "versionopts": options.VersionOptions,
332         "credopts": options.CredentialsOptions,
333     }
334
335     takes_options = [
336         Option("-H", "--URL", help="LDB URL for database or target server",
337             type=str, metavar="URL", dest="H")
338         ]
339
340     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
341
342         self.lp = sambaopts.get_loadparm()
343         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
344
345         self.url = dc_url(self.lp, self.creds, H)
346
347         samdb_connect(self)
348
349         try:
350             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
351                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
352             user_dn = msg[0].dn
353         except Exception:
354             raise CommandError("Failed to find account %s" % username)
355
356         # check if its a computer account
357         try:
358             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
359             is_computer = 'computer' in msg['objectClass']
360         except Exception:
361             raise CommandError("Failed to find objectClass for user %s" % username)
362
363         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
364                                AUTH_SESSION_INFO_AUTHENTICATED )
365
366         # When connecting to a remote server, don't look up the local privilege DB
367         if self.url is not None and self.url.startswith('ldap'):
368             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
369
370         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
371                                           session_info_flags=session_info_flags)
372
373         token = session.security_token
374
375         gpos = []
376
377         inherit = True
378         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
379         while True:
380             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
381             if 'gPLink' in msg:
382                 glist = parse_gplink(msg['gPLink'][0])
383                 for g in glist:
384                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
385                         continue
386                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
387                         continue
388
389                     try:
390                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
391                                                  attrs=['name', 'displayName', 'flags',
392                                                         'ntSecurityDescriptor'])
393                     except Exception:
394                         self.outf.write("Failed to fetch gpo object %s\n" %
395                             g['dn'])
396                         continue
397
398                     secdesc_ndr = gmsg[0]['ntSecurityDescriptor'][0]
399                     secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
400
401                     try:
402                         samba.security.access_check(secdesc, token,
403                                                     security.SEC_STD_READ_CONTROL |
404                                                     security.SEC_ADS_LIST |
405                                                     security.SEC_ADS_READ_PROP)
406                     except RuntimeError:
407                         self.outf.write("Failed access check on %s\n" % msg.dn)
408                         continue
409
410                     # check the flags on the GPO
411                     flags = int(attr_default(gmsg[0], 'flags', 0))
412                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
413                         continue
414                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
415                         continue
416                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
417
418             # check if this blocks inheritance
419             gpoptions = int(attr_default(msg, 'gPOptions', 0))
420             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
421                 inherit = False
422
423             if dn == self.samdb.get_default_basedn():
424                 break
425             dn = dn.parent()
426
427         if is_computer:
428             msg_str = 'computer'
429         else:
430             msg_str = 'user'
431
432         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
433         for g in gpos:
434             self.outf.write("    %s %s\n" % (g[0], g[1]))
435
436
437 class cmd_show(Command):
438     """Show information for a GPO."""
439
440     synopsis = "%prog <gpo> [options]"
441
442     takes_optiongroups = {
443         "sambaopts": options.SambaOptions,
444         "versionopts": options.VersionOptions,
445         "credopts": options.CredentialsOptions,
446     }
447
448     takes_args = ['gpo']
449
450     takes_options = [
451         Option("-H", help="LDB URL for database or target server", type=str)
452         ]
453
454     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
455
456         self.lp = sambaopts.get_loadparm()
457         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
458
459         self.url = dc_url(self.lp, self.creds, H)
460
461         samdb_connect(self)
462
463         try:
464             msg = get_gpo_info(self.samdb, gpo)[0]
465         except Exception:
466             raise CommandError("GPO '%s' does not exist" % gpo)
467
468         secdesc_ndr = msg['ntSecurityDescriptor'][0]
469         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
470
471         self.outf.write("GPO          : %s\n" % msg['name'][0])
472         self.outf.write("display name : %s\n" % msg['displayName'][0])
473         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
474         self.outf.write("dn           : %s\n" % msg.dn)
475         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
476         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
477         self.outf.write("ACL          : %s\n" % secdesc.as_sddl())
478         self.outf.write("\n")
479
480
481 class cmd_getlink(Command):
482     """List GPO Links for a container."""
483
484     synopsis = "%prog <container_dn> [options]"
485
486     takes_optiongroups = {
487         "sambaopts": options.SambaOptions,
488         "versionopts": options.VersionOptions,
489         "credopts": options.CredentialsOptions,
490     }
491
492     takes_args = ['container_dn']
493
494     takes_options = [
495         Option("-H", help="LDB URL for database or target server", type=str)
496         ]
497
498     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
499                 versionopts=None):
500
501         self.lp = sambaopts.get_loadparm()
502         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
503
504         self.url = dc_url(self.lp, self.creds, H)
505
506         samdb_connect(self)
507
508         try:
509             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
510                                     expression="(objectClass=*)",
511                                     attrs=['gPLink'])[0]
512         except Exception:
513             raise CommandError("Container '%s' does not exist" % container_dn)
514
515         if msg['gPLink']:
516             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
517             gplist = parse_gplink(msg['gPLink'][0])
518             for g in gplist:
519                 msg = get_gpo_info(self.samdb, dn=g['dn'])
520                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
521                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
522                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
523                 self.outf.write("\n")
524         else:
525             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
526
527
528 class cmd_setlink(Command):
529     """Add or update a GPO link to a container."""
530
531     synopsis = "%prog <container_dn> <gpo> [options]"
532
533     takes_optiongroups = {
534         "sambaopts": options.SambaOptions,
535         "versionopts": options.VersionOptions,
536         "credopts": options.CredentialsOptions,
537     }
538
539     takes_args = ['container_dn', 'gpo']
540
541     takes_options = [
542         Option("-H", help="LDB URL for database or target server", type=str),
543         Option("--disable", dest="disabled", default=False, action='store_true',
544             help="Disable policy"),
545         Option("--enforce", dest="enforced", default=False, action='store_true',
546             help="Enforce policy")
547         ]
548
549     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
550                 sambaopts=None, credopts=None, versionopts=None):
551
552         self.lp = sambaopts.get_loadparm()
553         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
554
555         self.url = dc_url(self.lp, self.creds, H)
556
557         samdb_connect(self)
558
559         gplink_options = 0
560         if disabled:
561             gplink_options |= dsdb.GPLINK_OPT_DISABLE
562         if enforced:
563             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
564
565         # Check if valid GPO DN
566         try:
567             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
568         except Exception:
569             raise CommandError("GPO '%s' does not exist" % gpo)
570         gpo_dn = str(get_gpo_dn(self.samdb, gpo))
571
572         # Check if valid Container DN
573         try:
574             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
575                                     expression="(objectClass=*)",
576                                     attrs=['gPLink'])[0]
577         except Exception:
578             raise CommandError("Container '%s' does not exist" % container_dn)
579
580         # Update existing GPlinks or Add new one
581         existing_gplink = False
582         if 'gPLink' in msg:
583             gplist = parse_gplink(msg['gPLink'][0])
584             existing_gplink = True
585             found = False
586             for g in gplist:
587                 if g['dn'].lower() == gpo_dn.lower():
588                     g['options'] = gplink_options
589                     found = True
590                     break
591             if found:
592                 raise CommandError("GPO '%s' already linked to this container" % gpo)
593             else:
594                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
595         else:
596             gplist = []
597             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
598
599         gplink_str = encode_gplink(gplist)
600
601         m = ldb.Message()
602         m.dn = ldb.Dn(self.samdb, container_dn)
603
604         if existing_gplink:
605             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
606         else:
607             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
608
609         try:
610             self.samdb.modify(m)
611         except Exception, e:
612             raise CommandError("Error adding GPO Link", e)
613
614         self.outf.write("Added/Updated GPO link\n")
615         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
616
617
618 class cmd_dellink(Command):
619     """Delete GPO link from a container."""
620
621     synopsis = "%prog <container_dn> <gpo> [options]"
622
623     takes_optiongroups = {
624         "sambaopts": options.SambaOptions,
625         "versionopts": options.VersionOptions,
626         "credopts": options.CredentialsOptions,
627     }
628
629     takes_args = ['container', 'gpo']
630
631     takes_options = [
632         Option("-H", help="LDB URL for database or target server", type=str),
633         ]
634
635     def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
636                 versionopts=None):
637
638         self.lp = sambaopts.get_loadparm()
639         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
640
641         self.url = dc_url(self.lp, self.creds, H)
642
643         samdb_connect(self)
644
645         # Check if valid GPO
646         try:
647             get_gpo_info(self.samdb, gpo=gpo)[0]
648         except Exception:
649             raise CommandError("GPO '%s' does not exist" % gpo)
650
651         container_dn = ldb.Dn(self.samdb, container)
652         del_gpo_link(self.samdb, container_dn, gpo)
653         self.outf.write("Deleted GPO link.\n")
654         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
655
656
657 class cmd_listcontainers(Command):
658     """List all linked containers for a GPO."""
659
660     synopsis = "%prog <gpo> [options]"
661
662     takes_optiongroups = {
663         "sambaopts": options.SambaOptions,
664         "versionopts": options.VersionOptions,
665         "credopts": options.CredentialsOptions,
666     }
667
668     takes_args = ['gpo']
669
670     takes_options = [
671         Option("-H", help="LDB URL for database or target server", type=str)
672         ]
673
674     def run(self, gpo, H=None, sambaopts=None, credopts=None,
675                 versionopts=None):
676
677         self.lp = sambaopts.get_loadparm()
678         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
679
680         self.url = dc_url(self.lp, self.creds, H)
681
682         samdb_connect(self)
683
684         msg = get_gpo_containers(self.samdb, gpo)
685         if len(msg):
686             self.outf.write("Container(s) using GPO %s\n" % gpo)
687             for m in msg:
688                 self.outf.write("    DN: %s\n" % m['dn'])
689         else:
690             self.outf.write("No Containers using GPO %s\n" % gpo)
691
692
693 class cmd_getinheritance(Command):
694     """Get inheritance flag for a container."""
695
696     synopsis = "%prog <container_dn> [options]"
697
698     takes_optiongroups = {
699         "sambaopts": options.SambaOptions,
700         "versionopts": options.VersionOptions,
701         "credopts": options.CredentialsOptions,
702     }
703
704     takes_args = ['container_dn']
705
706     takes_options = [
707         Option("-H", help="LDB URL for database or target server", type=str)
708         ]
709
710     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
711                 versionopts=None):
712
713         self.lp = sambaopts.get_loadparm()
714         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
715
716         self.url = dc_url(self.lp, self.creds, H)
717
718         samdb_connect(self)
719
720         try:
721             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
722                                     expression="(objectClass=*)",
723                                     attrs=['gPOptions'])[0]
724         except Exception:
725             raise CommandError("Container '%s' does not exist" % container_dn)
726
727         inheritance = 0
728         if 'gPOptions' in msg:
729             inheritance = int(msg['gPOptions'][0])
730
731         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
732             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
733         else:
734             self.outf.write("Container has GPO_INHERIT\n")
735
736
737 class cmd_setinheritance(Command):
738     """Set inheritance flag on a container."""
739
740     synopsis = "%prog <container_dn> <block|inherit> [options]"
741
742     takes_optiongroups = {
743         "sambaopts": options.SambaOptions,
744         "versionopts": options.VersionOptions,
745         "credopts": options.CredentialsOptions,
746     }
747
748     takes_args = [ 'container_dn', 'inherit_state' ]
749
750     takes_options = [
751         Option("-H", help="LDB URL for database or target server", type=str)
752         ]
753
754     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
755                 versionopts=None):
756
757         if inherit_state.lower() == 'block':
758             inheritance = dsdb.GPO_BLOCK_INHERITANCE
759         elif inherit_state.lower() == 'inherit':
760             inheritance = dsdb.GPO_INHERIT
761         else:
762             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
763
764         self.lp = sambaopts.get_loadparm()
765         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
766
767         self.url = dc_url(self.lp, self.creds, H)
768
769         samdb_connect(self)
770         try:
771             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
772                                     expression="(objectClass=*)",
773                                     attrs=['gPOptions'])[0]
774         except Exception:
775             raise CommandError("Container '%s' does not exist" % container_dn)
776
777         m = ldb.Message()
778         m.dn = ldb.Dn(self.samdb, container_dn)
779
780         if 'gPOptions' in msg:
781             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
782         else:
783             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
784
785         try:
786             self.samdb.modify(m)
787         except Exception, e:
788             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
789
790
791 class cmd_fetch(Command):
792     """Download a GPO."""
793
794     synopsis = "%prog <gpo> [options]"
795
796     takes_optiongroups = {
797         "sambaopts": options.SambaOptions,
798         "versionopts": options.VersionOptions,
799         "credopts": options.CredentialsOptions,
800     }
801
802     takes_args = ['gpo']
803
804     takes_options = [
805         Option("-H", help="LDB URL for database or target server", type=str),
806         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
807         ]
808
809     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
810
811         self.lp = sambaopts.get_loadparm()
812         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
813
814         # We need to know writable DC to setup SMB connection
815         if H and H.startswith('ldap://'):
816             dc_hostname = H[7:]
817             self.url = H
818         else:
819             dc_hostname = netcmd_finddc(self.lp, self.creds)
820             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
821
822         samdb_connect(self)
823         try:
824             msg = get_gpo_info(self.samdb, gpo)[0]
825         except Exception:
826             raise CommandError("GPO '%s' does not exist" % gpo)
827
828         # verify UNC path
829         unc = msg['gPCFileSysPath'][0]
830         try:
831             [dom_name, service, sharepath] = parse_unc(unc)
832         except ValueError:
833             raise CommandError("Invalid GPO path (%s)" % unc)
834
835         # SMB connect to DC
836         try:
837             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
838         except Exception:
839             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
840
841         # Copy GPT
842         if tmpdir is None:
843             tmpdir = "/tmp"
844         if not os.path.isdir(tmpdir):
845             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
846
847         localdir = os.path.join(tmpdir, "policy")
848         if not os.path.isdir(localdir):
849             os.mkdir(localdir)
850
851         gpodir = os.path.join(localdir, gpo)
852         if os.path.isdir(gpodir):
853             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
854
855         try:
856             os.mkdir(gpodir)
857             copy_directory_remote_to_local(conn, sharepath, gpodir)
858         except Exception, e:
859             # FIXME: Catch more specific exception
860             raise CommandError("Error copying GPO from DC", e)
861         self.outf.write('GPO copied to %s\n' % gpodir)
862
863
864 class cmd_create(Command):
865     """Create an empty GPO."""
866
867     synopsis = "%prog <displayname> [options]"
868
869     takes_optiongroups = {
870         "sambaopts": options.SambaOptions,
871         "versionopts": options.VersionOptions,
872         "credopts": options.CredentialsOptions,
873     }
874
875     takes_args = ['displayname']
876
877     takes_options = [
878         Option("-H", help="LDB URL for database or target server", type=str),
879         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
880         ]
881
882     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
883             versionopts=None):
884
885         self.lp = sambaopts.get_loadparm()
886         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
887
888         # We need to know writable DC to setup SMB connection
889         if H and H.startswith('ldap://'):
890             dc_hostname = H[7:]
891             self.url = H
892         else:
893             dc_hostname = netcmd_finddc(self.lp, self.creds)
894             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
895
896         samdb_connect(self)
897
898         msg = get_gpo_info(self.samdb, displayname=displayname)
899         if msg.count > 0:
900             raise CommandError("A GPO already existing with name '%s'" % displayname)
901
902         # Create new GUID
903         guid  = str(uuid.uuid4())
904         gpo = "{%s}" % guid.upper()
905         realm = self.lp.get('realm')
906         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
907
908         # Create GPT
909         if tmpdir is None:
910             tmpdir = "/tmp"
911         if not os.path.isdir(tmpdir):
912             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
913
914         localdir = os.path.join(tmpdir, "policy")
915         if not os.path.isdir(localdir):
916             os.mkdir(localdir)
917
918         gpodir = os.path.join(localdir, gpo)
919         if os.path.isdir(gpodir):
920             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
921
922         try:
923             os.mkdir(gpodir)
924             os.mkdir(os.path.join(gpodir, "Machine"))
925             os.mkdir(os.path.join(gpodir, "User"))
926             gpt_contents = "[General]\r\nVersion=0\r\n"
927             file(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
928         except Exception, e:
929             raise CommandError("Error Creating GPO files", e)
930
931         # Connect to DC over SMB
932         [dom_name, service, sharepath] = parse_unc(unc_path)
933         try:
934             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
935         except Exception, e:
936             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
937
938         self.samdb.transaction_start()
939         try:
940             # Add cn=<guid>
941             gpo_dn = get_gpo_dn(self.samdb, gpo)
942
943             m = ldb.Message()
944             m.dn = gpo_dn
945             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
946             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_ADD, "displayName")
947             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_ADD, "gPCFileSysPath")
948             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "flags")
949             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "versionNumber")
950             m['a06'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
951             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_ADD, "gpcFunctionalityVersion")
952             self.samdb.add(m)
953
954             # Add cn=User,cn=<guid>
955             m = ldb.Message()
956             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
957             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
958             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
959             self.samdb.add(m)
960
961             # Add cn=Machine,cn=<guid>
962             m = ldb.Message()
963             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
964             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
965             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
966             self.samdb.add(m)
967
968             # Copy GPO files over SMB
969             create_directory_hier(conn, sharepath)
970             copy_directory_local_to_remote(conn, gpodir, sharepath)
971
972             # Get new security descriptor
973             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
974             ds_sd_ndr = msg['ntSecurityDescriptor'][0]
975             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
976
977             # Create a file system security descriptor
978             domain_sid = self.samdb.get_domain_sid()
979             sddl = dsacl2fsacl(ds_sd, domain_sid)
980             fs_sd = security.descriptor.from_sddl(sddl, security.dom_sid(domain_sid))
981
982             # Set ACL
983             sio = ( security.SECINFO_OWNER |
984                     security.SECINFO_GROUP |
985                     security.SECINFO_DACL |
986                     security.SECINFO_PROTECTED_DACL )
987             conn.set_acl(sharepath, fs_sd, sio)
988         except Exception:
989             self.samdb.transaction_cancel()
990             raise
991         else:
992             self.samdb.transaction_commit()
993
994         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
995
996
997 class cmd_del(Command):
998     """Delete a GPO."""
999
1000     synopsis = "%prog <gpo> [options]"
1001
1002     takes_optiongroups = {
1003         "sambaopts": options.SambaOptions,
1004         "versionopts": options.VersionOptions,
1005         "credopts": options.CredentialsOptions,
1006     }
1007
1008     takes_args = ['gpo']
1009
1010     takes_options = [
1011         Option("-H", help="LDB URL for database or target server", type=str),
1012         ]
1013
1014     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1015                 versionopts=None):
1016
1017         self.lp = sambaopts.get_loadparm()
1018         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1019
1020         # We need to know writable DC to setup SMB connection
1021         if H and H.startswith('ldap://'):
1022             dc_hostname = H[7:]
1023             self.url = H
1024         else:
1025             dc_hostname = netcmd_finddc(self.lp, self.creds)
1026             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1027
1028         samdb_connect(self)
1029
1030         # Check if valid GPO
1031         try:
1032             get_gpo_info(self.samdb, gpo=gpo)[0]
1033         except Exception:
1034             raise CommandError("GPO '%s' does not exist" % gpo)
1035
1036         realm = self.lp.get('realm')
1037         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1038
1039         # Connect to DC over SMB
1040         [dom_name, service, sharepath] = parse_unc(unc_path)
1041         try:
1042             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1043         except Exception, e:
1044             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1045
1046         self.samdb.transaction_start()
1047         try:
1048             # Check for existing links
1049             msg = get_gpo_containers(self.samdb, gpo)
1050
1051             if len(msg):
1052                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1053                 for m in msg:
1054                     del_gpo_link(self.samdb, m['dn'], gpo)
1055                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1056
1057             # Remove LDAP entries
1058             gpo_dn = get_gpo_dn(self.samdb, gpo)
1059             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1060             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1061             self.samdb.delete(gpo_dn)
1062
1063             # Remove GPO files
1064             conn.deltree(sharepath)
1065
1066         except Exception:
1067             self.samdb.transaction_cancel()
1068             raise
1069         else:
1070             self.samdb.transaction_commit()
1071
1072         self.outf.write("GPO %s deleted.\n" % gpo)
1073
1074
1075 class cmd_gpo(SuperCommand):
1076     """Group Policy Object (GPO) management"""
1077
1078     subcommands = {}
1079     subcommands["listall"] = cmd_listall()
1080     subcommands["list"] = cmd_list()
1081     subcommands["show"] = cmd_show()
1082     subcommands["getlink"] = cmd_getlink()
1083     subcommands["setlink"] = cmd_setlink()
1084     subcommands["dellink"] = cmd_dellink()
1085     subcommands["listcontainers"] = cmd_listcontainers()
1086     subcommands["getinheritance"] = cmd_getinheritance()
1087     subcommands["setinheritance"] = cmd_setinheritance()
1088     subcommands["fetch"] = cmd_fetch()
1089     subcommands["create"] = cmd_create()
1090     subcommands["del"] = cmd_del()