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