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