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|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,
927                            service,
928                            lp=self.lp,
929                            creds=self.creds,
930                            sign=True)
931         except Exception:
932             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
933
934         # Copy GPT
935         if tmpdir is None:
936             tmpdir = "/tmp"
937         if not os.path.isdir(tmpdir):
938             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
939
940         localdir = os.path.join(tmpdir, "policy")
941         if not os.path.isdir(localdir):
942             os.mkdir(localdir)
943
944         gpodir = os.path.join(localdir, gpo)
945         if os.path.isdir(gpodir):
946             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
947
948         try:
949             os.mkdir(gpodir)
950             copy_directory_remote_to_local(conn, sharepath, gpodir)
951         except Exception as e:
952             # FIXME: Catch more specific exception
953             raise CommandError("Error copying GPO from DC", e)
954         self.outf.write('GPO copied to %s\n' % gpodir)
955
956
957 class cmd_backup(Command):
958     """Backup a GPO."""
959
960     synopsis = "%prog <gpo> [options]"
961
962     takes_optiongroups = {
963         "sambaopts": options.SambaOptions,
964         "versionopts": options.VersionOptions,
965         "credopts": options.CredentialsOptions,
966     }
967
968     takes_args = ['gpo']
969
970     takes_options = [
971         Option("-H", help="LDB URL for database or target server", type=str),
972         Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
973         Option("--generalize", help="Generalize XML entities to restore",
974                default=False, action='store_true'),
975         Option("--entities", help="File to export defining XML entities for the restore",
976                dest='ent_file', type=str)
977         ]
978
979     def run(self, gpo, H=None, tmpdir=None, generalize=False, sambaopts=None,
980             credopts=None, versionopts=None, ent_file=None):
981
982         self.lp = sambaopts.get_loadparm()
983         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
984
985         # We need to know writable DC to setup SMB connection
986         if H and H.startswith('ldap://'):
987             dc_hostname = H[7:]
988             self.url = H
989         else:
990             dc_hostname = netcmd_finddc(self.lp, self.creds)
991             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
992
993         samdb_connect(self)
994         try:
995             msg = get_gpo_info(self.samdb, gpo)[0]
996         except Exception:
997             raise CommandError("GPO '%s' does not exist" % gpo)
998
999         # verify UNC path
1000         unc = msg['gPCFileSysPath'][0]
1001         try:
1002             [dom_name, service, sharepath] = parse_unc(unc)
1003         except ValueError:
1004             raise CommandError("Invalid GPO path (%s)" % unc)
1005
1006         # SMB connect to DC
1007         try:
1008             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1009         except Exception:
1010             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1011
1012         # Copy GPT
1013         if tmpdir is None:
1014             tmpdir = "/tmp"
1015         if not os.path.isdir(tmpdir):
1016             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
1017
1018         localdir = os.path.join(tmpdir, "policy")
1019         if not os.path.isdir(localdir):
1020             os.mkdir(localdir)
1021
1022         gpodir = os.path.join(localdir, gpo)
1023         if os.path.isdir(gpodir):
1024             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1025
1026         try:
1027             os.mkdir(gpodir)
1028             backup_directory_remote_to_local(conn, sharepath, gpodir)
1029         except Exception as e:
1030             # FIXME: Catch more specific exception
1031             raise CommandError("Error copying GPO from DC", e)
1032
1033         self.outf.write('GPO copied to %s\n' % gpodir)
1034
1035         if generalize:
1036             self.outf.write('\nAttempting to generalize XML entities:\n')
1037             entities = cmd_backup.generalize_xml_entities(self.outf, gpodir,
1038                                                           gpodir)
1039             import operator
1040             ents = ''
1041             for ent in sorted(entities.items(), key=operator.itemgetter(1)):
1042                 ents += '<!ENTITY {} "{}">\n'.format(ent[1].strip('&;'), ent[0])
1043
1044             if ent_file:
1045                 with open(ent_file, 'w') as f:
1046                     f.write(ents)
1047                 self.outf.write('Entities successfully written to %s\n' %
1048                                 ent_file)
1049             else:
1050                 self.outf.write('\nEntities:\n')
1051                 self.outf.write(ents)
1052
1053     @staticmethod
1054     def generalize_xml_entities(outf, sourcedir, targetdir):
1055         entities = {}
1056
1057         if not os.path.exists(targetdir):
1058             os.mkdir(targetdir)
1059
1060         l_dirs = [ sourcedir ]
1061         r_dirs = [ targetdir ]
1062         while l_dirs:
1063             l_dir = l_dirs.pop()
1064             r_dir = r_dirs.pop()
1065
1066             dirlist = os.listdir(l_dir)
1067             dirlist.sort()
1068             for e in dirlist:
1069                 l_name = os.path.join(l_dir, e)
1070                 r_name = os.path.join(r_dir, e)
1071
1072                 if os.path.isdir(l_name):
1073                     l_dirs.append(l_name)
1074                     r_dirs.append(r_name)
1075                     if not os.path.exists(r_name):
1076                         os.mkdir(r_name)
1077                 else:
1078                     if l_name.endswith('.xml'):
1079                         # Restore the xml file if possible
1080
1081                         # Get the filename to find the parser
1082                         to_parse = os.path.basename(l_name)[:-4]
1083
1084                         parser = find_parser(to_parse)
1085                         try:
1086                             with open(l_name, 'r') as ltemp:
1087                                 data = ltemp.read()
1088
1089                             concrete_xml = ET.fromstring(data)
1090                             found_entities = parser.generalize_xml(concrete_xml, r_name, entities)
1091                         except GPGeneralizeException:
1092                             outf.write('SKIPPING: Generalizing failed for %s\n' % to_parse)
1093
1094                     else:
1095                         # No need to generalize non-xml files.
1096                         #
1097                         # TODO This could be improved with xml files stored in
1098                         # the renamed backup file (with custom extension) by
1099                         # inlining them into the exported backups.
1100                         if not os.path.samefile(l_name, r_name):
1101                             shutil.copy2(l_name, r_name)
1102
1103         return entities
1104
1105
1106 class cmd_create(Command):
1107     """Create an empty GPO."""
1108
1109     synopsis = "%prog <displayname> [options]"
1110
1111     takes_optiongroups = {
1112         "sambaopts": options.SambaOptions,
1113         "versionopts": options.VersionOptions,
1114         "credopts": options.CredentialsOptions,
1115     }
1116
1117     takes_args = ['displayname']
1118
1119     takes_options = [
1120         Option("-H", help="LDB URL for database or target server", type=str),
1121         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1122     ]
1123
1124     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
1125             versionopts=None):
1126
1127         self.lp = sambaopts.get_loadparm()
1128         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1129
1130         net = Net(creds=self.creds, lp=self.lp)
1131
1132         # We need to know writable DC to setup SMB connection
1133         if H and H.startswith('ldap://'):
1134             dc_hostname = H[7:]
1135             self.url = H
1136             flags = (nbt.NBT_SERVER_LDAP |
1137                      nbt.NBT_SERVER_DS |
1138                      nbt.NBT_SERVER_WRITABLE)
1139             cldap_ret = net.finddc(address=dc_hostname, flags=flags)
1140         else:
1141             flags = (nbt.NBT_SERVER_LDAP |
1142                      nbt.NBT_SERVER_DS |
1143                      nbt.NBT_SERVER_WRITABLE)
1144             cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
1145             dc_hostname = cldap_ret.pdc_dns_name
1146             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1147
1148         samdb_connect(self)
1149
1150         msg = get_gpo_info(self.samdb, displayname=displayname)
1151         if msg.count > 0:
1152             raise CommandError("A GPO already existing with name '%s'" % displayname)
1153
1154         # Create new GUID
1155         guid  = str(uuid.uuid4())
1156         gpo = "{%s}" % guid.upper()
1157
1158         self.gpo_name = gpo
1159
1160         realm = cldap_ret.dns_domain
1161         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1162
1163         # Create GPT
1164         if tmpdir is None:
1165             tmpdir = "/tmp"
1166         if not os.path.isdir(tmpdir):
1167             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
1168         self.tmpdir = tmpdir
1169
1170         localdir = os.path.join(tmpdir, "policy")
1171         if not os.path.isdir(localdir):
1172             os.mkdir(localdir)
1173
1174         gpodir = os.path.join(localdir, gpo)
1175         self.gpodir = gpodir
1176         if os.path.isdir(gpodir):
1177             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1178
1179         try:
1180             os.mkdir(gpodir)
1181             os.mkdir(os.path.join(gpodir, "Machine"))
1182             os.mkdir(os.path.join(gpodir, "User"))
1183             gpt_contents = "[General]\r\nVersion=0\r\n"
1184             open(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
1185         except Exception as e:
1186             raise CommandError("Error Creating GPO files", e)
1187
1188         # Connect to DC over SMB
1189         [dom_name, service, sharepath] = parse_unc(unc_path)
1190         self.sharepath = sharepath
1191         try:
1192             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1193         except Exception as e:
1194             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1195
1196         self.conn = conn
1197
1198         self.samdb.transaction_start()
1199         try:
1200             # Add cn=<guid>
1201             gpo_dn = get_gpo_dn(self.samdb, gpo)
1202
1203             m = ldb.Message()
1204             m.dn = gpo_dn
1205             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
1206             self.samdb.add(m)
1207
1208             # Add cn=User,cn=<guid>
1209             m = ldb.Message()
1210             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
1211             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1212             self.samdb.add(m)
1213
1214             # Add cn=Machine,cn=<guid>
1215             m = ldb.Message()
1216             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
1217             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1218             self.samdb.add(m)
1219
1220             # Get new security descriptor
1221             ds_sd_flags = ( security.SECINFO_OWNER |
1222                             security.SECINFO_GROUP |
1223                             security.SECINFO_DACL )
1224             msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
1225             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
1226             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1227
1228             # Create a file system security descriptor
1229             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1230             sddl = dsacl2fsacl(ds_sd, domain_sid)
1231             fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
1232
1233             # Copy GPO directory
1234             create_directory_hier(conn, sharepath)
1235
1236             # Set ACL
1237             sio = ( security.SECINFO_OWNER |
1238                     security.SECINFO_GROUP |
1239                     security.SECINFO_DACL |
1240                     security.SECINFO_PROTECTED_DACL )
1241             conn.set_acl(sharepath, fs_sd, sio)
1242
1243             # Copy GPO files over SMB
1244             copy_directory_local_to_remote(conn, gpodir, sharepath)
1245
1246             m = ldb.Message()
1247             m.dn = gpo_dn
1248             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1249             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1250             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1251             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1252             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1253             controls=["permissive_modify:0"]
1254             self.samdb.modify(m, controls=controls)
1255         except Exception:
1256             self.samdb.transaction_cancel()
1257             raise
1258         else:
1259             self.samdb.transaction_commit()
1260
1261         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1262
1263
1264 class cmd_restore(cmd_create):
1265     """Restore a GPO to a new container."""
1266
1267     synopsis = "%prog <displayname> <backup location> [options]"
1268
1269     takes_optiongroups = {
1270         "sambaopts": options.SambaOptions,
1271         "versionopts": options.VersionOptions,
1272         "credopts": options.CredentialsOptions,
1273     }
1274
1275     takes_args = ['displayname', 'backup']
1276
1277     takes_options = [
1278         Option("-H", help="LDB URL for database or target server", type=str),
1279         Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1280         Option("--entities", help="File defining XML entities to insert into DOCTYPE header", type=str)
1281         ]
1282
1283     def restore_from_backup_to_local_dir(self, sourcedir, targetdir, dtd_header=''):
1284         SUFFIX = '.SAMBABACKUP'
1285
1286         if not os.path.exists(targetdir):
1287             os.mkdir(targetdir)
1288
1289         l_dirs = [ sourcedir ]
1290         r_dirs = [ targetdir ]
1291         while l_dirs:
1292             l_dir = l_dirs.pop()
1293             r_dir = r_dirs.pop()
1294
1295             dirlist = os.listdir(l_dir)
1296             dirlist.sort()
1297             for e in dirlist:
1298                 l_name = os.path.join(l_dir, e)
1299                 r_name = os.path.join(r_dir, e)
1300
1301                 if os.path.isdir(l_name):
1302                     l_dirs.append(l_name)
1303                     r_dirs.append(r_name)
1304                     if not os.path.exists(r_name):
1305                         os.mkdir(r_name)
1306                 else:
1307                     if l_name.endswith('.xml'):
1308                         # Restore the xml file if possible
1309
1310                         # Get the filename to find the parser
1311                         to_parse = os.path.basename(l_name)[:-4]
1312
1313                         parser = find_parser(to_parse)
1314                         try:
1315                             with open(l_name, 'r') as ltemp:
1316                                 data = ltemp.read()
1317                                 xml_head = '<?xml version="1.0" encoding="utf-8"?>'
1318
1319                                 if data.startswith(xml_head):
1320                                     # It appears that sometimes the DTD rejects
1321                                     # the xml header being after it.
1322                                     data = data[len(xml_head):]
1323
1324                                     # Load the XML file with the DTD (entity) header
1325                                     parser.load_xml(ET.fromstring(xml_head + dtd_header + data))
1326                                 else:
1327                                     parser.load_xml(ET.fromstring(dtd_header + data))
1328
1329                                 # Write out the substituted files in the output
1330                                 # location, ready to copy over.
1331                                 parser.write_binary(r_name[:-4])
1332
1333                         except GPNoParserException:
1334                             # In the failure case, we fallback
1335                             original_file = l_name[:-4] + SUFFIX
1336                             shutil.copy2(original_file, r_name[:-4])
1337
1338                             self.outf.write('WARNING: No such parser for %s\n' % to_parse)
1339                             self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1340                         except:
1341                             import traceback
1342                             traceback.print_exc()
1343
1344                             # In the failure case, we fallback
1345                             original_file = l_name[:-4] + SUFFIX
1346                             shutil.copy2(original_file, r_name[:-4])
1347
1348                             self.outf.write('WARNING: Error during parsing for %s\n' % l_name)
1349                             self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1350
1351     def run(self, displayname, backup, H=None, tmpdir=None, entities=None, sambaopts=None, credopts=None,
1352             versionopts=None):
1353
1354         dtd_header = ''
1355
1356         if not os.path.exists(backup):
1357             raise CommandError("Backup directory does not exist %s" % backup)
1358
1359         if entities is not None:
1360             # DOCTYPE name is meant to match root element, but ElementTree does
1361             # not seem to care, so this seems to be enough.
1362
1363             dtd_header = '<!DOCTYPE foobar [\n'
1364
1365             if not os.path.exists(entities):
1366                 raise CommandError("Entities file does not exist %s" %
1367                                    entities)
1368             with open(entities, 'r') as entities_file:
1369                 entities_content = entities_file.read()
1370
1371                 # Do a basic regex test of the entities file format
1372                 if re.match('(\s*<!ENTITY\s*[a-zA-Z0-9_]+\s*.*?>)+\s*\Z',
1373                             entities_content, flags=re.MULTILINE) is None:
1374                     raise CommandError("Entities file does not appear to "
1375                                        "conform to format\n"
1376                                        'e.g. <!ENTITY entity "value">')
1377                 dtd_header += entities_content.strip()
1378
1379             dtd_header += '\n]>\n'
1380
1381         super(cmd_restore, self).run(displayname, H, tmpdir, sambaopts,
1382                                     credopts, versionopts)
1383
1384         try:
1385             # Iterate over backup files and restore with DTD
1386             self.restore_from_backup_to_local_dir(backup, self.gpodir,
1387                                                   dtd_header)
1388
1389             # Copy GPO files over SMB
1390             copy_directory_local_to_remote(self.conn, self.gpodir,
1391                                            self.sharepath,
1392                                            ignore_existing=True)
1393
1394         except Exception as e:
1395             import traceback
1396             traceback.print_exc()
1397             self.outf.write(str(e) + '\n')
1398
1399             self.outf.write("Failed to restore GPO -- deleting...\n")
1400             cmd = cmd_del()
1401             cmd.run(self.gpo_name, H, sambaopts, credopts, versionopts)
1402
1403             raise CommandError("Failed to restore: %s" % e)
1404
1405
1406 class cmd_del(Command):
1407     """Delete a GPO."""
1408
1409     synopsis = "%prog <gpo> [options]"
1410
1411     takes_optiongroups = {
1412         "sambaopts": options.SambaOptions,
1413         "versionopts": options.VersionOptions,
1414         "credopts": options.CredentialsOptions,
1415     }
1416
1417     takes_args = ['gpo']
1418
1419     takes_options = [
1420         Option("-H", help="LDB URL for database or target server", type=str),
1421     ]
1422
1423     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1424             versionopts=None):
1425
1426         self.lp = sambaopts.get_loadparm()
1427         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1428
1429         # We need to know writable DC to setup SMB connection
1430         if H and H.startswith('ldap://'):
1431             dc_hostname = H[7:]
1432             self.url = H
1433         else:
1434             dc_hostname = netcmd_finddc(self.lp, self.creds)
1435             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1436
1437         samdb_connect(self)
1438
1439         # Check if valid GPO
1440         try:
1441             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1442             unc_path = msg['gPCFileSysPath'][0]
1443         except Exception:
1444             raise CommandError("GPO '%s' does not exist" % gpo)
1445
1446         # Connect to DC over SMB
1447         [dom_name, service, sharepath] = parse_unc(unc_path)
1448         try:
1449             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1450         except Exception as e:
1451             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1452
1453         self.samdb.transaction_start()
1454         try:
1455             # Check for existing links
1456             msg = get_gpo_containers(self.samdb, gpo)
1457
1458             if len(msg):
1459                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1460                 for m in msg:
1461                     del_gpo_link(self.samdb, m['dn'], gpo)
1462                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1463
1464             # Remove LDAP entries
1465             gpo_dn = get_gpo_dn(self.samdb, gpo)
1466             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1467             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1468             self.samdb.delete(gpo_dn)
1469
1470             # Remove GPO files
1471             conn.deltree(sharepath)
1472
1473         except Exception:
1474             self.samdb.transaction_cancel()
1475             raise
1476         else:
1477             self.samdb.transaction_commit()
1478
1479         self.outf.write("GPO %s deleted.\n" % gpo)
1480
1481
1482 class cmd_aclcheck(Command):
1483     """Check all GPOs have matching LDAP and DS ACLs."""
1484
1485     synopsis = "%prog [options]"
1486
1487     takes_optiongroups = {
1488         "sambaopts": options.SambaOptions,
1489         "versionopts": options.VersionOptions,
1490         "credopts": options.CredentialsOptions,
1491     }
1492
1493     takes_options = [
1494         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1495                metavar="URL", dest="H")
1496     ]
1497
1498     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1499
1500         self.lp = sambaopts.get_loadparm()
1501         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1502
1503         self.url = dc_url(self.lp, self.creds, H)
1504
1505         # We need to know writable DC to setup SMB connection
1506         if H and H.startswith('ldap://'):
1507             dc_hostname = H[7:]
1508             self.url = H
1509         else:
1510             dc_hostname = netcmd_finddc(self.lp, self.creds)
1511             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1512
1513         samdb_connect(self)
1514
1515         msg = get_gpo_info(self.samdb, None)
1516
1517         for m in msg:
1518             # verify UNC path
1519             unc = m['gPCFileSysPath'][0]
1520             try:
1521                 [dom_name, service, sharepath] = parse_unc(unc)
1522             except ValueError:
1523                 raise CommandError("Invalid GPO path (%s)" % unc)
1524
1525             # SMB connect to DC
1526             try:
1527                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1528             except Exception:
1529                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1530
1531             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1532
1533             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1534             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1535
1536             # Create a file system security descriptor
1537             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1538             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1539
1540             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1541                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1542
1543
1544 class cmd_gpo(SuperCommand):
1545     """Group Policy Object (GPO) management."""
1546
1547     subcommands = {}
1548     subcommands["listall"] = cmd_listall()
1549     subcommands["list"] = cmd_list()
1550     subcommands["show"] = cmd_show()
1551     subcommands["getlink"] = cmd_getlink()
1552     subcommands["setlink"] = cmd_setlink()
1553     subcommands["dellink"] = cmd_dellink()
1554     subcommands["listcontainers"] = cmd_listcontainers()
1555     subcommands["getinheritance"] = cmd_getinheritance()
1556     subcommands["setinheritance"] = cmd_setinheritance()
1557     subcommands["fetch"] = cmd_fetch()
1558     subcommands["create"] = cmd_create()
1559     subcommands["del"] = cmd_del()
1560     subcommands["aclcheck"] = cmd_aclcheck()
1561     subcommands["backup"] = cmd_backup()
1562     subcommands["restore"] = cmd_restore()