gp_ini: Add a fdeploy1 parser for better generalization
[samba.git] / 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 import re
26 import xml.etree.ElementTree as ET
27 import shutil
28
29 from samba.auth import system_session
30 from samba.netcmd import (
31     Command,
32     CommandError,
33     Option,
34     SuperCommand,
35     )
36 from samba.samdb import SamDB
37 from samba import dsdb
38 from samba.dcerpc import security
39 from samba.ndr import ndr_unpack
40 import samba.security
41 import samba.auth
42 from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
43 from samba.netcmd.common import netcmd_finddc
44 from samba import policy
45 from samba import smb
46 from samba import NTSTATUSError
47 import uuid
48 from samba.ntacls import dsacl2fsacl
49 from samba.dcerpc import nbt
50 from samba.net import Net
51 from samba.gp_parse import GPParser, GPNoParserException
52 from samba.gp_parse.gp_pol import GPPolParser
53 from samba.gp_parse.gp_ini import GPIniParser, GPTIniParser, GPFDeploy1IniParser
54 from samba.gp_parse.gp_csv import GPAuditCsvParser
55 from samba.gp_parse.gp_inf import GptTmplInfParser
56 from samba.gp_parse.gp_aas import GPAasParser
57
58
59 def samdb_connect(ctx):
60     '''make a ldap connection to the server'''
61     try:
62         ctx.samdb = SamDB(url=ctx.url,
63                           session_info=system_session(),
64                           credentials=ctx.creds, lp=ctx.lp)
65     except Exception as e:
66         raise CommandError("LDAP connection to %s failed " % ctx.url, e)
67
68
69 def attr_default(msg, attrname, default):
70     '''get an attribute from a ldap msg with a default'''
71     if attrname in msg:
72         return msg[attrname][0]
73     return default
74
75
76 def gpo_flags_string(value):
77     '''return gpo flags string'''
78     flags = policy.get_gpo_flags(value)
79     if not flags:
80         ret = 'NONE'
81     else:
82         ret = ' '.join(flags)
83     return ret
84
85
86 def gplink_options_string(value):
87     '''return gplink options string'''
88     options = policy.get_gplink_options(value)
89     if not options:
90         ret = 'NONE'
91     else:
92         ret = ' '.join(options)
93     return ret
94
95
96 def parse_gplink(gplink):
97     '''parse a gPLink into an array of dn and options'''
98     ret = []
99     a = gplink.split(']')
100     for g in a:
101         if not g:
102             continue
103         d = g.split(';')
104         if len(d) != 2 or not d[0].startswith("[LDAP://"):
105             raise RuntimeError("Badly formed gPLink '%s'" % g)
106         ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
107     return ret
108
109
110 def encode_gplink(gplist):
111     '''Encode an array of dn and options into gPLink string'''
112     ret = ''
113     for g in gplist:
114         ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
115     return ret
116
117
118 def dc_url(lp, creds, url=None, dc=None):
119     '''If URL is not specified, return URL for writable DC.
120     If dc is provided, use that to construct ldap URL'''
121
122     if url is None:
123         if dc is None:
124             try:
125                 dc = netcmd_finddc(lp, creds)
126             except Exception as e:
127                 raise RuntimeError("Could not find a DC for domain", e)
128         url = 'ldap://' + dc
129     return url
130
131
132 def get_gpo_dn(samdb, gpo):
133     '''Construct the DN for gpo'''
134
135     dn = samdb.get_default_basedn()
136     dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
137     dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
138     return dn
139
140
141 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None,
142                  sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL|security.SECINFO_SACL):
143     '''Get GPO information using gpo, displayname or dn'''
144
145     policies_dn = samdb.get_default_basedn()
146     policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
147
148     base_dn = policies_dn
149     search_expr = "(objectClass=groupPolicyContainer)"
150     search_scope = ldb.SCOPE_ONELEVEL
151
152     if gpo is not None:
153         search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
154
155     if displayname is not None:
156         search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
157
158     if dn is not None:
159         base_dn = dn
160         search_scope = ldb.SCOPE_BASE
161
162     try:
163         msg = samdb.search(base=base_dn, scope=search_scope,
164                             expression=search_expr,
165                             attrs=['nTSecurityDescriptor',
166                                     'versionNumber',
167                                     'flags',
168                                     'name',
169                                     'displayName',
170                                     'gPCFileSysPath'],
171                             controls=['sd_flags:1:%d' % sd_flags])
172     except Exception as e:
173         if gpo is not None:
174             mesg = "Cannot get information for GPO %s" % gpo
175         else:
176             mesg = "Cannot get information for GPOs"
177         raise CommandError(mesg, e)
178
179     return msg
180
181
182 def get_gpo_containers(samdb, gpo):
183     '''lists dn of containers for a GPO'''
184
185     search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
186     try:
187         msg = samdb.search(expression=search_expr, attrs=['gPLink'])
188     except Exception as e:
189         raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
190
191     return msg
192
193
194 def del_gpo_link(samdb, container_dn, gpo):
195     '''delete GPO link for the container'''
196     # Check if valid Container DN and get existing GPlinks
197     try:
198         msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
199                             expression="(objectClass=*)",
200                             attrs=['gPLink'])[0]
201     except Exception as e:
202         raise CommandError("Container '%s' does not exist" % container_dn, e)
203
204     found = False
205     gpo_dn = str(get_gpo_dn(samdb, gpo))
206     if 'gPLink' in msg:
207         gplist = parse_gplink(msg['gPLink'][0])
208         for g in gplist:
209             if g['dn'].lower() == gpo_dn.lower():
210                 gplist.remove(g)
211                 found = True
212                 break
213     else:
214         raise CommandError("No GPO(s) linked to this container")
215
216     if not found:
217         raise CommandError("GPO '%s' not linked to this container" % gpo)
218
219     m = ldb.Message()
220     m.dn = container_dn
221     if gplist:
222         gplink_str = encode_gplink(gplist)
223         m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
224     else:
225         m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
226     try:
227         samdb.modify(m)
228     except Exception as e:
229         raise CommandError("Error removing GPO from container", e)
230
231
232 def parse_unc(unc):
233     '''Parse UNC string into a hostname, a service, and a filepath'''
234     if unc.startswith('\\\\') and unc.startswith('//'):
235         raise ValueError("UNC doesn't start with \\\\ or //")
236     tmp = unc[2:].split('/', 2)
237     if len(tmp) == 3:
238         return tmp
239     tmp = unc[2:].split('\\', 2)
240     if len(tmp) == 3:
241         return tmp
242     raise ValueError("Invalid UNC string: %s" % unc)
243
244
245 def find_parser(name, flags=re.IGNORECASE):
246     if re.match('fdeploy1\.ini$', name, flags=flags):
247         return GPFDeploy1IniParser()
248     if re.match('audit\.csv$', name, flags=flags):
249         return GPAuditCsvParser()
250     if re.match('GptTmpl\.inf$', name, flags=flags):
251         return GptTmplInfParser()
252     if re.match('GPT\.INI$', name, flags=flags):
253         return GPTIniParser()
254     if re.match('.*\.ini$', name, flags=flags):
255         return GPIniParser()
256     if re.match('.*\.pol$', name, flags=flags):
257         return GPPolParser()
258     if re.match('.*\.aas$', name, flags=flags):
259         return GPAasParser()
260
261     return GPParser()
262
263
264 def backup_directory_remote_to_local(conn, remotedir, localdir):
265     SUFFIX = '.SAMBABACKUP'
266     if not os.path.isdir(localdir):
267         os.mkdir(localdir)
268     r_dirs = [ remotedir ]
269     l_dirs = [ localdir ]
270     while r_dirs:
271         r_dir = r_dirs.pop()
272         l_dir = l_dirs.pop()
273
274         dirlist = conn.list(r_dir, attribs=attr_flags)
275         dirlist.sort()
276         for e in dirlist:
277             r_name = r_dir + '\\' + e['name']
278             l_name = os.path.join(l_dir, e['name'])
279
280             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
281                 r_dirs.append(r_name)
282                 l_dirs.append(l_name)
283                 os.mkdir(l_name)
284             else:
285                 data = conn.loadfile(r_name)
286                 with file(l_name + SUFFIX, 'w') as f:
287                     f.write(data)
288
289                 parser = find_parser(e['name'])
290                 parser.parse(data)
291                 parser.write_xml(l_name + '.xml')
292
293
294 attr_flags = smb.FILE_ATTRIBUTE_SYSTEM | \
295              smb.FILE_ATTRIBUTE_DIRECTORY | \
296              smb.FILE_ATTRIBUTE_ARCHIVE | \
297              smb.FILE_ATTRIBUTE_HIDDEN
298
299 def copy_directory_remote_to_local(conn, remotedir, localdir):
300     if not os.path.isdir(localdir):
301         os.mkdir(localdir)
302     r_dirs = [ remotedir ]
303     l_dirs = [ localdir ]
304     while r_dirs:
305         r_dir = r_dirs.pop()
306         l_dir = l_dirs.pop()
307
308         dirlist = conn.list(r_dir, attribs=attr_flags)
309         dirlist.sort()
310         for e in dirlist:
311             r_name = r_dir + '\\' + e['name']
312             l_name = os.path.join(l_dir, e['name'])
313
314             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
315                 r_dirs.append(r_name)
316                 l_dirs.append(l_name)
317                 os.mkdir(l_name)
318             else:
319                 data = conn.loadfile(r_name)
320                 open(l_name, 'w').write(data)
321
322
323 def copy_directory_local_to_remote(conn, localdir, remotedir,
324                                    ignore_existing=False):
325     if not conn.chkpath(remotedir):
326         conn.mkdir(remotedir)
327     l_dirs = [ localdir ]
328     r_dirs = [ remotedir ]
329     while l_dirs:
330         l_dir = l_dirs.pop()
331         r_dir = r_dirs.pop()
332
333         dirlist = os.listdir(l_dir)
334         dirlist.sort()
335         for e in dirlist:
336             l_name = os.path.join(l_dir, e)
337             r_name = r_dir + '\\' + e
338
339             if os.path.isdir(l_name):
340                 l_dirs.append(l_name)
341                 r_dirs.append(r_name)
342                 try:
343                     conn.mkdir(r_name)
344                 except NTSTATUSError:
345                     if not ignore_existing:
346                         raise
347             else:
348                 data = open(l_name, 'r').read()
349                 conn.savefile(r_name, data)
350
351
352 def create_directory_hier(conn, remotedir):
353     elems = remotedir.replace('/', '\\').split('\\')
354     path = ""
355     for e in elems:
356         path = path + '\\' + e
357         if not conn.chkpath(path):
358             conn.mkdir(path)
359
360
361 class cmd_listall(Command):
362     """List all GPOs."""
363
364     synopsis = "%prog [options]"
365
366     takes_optiongroups = {
367         "sambaopts": options.SambaOptions,
368         "versionopts": options.VersionOptions,
369         "credopts": options.CredentialsOptions,
370     }
371
372     takes_options = [
373         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
374                metavar="URL", dest="H")
375         ]
376
377     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
378
379         self.lp = sambaopts.get_loadparm()
380         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
381
382         self.url = dc_url(self.lp, self.creds, H)
383
384         samdb_connect(self)
385
386         msg = get_gpo_info(self.samdb, None)
387
388         for m in msg:
389             self.outf.write("GPO          : %s\n" % m['name'][0])
390             self.outf.write("display name : %s\n" % m['displayName'][0])
391             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
392             self.outf.write("dn           : %s\n" % m.dn)
393             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
394             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
395             self.outf.write("\n")
396
397
398 class cmd_list(Command):
399     """List GPOs for an account."""
400
401     synopsis = "%prog <username> [options]"
402
403     takes_args = ['username']
404     takes_optiongroups = {
405         "sambaopts": options.SambaOptions,
406         "versionopts": options.VersionOptions,
407         "credopts": options.CredentialsOptions,
408     }
409
410     takes_options = [
411         Option("-H", "--URL", help="LDB URL for database or target server",
412             type=str, metavar="URL", dest="H")
413         ]
414
415     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
416
417         self.lp = sambaopts.get_loadparm()
418         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
419
420         self.url = dc_url(self.lp, self.creds, H)
421
422         samdb_connect(self)
423
424         try:
425             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
426                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
427             user_dn = msg[0].dn
428         except Exception:
429             raise CommandError("Failed to find account %s" % username)
430
431         # check if its a computer account
432         try:
433             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
434             is_computer = 'computer' in msg['objectClass']
435         except Exception:
436             raise CommandError("Failed to find objectClass for user %s" % username)
437
438         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
439                                AUTH_SESSION_INFO_AUTHENTICATED )
440
441         # When connecting to a remote server, don't look up the local privilege DB
442         if self.url is not None and self.url.startswith('ldap'):
443             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
444
445         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
446                                           session_info_flags=session_info_flags)
447
448         token = session.security_token
449
450         gpos = []
451
452         inherit = True
453         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
454         while True:
455             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
456             if 'gPLink' in msg:
457                 glist = parse_gplink(msg['gPLink'][0])
458                 for g in glist:
459                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
460                         continue
461                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
462                         continue
463
464                     try:
465                         sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
466                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
467                                                  attrs=['name', 'displayName', 'flags',
468                                                         'nTSecurityDescriptor'],
469                                                  controls=['sd_flags:1:%d' % sd_flags])
470                         secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
471                         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
472                     except Exception:
473                         self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
474                             g['dn'])
475                         continue
476
477                     try:
478                         samba.security.access_check(secdesc, token,
479                                                     security.SEC_STD_READ_CONTROL |
480                                                     security.SEC_ADS_LIST |
481                                                     security.SEC_ADS_READ_PROP)
482                     except RuntimeError:
483                         self.outf.write("Failed access check on %s\n" % msg.dn)
484                         continue
485
486                     # check the flags on the GPO
487                     flags = int(attr_default(gmsg[0], 'flags', 0))
488                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
489                         continue
490                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
491                         continue
492                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
493
494             # check if this blocks inheritance
495             gpoptions = int(attr_default(msg, 'gPOptions', 0))
496             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
497                 inherit = False
498
499             if dn == self.samdb.get_default_basedn():
500                 break
501             dn = dn.parent()
502
503         if is_computer:
504             msg_str = 'computer'
505         else:
506             msg_str = 'user'
507
508         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
509         for g in gpos:
510             self.outf.write("    %s %s\n" % (g[0], g[1]))
511
512
513 class cmd_show(Command):
514     """Show information for a GPO."""
515
516     synopsis = "%prog <gpo> [options]"
517
518     takes_optiongroups = {
519         "sambaopts": options.SambaOptions,
520         "versionopts": options.VersionOptions,
521         "credopts": options.CredentialsOptions,
522     }
523
524     takes_args = ['gpo']
525
526     takes_options = [
527         Option("-H", help="LDB URL for database or target server", type=str)
528         ]
529
530     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
531
532         self.lp = sambaopts.get_loadparm()
533         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
534
535         self.url = dc_url(self.lp, self.creds, H)
536
537         samdb_connect(self)
538
539         try:
540             msg = get_gpo_info(self.samdb, gpo)[0]
541         except Exception:
542             raise CommandError("GPO '%s' does not exist" % gpo)
543
544         try:
545             secdesc_ndr = msg['nTSecurityDescriptor'][0]
546             secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
547             secdesc_sddl = secdesc.as_sddl()
548         except Exception:
549             secdesc_sddl = "<hidden>"
550
551         self.outf.write("GPO          : %s\n" % msg['name'][0])
552         self.outf.write("display name : %s\n" % msg['displayName'][0])
553         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
554         self.outf.write("dn           : %s\n" % msg.dn)
555         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
556         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
557         self.outf.write("ACL          : %s\n" % secdesc_sddl)
558         self.outf.write("\n")
559
560
561 class cmd_getlink(Command):
562     """List GPO Links for a container."""
563
564     synopsis = "%prog <container_dn> [options]"
565
566     takes_optiongroups = {
567         "sambaopts": options.SambaOptions,
568         "versionopts": options.VersionOptions,
569         "credopts": options.CredentialsOptions,
570     }
571
572     takes_args = ['container_dn']
573
574     takes_options = [
575         Option("-H", help="LDB URL for database or target server", type=str)
576         ]
577
578     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
579                 versionopts=None):
580
581         self.lp = sambaopts.get_loadparm()
582         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
583
584         self.url = dc_url(self.lp, self.creds, H)
585
586         samdb_connect(self)
587
588         try:
589             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
590                                     expression="(objectClass=*)",
591                                     attrs=['gPLink'])[0]
592         except Exception:
593             raise CommandError("Container '%s' does not exist" % container_dn)
594
595         if msg['gPLink']:
596             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
597             gplist = parse_gplink(msg['gPLink'][0])
598             for g in gplist:
599                 msg = get_gpo_info(self.samdb, dn=g['dn'])
600                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
601                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
602                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
603                 self.outf.write("\n")
604         else:
605             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
606
607
608 class cmd_setlink(Command):
609     """Add or update a GPO link to a container."""
610
611     synopsis = "%prog <container_dn> <gpo> [options]"
612
613     takes_optiongroups = {
614         "sambaopts": options.SambaOptions,
615         "versionopts": options.VersionOptions,
616         "credopts": options.CredentialsOptions,
617     }
618
619     takes_args = ['container_dn', 'gpo']
620
621     takes_options = [
622         Option("-H", help="LDB URL for database or target server", type=str),
623         Option("--disable", dest="disabled", default=False, action='store_true',
624             help="Disable policy"),
625         Option("--enforce", dest="enforced", default=False, action='store_true',
626             help="Enforce policy")
627         ]
628
629     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
630                 sambaopts=None, credopts=None, versionopts=None):
631
632         self.lp = sambaopts.get_loadparm()
633         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
634
635         self.url = dc_url(self.lp, self.creds, H)
636
637         samdb_connect(self)
638
639         gplink_options = 0
640         if disabled:
641             gplink_options |= dsdb.GPLINK_OPT_DISABLE
642         if enforced:
643             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
644
645         # Check if valid GPO DN
646         try:
647             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
648         except Exception:
649             raise CommandError("GPO '%s' does not exist" % gpo)
650         gpo_dn = str(get_gpo_dn(self.samdb, gpo))
651
652         # Check if valid Container DN
653         try:
654             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
655                                     expression="(objectClass=*)",
656                                     attrs=['gPLink'])[0]
657         except Exception:
658             raise CommandError("Container '%s' does not exist" % container_dn)
659
660         # Update existing GPlinks or Add new one
661         existing_gplink = False
662         if 'gPLink' in msg:
663             gplist = parse_gplink(msg['gPLink'][0])
664             existing_gplink = True
665             found = False
666             for g in gplist:
667                 if g['dn'].lower() == gpo_dn.lower():
668                     g['options'] = gplink_options
669                     found = True
670                     break
671             if found:
672                 raise CommandError("GPO '%s' already linked to this container" % gpo)
673             else:
674                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
675         else:
676             gplist = []
677             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
678
679         gplink_str = encode_gplink(gplist)
680
681         m = ldb.Message()
682         m.dn = ldb.Dn(self.samdb, container_dn)
683
684         if existing_gplink:
685             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
686         else:
687             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
688
689         try:
690             self.samdb.modify(m)
691         except Exception as e:
692             raise CommandError("Error adding GPO Link", e)
693
694         self.outf.write("Added/Updated GPO link\n")
695         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
696
697
698 class cmd_dellink(Command):
699     """Delete GPO link from a container."""
700
701     synopsis = "%prog <container_dn> <gpo> [options]"
702
703     takes_optiongroups = {
704         "sambaopts": options.SambaOptions,
705         "versionopts": options.VersionOptions,
706         "credopts": options.CredentialsOptions,
707     }
708
709     takes_args = ['container', 'gpo']
710
711     takes_options = [
712         Option("-H", help="LDB URL for database or target server", type=str),
713         ]
714
715     def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
716                 versionopts=None):
717
718         self.lp = sambaopts.get_loadparm()
719         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
720
721         self.url = dc_url(self.lp, self.creds, H)
722
723         samdb_connect(self)
724
725         # Check if valid GPO
726         try:
727             get_gpo_info(self.samdb, gpo=gpo)[0]
728         except Exception:
729             raise CommandError("GPO '%s' does not exist" % gpo)
730
731         container_dn = ldb.Dn(self.samdb, container)
732         del_gpo_link(self.samdb, container_dn, gpo)
733         self.outf.write("Deleted GPO link.\n")
734         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
735
736
737 class cmd_listcontainers(Command):
738     """List all linked containers for a GPO."""
739
740     synopsis = "%prog <gpo> [options]"
741
742     takes_optiongroups = {
743         "sambaopts": options.SambaOptions,
744         "versionopts": options.VersionOptions,
745         "credopts": options.CredentialsOptions,
746     }
747
748     takes_args = ['gpo']
749
750     takes_options = [
751         Option("-H", help="LDB URL for database or target server", type=str)
752         ]
753
754     def run(self, gpo, H=None, sambaopts=None, credopts=None,
755                 versionopts=None):
756
757         self.lp = sambaopts.get_loadparm()
758         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
759
760         self.url = dc_url(self.lp, self.creds, H)
761
762         samdb_connect(self)
763
764         msg = get_gpo_containers(self.samdb, gpo)
765         if len(msg):
766             self.outf.write("Container(s) using GPO %s\n" % gpo)
767             for m in msg:
768                 self.outf.write("    DN: %s\n" % m['dn'])
769         else:
770             self.outf.write("No Containers using GPO %s\n" % gpo)
771
772
773 class cmd_getinheritance(Command):
774     """Get inheritance flag for a container."""
775
776     synopsis = "%prog <container_dn> [options]"
777
778     takes_optiongroups = {
779         "sambaopts": options.SambaOptions,
780         "versionopts": options.VersionOptions,
781         "credopts": options.CredentialsOptions,
782     }
783
784     takes_args = ['container_dn']
785
786     takes_options = [
787         Option("-H", help="LDB URL for database or target server", type=str)
788         ]
789
790     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
791                 versionopts=None):
792
793         self.lp = sambaopts.get_loadparm()
794         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
795
796         self.url = dc_url(self.lp, self.creds, H)
797
798         samdb_connect(self)
799
800         try:
801             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
802                                     expression="(objectClass=*)",
803                                     attrs=['gPOptions'])[0]
804         except Exception:
805             raise CommandError("Container '%s' does not exist" % container_dn)
806
807         inheritance = 0
808         if 'gPOptions' in msg:
809             inheritance = int(msg['gPOptions'][0])
810
811         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
812             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
813         else:
814             self.outf.write("Container has GPO_INHERIT\n")
815
816
817 class cmd_setinheritance(Command):
818     """Set inheritance flag on a container."""
819
820     synopsis = "%prog <container_dn> <block|inherit> [options]"
821
822     takes_optiongroups = {
823         "sambaopts": options.SambaOptions,
824         "versionopts": options.VersionOptions,
825         "credopts": options.CredentialsOptions,
826     }
827
828     takes_args = [ 'container_dn', 'inherit_state' ]
829
830     takes_options = [
831         Option("-H", help="LDB URL for database or target server", type=str)
832         ]
833
834     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
835                 versionopts=None):
836
837         if inherit_state.lower() == 'block':
838             inheritance = dsdb.GPO_BLOCK_INHERITANCE
839         elif inherit_state.lower() == 'inherit':
840             inheritance = dsdb.GPO_INHERIT
841         else:
842             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
843
844         self.lp = sambaopts.get_loadparm()
845         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
846
847         self.url = dc_url(self.lp, self.creds, H)
848
849         samdb_connect(self)
850         try:
851             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
852                                     expression="(objectClass=*)",
853                                     attrs=['gPOptions'])[0]
854         except Exception:
855             raise CommandError("Container '%s' does not exist" % container_dn)
856
857         m = ldb.Message()
858         m.dn = ldb.Dn(self.samdb, container_dn)
859
860         if 'gPOptions' in msg:
861             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
862         else:
863             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
864
865         try:
866             self.samdb.modify(m)
867         except Exception as e:
868             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
869
870
871 class cmd_fetch(Command):
872     """Download a GPO."""
873
874     synopsis = "%prog <gpo> [options]"
875
876     takes_optiongroups = {
877         "sambaopts": options.SambaOptions,
878         "versionopts": options.VersionOptions,
879         "credopts": options.CredentialsOptions,
880     }
881
882     takes_args = ['gpo']
883
884     takes_options = [
885         Option("-H", help="LDB URL for database or target server", type=str),
886         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
887         ]
888
889     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
890
891         self.lp = sambaopts.get_loadparm()
892         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
893
894         # We need to know writable DC to setup SMB connection
895         if H and H.startswith('ldap://'):
896             dc_hostname = H[7:]
897             self.url = H
898         else:
899             dc_hostname = netcmd_finddc(self.lp, self.creds)
900             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
901
902         samdb_connect(self)
903         try:
904             msg = get_gpo_info(self.samdb, gpo)[0]
905         except Exception:
906             raise CommandError("GPO '%s' does not exist" % gpo)
907
908         # verify UNC path
909         unc = msg['gPCFileSysPath'][0]
910         try:
911             [dom_name, service, sharepath] = parse_unc(unc)
912         except ValueError:
913             raise CommandError("Invalid GPO path (%s)" % unc)
914
915         # SMB connect to DC
916         try:
917             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
918         except Exception:
919             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
920
921         # Copy GPT
922         if tmpdir is None:
923             tmpdir = "/tmp"
924         if not os.path.isdir(tmpdir):
925             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
926
927         localdir = os.path.join(tmpdir, "policy")
928         if not os.path.isdir(localdir):
929             os.mkdir(localdir)
930
931         gpodir = os.path.join(localdir, gpo)
932         if os.path.isdir(gpodir):
933             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
934
935         try:
936             os.mkdir(gpodir)
937             copy_directory_remote_to_local(conn, sharepath, gpodir)
938         except Exception as e:
939             # FIXME: Catch more specific exception
940             raise CommandError("Error copying GPO from DC", e)
941         self.outf.write('GPO copied to %s\n' % gpodir)
942
943
944 class cmd_backup(Command):
945     """Backup a GPO."""
946
947     synopsis = "%prog <gpo> [options]"
948
949     takes_optiongroups = {
950         "sambaopts": options.SambaOptions,
951         "versionopts": options.VersionOptions,
952         "credopts": options.CredentialsOptions,
953     }
954
955     takes_args = ['gpo']
956
957     takes_options = [
958         Option("-H", help="LDB URL for database or target server", type=str),
959         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
960         ]
961
962     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
963
964         self.lp = sambaopts.get_loadparm()
965         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
966
967         # We need to know writable DC to setup SMB connection
968         if H and H.startswith('ldap://'):
969             dc_hostname = H[7:]
970             self.url = H
971         else:
972             dc_hostname = netcmd_finddc(self.lp, self.creds)
973             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
974
975         samdb_connect(self)
976         try:
977             msg = get_gpo_info(self.samdb, gpo)[0]
978         except Exception:
979             raise CommandError("GPO '%s' does not exist" % gpo)
980
981         # verify UNC path
982         unc = msg['gPCFileSysPath'][0]
983         try:
984             [dom_name, service, sharepath] = parse_unc(unc)
985         except ValueError:
986             raise CommandError("Invalid GPO path (%s)" % unc)
987
988         # SMB connect to DC
989         try:
990             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
991         except Exception:
992             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
993
994         # Copy GPT
995         if tmpdir is None:
996             tmpdir = "/tmp"
997         if not os.path.isdir(tmpdir):
998             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
999
1000         localdir = os.path.join(tmpdir, "policy")
1001         if not os.path.isdir(localdir):
1002             os.mkdir(localdir)
1003
1004         gpodir = os.path.join(localdir, gpo)
1005         if os.path.isdir(gpodir):
1006             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1007
1008         try:
1009             os.mkdir(gpodir)
1010             backup_directory_remote_to_local(conn, sharepath, gpodir)
1011         except Exception as e:
1012             # FIXME: Catch more specific exception
1013             raise CommandError("Error copying GPO from DC", e)
1014         self.outf.write('GPO copied to %s\n' % gpodir)
1015
1016
1017 class cmd_create(Command):
1018     """Create an empty GPO."""
1019
1020     synopsis = "%prog <displayname> [options]"
1021
1022     takes_optiongroups = {
1023         "sambaopts": options.SambaOptions,
1024         "versionopts": options.VersionOptions,
1025         "credopts": options.CredentialsOptions,
1026     }
1027
1028     takes_args = ['displayname']
1029
1030     takes_options = [
1031         Option("-H", help="LDB URL for database or target server", type=str),
1032         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1033         ]
1034
1035     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
1036             versionopts=None):
1037
1038         self.lp = sambaopts.get_loadparm()
1039         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1040
1041         net = Net(creds=self.creds, lp=self.lp)
1042
1043         # We need to know writable DC to setup SMB connection
1044         if H and H.startswith('ldap://'):
1045             dc_hostname = H[7:]
1046             self.url = H
1047             flags = (nbt.NBT_SERVER_LDAP |
1048                      nbt.NBT_SERVER_DS |
1049                      nbt.NBT_SERVER_WRITABLE)
1050             cldap_ret = net.finddc(address=dc_hostname, flags=flags)
1051         else:
1052             flags = (nbt.NBT_SERVER_LDAP |
1053                      nbt.NBT_SERVER_DS |
1054                      nbt.NBT_SERVER_WRITABLE)
1055             cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
1056             dc_hostname = cldap_ret.pdc_dns_name
1057             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1058
1059         samdb_connect(self)
1060
1061         msg = get_gpo_info(self.samdb, displayname=displayname)
1062         if msg.count > 0:
1063             raise CommandError("A GPO already existing with name '%s'" % displayname)
1064
1065         # Create new GUID
1066         guid  = str(uuid.uuid4())
1067         gpo = "{%s}" % guid.upper()
1068
1069         self.gpo_name = gpo
1070
1071         realm = cldap_ret.dns_domain
1072         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1073
1074         # Create GPT
1075         if tmpdir is None:
1076             tmpdir = "/tmp"
1077         if not os.path.isdir(tmpdir):
1078             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
1079         self.tmpdir = tmpdir
1080
1081         localdir = os.path.join(tmpdir, "policy")
1082         if not os.path.isdir(localdir):
1083             os.mkdir(localdir)
1084
1085         gpodir = os.path.join(localdir, gpo)
1086         self.gpodir = gpodir
1087         if os.path.isdir(gpodir):
1088             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1089
1090         try:
1091             os.mkdir(gpodir)
1092             os.mkdir(os.path.join(gpodir, "Machine"))
1093             os.mkdir(os.path.join(gpodir, "User"))
1094             gpt_contents = "[General]\r\nVersion=0\r\n"
1095             open(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
1096         except Exception as e:
1097             raise CommandError("Error Creating GPO files", e)
1098
1099         # Connect to DC over SMB
1100         [dom_name, service, sharepath] = parse_unc(unc_path)
1101         self.sharepath = sharepath
1102         try:
1103             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1104         except Exception as e:
1105             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1106
1107         self.conn = conn
1108
1109         self.samdb.transaction_start()
1110         try:
1111             # Add cn=<guid>
1112             gpo_dn = get_gpo_dn(self.samdb, gpo)
1113
1114             m = ldb.Message()
1115             m.dn = gpo_dn
1116             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
1117             self.samdb.add(m)
1118
1119             # Add cn=User,cn=<guid>
1120             m = ldb.Message()
1121             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
1122             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1123             self.samdb.add(m)
1124
1125             # Add cn=Machine,cn=<guid>
1126             m = ldb.Message()
1127             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
1128             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1129             self.samdb.add(m)
1130
1131             # Get new security descriptor
1132             ds_sd_flags = ( security.SECINFO_OWNER |
1133                             security.SECINFO_GROUP |
1134                             security.SECINFO_DACL )
1135             msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
1136             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
1137             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1138
1139             # Create a file system security descriptor
1140             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1141             sddl = dsacl2fsacl(ds_sd, domain_sid)
1142             fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
1143
1144             # Copy GPO directory
1145             create_directory_hier(conn, sharepath)
1146
1147             # Set ACL
1148             sio = ( security.SECINFO_OWNER |
1149                     security.SECINFO_GROUP |
1150                     security.SECINFO_DACL |
1151                     security.SECINFO_PROTECTED_DACL )
1152             conn.set_acl(sharepath, fs_sd, sio)
1153
1154             # Copy GPO files over SMB
1155             copy_directory_local_to_remote(conn, gpodir, sharepath)
1156
1157             m = ldb.Message()
1158             m.dn = gpo_dn
1159             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1160             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1161             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1162             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1163             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1164             controls=["permissive_modify:0"]
1165             self.samdb.modify(m, controls=controls)
1166         except Exception:
1167             self.samdb.transaction_cancel()
1168             raise
1169         else:
1170             self.samdb.transaction_commit()
1171
1172         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1173
1174
1175 class cmd_restore(cmd_create):
1176     """Restore a GPO to a new container."""
1177
1178     synopsis = "%prog <displayname> <backup location> [options]"
1179
1180     takes_optiongroups = {
1181         "sambaopts": options.SambaOptions,
1182         "versionopts": options.VersionOptions,
1183         "credopts": options.CredentialsOptions,
1184     }
1185
1186     takes_args = ['displayname', 'backup']
1187
1188     takes_options = [
1189         Option("-H", help="LDB URL for database or target server", type=str),
1190         Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1191         Option("--entities", help="File defining XML entities to insert into DOCTYPE header", type=str)
1192         ]
1193
1194     def restore_from_backup_to_local_dir(self, sourcedir, targetdir, dtd_header=''):
1195         SUFFIX = '.SAMBABACKUP'
1196
1197         if not os.path.exists(targetdir):
1198             os.mkdir(targetdir)
1199
1200         l_dirs = [ sourcedir ]
1201         r_dirs = [ targetdir ]
1202         while l_dirs:
1203             l_dir = l_dirs.pop()
1204             r_dir = r_dirs.pop()
1205
1206             dirlist = os.listdir(l_dir)
1207             for e in dirlist:
1208                 l_name = os.path.join(l_dir, e)
1209                 r_name = os.path.join(r_dir, e)
1210
1211                 if os.path.isdir(l_name):
1212                     l_dirs.append(l_name)
1213                     r_dirs.append(r_name)
1214                     if not os.path.exists(r_name):
1215                         os.mkdir(r_name)
1216                 else:
1217                     if l_name.endswith('.xml'):
1218                         # Restore the xml file if possible
1219
1220                         # Get the filename to find the parser
1221                         to_parse = os.path.basename(l_name)[:-4]
1222
1223                         parser = find_parser(to_parse)
1224                         try:
1225                             with open(l_name, 'r') as ltemp:
1226                                 data = ltemp.read()
1227                                 # Load the XML file with the DTD (entity) header
1228                                 parser.load_xml(ET.fromstring(dtd_header + data))
1229
1230                                 # Write out the substituted files in the output
1231                                 # location, ready to copy over.
1232                                 parser.write_binary(r_name[:-4])
1233
1234                         except GPNoParserException:
1235                             # In the failure case, we fallback
1236                             original_file = l_name[:-4] + SUFFIX
1237                             shutil.copy2(original_file, r_name[:-4])
1238
1239                             self.outf.write('WARNING: No such parser for %s\n' % to_parse)
1240                             self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1241                         except:
1242                             import traceback
1243                             traceback.print_exc()
1244
1245                             # In the failure case, we fallback
1246                             original_file = l_name[:-4] + SUFFIX
1247                             shutil.copy2(original_file, r_name[:-4])
1248
1249                             self.outf.write('WARNING: Error during parsing for %s\n' % l_name)
1250                             self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1251
1252     def run(self, displayname, backup, H=None, tmpdir=None, entities=None, sambaopts=None, credopts=None,
1253             versionopts=None):
1254
1255         dtd_header = ''
1256
1257         if not os.path.exists(backup):
1258             raise CommandError("Backup directory does not exist %s" % backup)
1259
1260         if entities is not None:
1261             # DOCTYPE name is meant to match root element, but ElementTree does
1262             # not seem to care, so this seems to be enough.
1263
1264             dtd_header = '<!DOCTYPE foobar [\n'
1265
1266             if not os.path.exists(entities):
1267                 raise CommandError("Entities file does not exist %s" %
1268                                    entities)
1269             with open(entities, 'r') as entities_file:
1270                 entities_content = entities_file.read()
1271
1272                 # Do a basic regex test of the entities file format
1273                 if re.match('(\s*<!ENTITY\s*[a-zA-Z0-9_]+\s*.*?>)+\s*\Z',
1274                             entities_content, flags=re.MULTILINE) is None:
1275                     raise CommandError("Entities file does not appear to "
1276                                        "conform to format\n"
1277                                        'e.g. <!ENTITY entity "value">')
1278                 dtd_header += entities_content.strip()
1279
1280             dtd_header += '\n]>\n'
1281
1282         super(cmd_restore, self).run(displayname, H, tmpdir, sambaopts,
1283                                     credopts, versionopts)
1284
1285         try:
1286             # Iterate over backup files and restore with DTD
1287             self.restore_from_backup_to_local_dir(backup, self.gpodir,
1288                                                   dtd_header)
1289
1290             # Copy GPO files over SMB
1291             copy_directory_local_to_remote(self.conn, self.gpodir,
1292                                            self.sharepath,
1293                                            ignore_existing=True)
1294
1295         except Exception as e:
1296             import traceback
1297             traceback.print_exc()
1298             self.outf.write(str(e) + '\n')
1299
1300             self.outf.write("Failed to restore GPO -- deleting...\n")
1301             cmd = cmd_del()
1302             cmd.run(self.gpo_name, H, sambaopts, credopts, versionopts)
1303
1304             raise CommandError("Failed to restore: %s" % e)
1305
1306
1307 class cmd_del(Command):
1308     """Delete a GPO."""
1309
1310     synopsis = "%prog <gpo> [options]"
1311
1312     takes_optiongroups = {
1313         "sambaopts": options.SambaOptions,
1314         "versionopts": options.VersionOptions,
1315         "credopts": options.CredentialsOptions,
1316     }
1317
1318     takes_args = ['gpo']
1319
1320     takes_options = [
1321         Option("-H", help="LDB URL for database or target server", type=str),
1322         ]
1323
1324     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1325                 versionopts=None):
1326
1327         self.lp = sambaopts.get_loadparm()
1328         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1329
1330         # We need to know writable DC to setup SMB connection
1331         if H and H.startswith('ldap://'):
1332             dc_hostname = H[7:]
1333             self.url = H
1334         else:
1335             dc_hostname = netcmd_finddc(self.lp, self.creds)
1336             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1337
1338         samdb_connect(self)
1339
1340         # Check if valid GPO
1341         try:
1342             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1343             unc_path = msg['gPCFileSysPath'][0]
1344         except Exception:
1345             raise CommandError("GPO '%s' does not exist" % gpo)
1346
1347         # Connect to DC over SMB
1348         [dom_name, service, sharepath] = parse_unc(unc_path)
1349         try:
1350             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1351         except Exception as e:
1352             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1353
1354         self.samdb.transaction_start()
1355         try:
1356             # Check for existing links
1357             msg = get_gpo_containers(self.samdb, gpo)
1358
1359             if len(msg):
1360                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1361                 for m in msg:
1362                     del_gpo_link(self.samdb, m['dn'], gpo)
1363                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1364
1365             # Remove LDAP entries
1366             gpo_dn = get_gpo_dn(self.samdb, gpo)
1367             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1368             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1369             self.samdb.delete(gpo_dn)
1370
1371             # Remove GPO files
1372             conn.deltree(sharepath)
1373
1374         except Exception:
1375             self.samdb.transaction_cancel()
1376             raise
1377         else:
1378             self.samdb.transaction_commit()
1379
1380         self.outf.write("GPO %s deleted.\n" % gpo)
1381
1382
1383 class cmd_aclcheck(Command):
1384     """Check all GPOs have matching LDAP and DS ACLs."""
1385
1386     synopsis = "%prog [options]"
1387
1388     takes_optiongroups = {
1389         "sambaopts": options.SambaOptions,
1390         "versionopts": options.VersionOptions,
1391         "credopts": options.CredentialsOptions,
1392     }
1393
1394     takes_options = [
1395         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1396                metavar="URL", dest="H")
1397         ]
1398
1399     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1400
1401         self.lp = sambaopts.get_loadparm()
1402         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1403
1404         self.url = dc_url(self.lp, self.creds, H)
1405
1406         # We need to know writable DC to setup SMB connection
1407         if H and H.startswith('ldap://'):
1408             dc_hostname = H[7:]
1409             self.url = H
1410         else:
1411             dc_hostname = netcmd_finddc(self.lp, self.creds)
1412             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1413
1414         samdb_connect(self)
1415
1416         msg = get_gpo_info(self.samdb, None)
1417
1418         for m in msg:
1419             # verify UNC path
1420             unc = m['gPCFileSysPath'][0]
1421             try:
1422                 [dom_name, service, sharepath] = parse_unc(unc)
1423             except ValueError:
1424                 raise CommandError("Invalid GPO path (%s)" % unc)
1425
1426             # SMB connect to DC
1427             try:
1428                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1429             except Exception:
1430                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1431
1432             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1433
1434             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1435             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1436
1437             # Create a file system security descriptor
1438             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1439             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1440
1441             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1442                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1443
1444
1445 class cmd_gpo(SuperCommand):
1446     """Group Policy Object (GPO) management."""
1447
1448     subcommands = {}
1449     subcommands["listall"] = cmd_listall()
1450     subcommands["list"] = cmd_list()
1451     subcommands["show"] = cmd_show()
1452     subcommands["getlink"] = cmd_getlink()
1453     subcommands["setlink"] = cmd_setlink()
1454     subcommands["dellink"] = cmd_dellink()
1455     subcommands["listcontainers"] = cmd_listcontainers()
1456     subcommands["getinheritance"] = cmd_getinheritance()
1457     subcommands["setinheritance"] = cmd_setinheritance()
1458     subcommands["fetch"] = cmd_fetch()
1459     subcommands["create"] = cmd_create()
1460     subcommands["del"] = cmd_del()
1461     subcommands["aclcheck"] = cmd_aclcheck()
1462     subcommands["backup"] = cmd_backup()
1463     subcommands["restore"] = cmd_restore()