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