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