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