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