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