gpo: Add a --generalize to the backup command
[Samba.git] / python / samba / netcmd / gpo.py
blob92526965ccc96d442fc603ba79d0948bb47e3cf4
1 # implement samba_tool gpo commands
3 # Copyright Andrew Tridgell 2010
4 # Copyright Amitay Isaacs 2011-2012 <amitay@gmail.com>
6 # based on C implementation by Guenther Deschner and Wilco Baan Hofman
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.
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.
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/>.
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
29 from samba.auth import system_session
30 from samba.netcmd import (
31 Command,
32 CommandError,
33 Option,
34 SuperCommand,
36 from samba.samdb import SamDB
37 from samba import dsdb
38 from samba.dcerpc import security
39 from samba.ndr import ndr_unpack
40 import samba.security
41 import samba.auth
42 from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
43 from samba.netcmd.common import netcmd_finddc
44 from samba import policy
45 from samba import smb
46 from samba import NTSTATUSError
47 import uuid
48 from samba.ntacls import dsacl2fsacl
49 from samba.dcerpc import nbt
50 from samba.net import Net
51 from samba.gp_parse import GPParser, GPNoParserException, GPGeneralizeException
52 from samba.gp_parse.gp_pol import GPPolParser
53 from samba.gp_parse.gp_ini import (
54 GPIniParser,
55 GPTIniParser,
56 GPFDeploy1IniParser,
57 GPScriptsIniParser
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
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)
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
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
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
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
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
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'''
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
137 def get_gpo_dn(samdb, gpo):
138 '''Construct the DN for gpo'''
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
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'''
150 policies_dn = samdb.get_default_basedn()
151 policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
153 base_dn = policies_dn
154 search_expr = "(objectClass=groupPolicyContainer)"
155 search_scope = ldb.SCOPE_ONELEVEL
157 if gpo is not None:
158 search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
160 if displayname is not None:
161 search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
163 if dn is not None:
164 base_dn = dn
165 search_scope = ldb.SCOPE_BASE
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)
184 return msg
187 def get_gpo_containers(samdb, gpo):
188 '''lists dn of containers for a GPO'''
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)
196 return msg
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)
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")
221 if not found:
222 raise CommandError("GPO '%s' not linked to this container" % gpo)
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)
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)
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()
270 return GPParser()
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()
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'])
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)
298 parser = find_parser(e['name'])
299 parser.parse(data)
300 parser.write_xml(l_name + '.xml')
303 attr_flags = smb.FILE_ATTRIBUTE_SYSTEM | \
304 smb.FILE_ATTRIBUTE_DIRECTORY | \
305 smb.FILE_ATTRIBUTE_ARCHIVE | \
306 smb.FILE_ATTRIBUTE_HIDDEN
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()
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'])
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)
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()
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
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)
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)
370 class cmd_listall(Command):
371 """List all GPOs."""
373 synopsis = "%prog [options]"
375 takes_optiongroups = {
376 "sambaopts": options.SambaOptions,
377 "versionopts": options.VersionOptions,
378 "credopts": options.CredentialsOptions,
381 takes_options = [
382 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
383 metavar="URL", dest="H")
386 def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
388 self.lp = sambaopts.get_loadparm()
389 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
391 self.url = dc_url(self.lp, self.creds, H)
393 samdb_connect(self)
395 msg = get_gpo_info(self.samdb, None)
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")
407 class cmd_list(Command):
408 """List GPOs for an account."""
410 synopsis = "%prog <username> [options]"
412 takes_args = ['username']
413 takes_optiongroups = {
414 "sambaopts": options.SambaOptions,
415 "versionopts": options.VersionOptions,
416 "credopts": options.CredentialsOptions,
419 takes_options = [
420 Option("-H", "--URL", help="LDB URL for database or target server",
421 type=str, metavar="URL", dest="H")
424 def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
426 self.lp = sambaopts.get_loadparm()
427 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
429 self.url = dc_url(self.lp, self.creds, H)
431 samdb_connect(self)
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)
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)
447 session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
448 AUTH_SESSION_INFO_AUTHENTICATED )
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
454 session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
455 session_info_flags=session_info_flags)
457 token = session.security_token
459 gpos = []
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
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
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
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]))
503 # check if this blocks inheritance
504 gpoptions = int(attr_default(msg, 'gPOptions', 0))
505 if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
506 inherit = False
508 if dn == self.samdb.get_default_basedn():
509 break
510 dn = dn.parent()
512 if is_computer:
513 msg_str = 'computer'
514 else:
515 msg_str = 'user'
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]))
522 class cmd_show(Command):
523 """Show information for a GPO."""
525 synopsis = "%prog <gpo> [options]"
527 takes_optiongroups = {
528 "sambaopts": options.SambaOptions,
529 "versionopts": options.VersionOptions,
530 "credopts": options.CredentialsOptions,
533 takes_args = ['gpo']
535 takes_options = [
536 Option("-H", help="LDB URL for database or target server", type=str)
539 def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
541 self.lp = sambaopts.get_loadparm()
542 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
544 self.url = dc_url(self.lp, self.creds, H)
546 samdb_connect(self)
548 try:
549 msg = get_gpo_info(self.samdb, gpo)[0]
550 except Exception:
551 raise CommandError("GPO '%s' does not exist" % gpo)
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>"
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")
570 class cmd_getlink(Command):
571 """List GPO Links for a container."""
573 synopsis = "%prog <container_dn> [options]"
575 takes_optiongroups = {
576 "sambaopts": options.SambaOptions,
577 "versionopts": options.VersionOptions,
578 "credopts": options.CredentialsOptions,
581 takes_args = ['container_dn']
583 takes_options = [
584 Option("-H", help="LDB URL for database or target server", type=str)
587 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
588 versionopts=None):
590 self.lp = sambaopts.get_loadparm()
591 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
593 self.url = dc_url(self.lp, self.creds, H)
595 samdb_connect(self)
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)
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)
617 class cmd_setlink(Command):
618 """Add or update a GPO link to a container."""
620 synopsis = "%prog <container_dn> <gpo> [options]"
622 takes_optiongroups = {
623 "sambaopts": options.SambaOptions,
624 "versionopts": options.VersionOptions,
625 "credopts": options.CredentialsOptions,
628 takes_args = ['container_dn', 'gpo']
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")
638 def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
639 sambaopts=None, credopts=None, versionopts=None):
641 self.lp = sambaopts.get_loadparm()
642 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
644 self.url = dc_url(self.lp, self.creds, H)
646 samdb_connect(self)
648 gplink_options = 0
649 if disabled:
650 gplink_options |= dsdb.GPLINK_OPT_DISABLE
651 if enforced:
652 gplink_options |= dsdb.GPLINK_OPT_ENFORCE
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))
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)
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 })
688 gplink_str = encode_gplink(gplist)
690 m = ldb.Message()
691 m.dn = ldb.Dn(self.samdb, container_dn)
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')
698 try:
699 self.samdb.modify(m)
700 except Exception as e:
701 raise CommandError("Error adding GPO Link", e)
703 self.outf.write("Added/Updated GPO link\n")
704 cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
707 class cmd_dellink(Command):
708 """Delete GPO link from a container."""
710 synopsis = "%prog <container_dn> <gpo> [options]"
712 takes_optiongroups = {
713 "sambaopts": options.SambaOptions,
714 "versionopts": options.VersionOptions,
715 "credopts": options.CredentialsOptions,
718 takes_args = ['container', 'gpo']
720 takes_options = [
721 Option("-H", help="LDB URL for database or target server", type=str),
724 def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
725 versionopts=None):
727 self.lp = sambaopts.get_loadparm()
728 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
730 self.url = dc_url(self.lp, self.creds, H)
732 samdb_connect(self)
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)
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)
746 class cmd_listcontainers(Command):
747 """List all linked containers for a GPO."""
749 synopsis = "%prog <gpo> [options]"
751 takes_optiongroups = {
752 "sambaopts": options.SambaOptions,
753 "versionopts": options.VersionOptions,
754 "credopts": options.CredentialsOptions,
757 takes_args = ['gpo']
759 takes_options = [
760 Option("-H", help="LDB URL for database or target server", type=str)
763 def run(self, gpo, H=None, sambaopts=None, credopts=None,
764 versionopts=None):
766 self.lp = sambaopts.get_loadparm()
767 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
769 self.url = dc_url(self.lp, self.creds, H)
771 samdb_connect(self)
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)
782 class cmd_getinheritance(Command):
783 """Get inheritance flag for a container."""
785 synopsis = "%prog <container_dn> [options]"
787 takes_optiongroups = {
788 "sambaopts": options.SambaOptions,
789 "versionopts": options.VersionOptions,
790 "credopts": options.CredentialsOptions,
793 takes_args = ['container_dn']
795 takes_options = [
796 Option("-H", help="LDB URL for database or target server", type=str)
799 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
800 versionopts=None):
802 self.lp = sambaopts.get_loadparm()
803 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
805 self.url = dc_url(self.lp, self.creds, H)
807 samdb_connect(self)
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)
816 inheritance = 0
817 if 'gPOptions' in msg:
818 inheritance = int(msg['gPOptions'][0])
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")
826 class cmd_setinheritance(Command):
827 """Set inheritance flag on a container."""
829 synopsis = "%prog <container_dn> <block|inherit> [options]"
831 takes_optiongroups = {
832 "sambaopts": options.SambaOptions,
833 "versionopts": options.VersionOptions,
834 "credopts": options.CredentialsOptions,
837 takes_args = [ 'container_dn', 'inherit_state' ]
839 takes_options = [
840 Option("-H", help="LDB URL for database or target server", type=str)
843 def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
844 versionopts=None):
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)
853 self.lp = sambaopts.get_loadparm()
854 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
856 self.url = dc_url(self.lp, self.creds, H)
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)
866 m = ldb.Message()
867 m.dn = ldb.Dn(self.samdb, container_dn)
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')
874 try:
875 self.samdb.modify(m)
876 except Exception as e:
877 raise CommandError("Error setting inheritance state %s" % inherit_state, e)
880 class cmd_fetch(Command):
881 """Download a GPO."""
883 synopsis = "%prog <gpo> [options]"
885 takes_optiongroups = {
886 "sambaopts": options.SambaOptions,
887 "versionopts": options.VersionOptions,
888 "credopts": options.CredentialsOptions,
891 takes_args = ['gpo']
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)
898 def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
900 self.lp = sambaopts.get_loadparm()
901 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
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)
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)
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)
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)
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)
936 localdir = os.path.join(tmpdir, "policy")
937 if not os.path.isdir(localdir):
938 os.mkdir(localdir)
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)
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)
953 class cmd_backup(Command):
954 """Backup a GPO."""
956 synopsis = "%prog <gpo> [options]"
958 takes_optiongroups = {
959 "sambaopts": options.SambaOptions,
960 "versionopts": options.VersionOptions,
961 "credopts": options.CredentialsOptions,
964 takes_args = ['gpo']
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 Option("--generalize", help="Generalize XML entities to restore",
970 default=False, action='store_true'),
971 Option("--entities", help="File to export defining XML entities for the restore",
972 dest='ent_file', type=str)
975 def run(self, gpo, H=None, tmpdir=None, generalize=False, sambaopts=None,
976 credopts=None, versionopts=None, ent_file=None):
978 self.lp = sambaopts.get_loadparm()
979 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
981 # We need to know writable DC to setup SMB connection
982 if H and H.startswith('ldap://'):
983 dc_hostname = H[7:]
984 self.url = H
985 else:
986 dc_hostname = netcmd_finddc(self.lp, self.creds)
987 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
989 samdb_connect(self)
990 try:
991 msg = get_gpo_info(self.samdb, gpo)[0]
992 except Exception:
993 raise CommandError("GPO '%s' does not exist" % gpo)
995 # verify UNC path
996 unc = msg['gPCFileSysPath'][0]
997 try:
998 [dom_name, service, sharepath] = parse_unc(unc)
999 except ValueError:
1000 raise CommandError("Invalid GPO path (%s)" % unc)
1002 # SMB connect to DC
1003 try:
1004 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1005 except Exception:
1006 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1008 # Copy GPT
1009 if tmpdir is None:
1010 tmpdir = "/tmp"
1011 if not os.path.isdir(tmpdir):
1012 raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
1014 localdir = os.path.join(tmpdir, "policy")
1015 if not os.path.isdir(localdir):
1016 os.mkdir(localdir)
1018 gpodir = os.path.join(localdir, gpo)
1019 if os.path.isdir(gpodir):
1020 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1022 try:
1023 os.mkdir(gpodir)
1024 backup_directory_remote_to_local(conn, sharepath, gpodir)
1025 except Exception as e:
1026 # FIXME: Catch more specific exception
1027 raise CommandError("Error copying GPO from DC", e)
1029 self.outf.write('GPO copied to %s\n' % gpodir)
1031 if generalize:
1032 self.outf.write('\nAttempting to generalize XML entities:\n')
1033 entities = cmd_backup.generalize_xml_entities(self.outf, gpodir,
1034 gpodir)
1035 import operator
1036 ents = ''
1037 for ent in sorted(entities.items(), key=operator.itemgetter(1)):
1038 ents += '<!ENTITY {} "{}">\n'.format(ent[1].strip('&;'), ent[0])
1040 if ent_file:
1041 with open(ent_file, 'w') as f:
1042 f.write(ents)
1043 self.outf.write('Entities successfully written to %s\n' %
1044 ent_file)
1045 else:
1046 self.outf.write('\nEntities:\n')
1047 self.outf.write(ents)
1049 @staticmethod
1050 def generalize_xml_entities(outf, sourcedir, targetdir):
1051 entities = {}
1053 if not os.path.exists(targetdir):
1054 os.mkdir(targetdir)
1056 l_dirs = [ sourcedir ]
1057 r_dirs = [ targetdir ]
1058 while l_dirs:
1059 l_dir = l_dirs.pop()
1060 r_dir = r_dirs.pop()
1062 dirlist = os.listdir(l_dir)
1063 dirlist.sort()
1064 for e in dirlist:
1065 l_name = os.path.join(l_dir, e)
1066 r_name = os.path.join(r_dir, e)
1068 if os.path.isdir(l_name):
1069 l_dirs.append(l_name)
1070 r_dirs.append(r_name)
1071 if not os.path.exists(r_name):
1072 os.mkdir(r_name)
1073 else:
1074 if l_name.endswith('.xml'):
1075 # Restore the xml file if possible
1077 # Get the filename to find the parser
1078 to_parse = os.path.basename(l_name)[:-4]
1080 parser = find_parser(to_parse)
1081 try:
1082 with open(l_name, 'r') as ltemp:
1083 data = ltemp.read()
1085 concrete_xml = ET.fromstring(data)
1086 found_entities = parser.generalize_xml(concrete_xml, r_name, entities)
1087 except GPGeneralizeException:
1088 outf.write('SKIPPING: Generalizing failed for %s\n' % to_parse)
1090 else:
1091 # No need to generalize non-xml files.
1093 # TODO This could be improved with xml files stored in
1094 # the renamed backup file (with custom extension) by
1095 # inlining them into the exported backups.
1096 if not os.path.samefile(l_name, r_name):
1097 shutil.copy2(l_name, r_name)
1099 return entities
1102 class cmd_create(Command):
1103 """Create an empty GPO."""
1105 synopsis = "%prog <displayname> [options]"
1107 takes_optiongroups = {
1108 "sambaopts": options.SambaOptions,
1109 "versionopts": options.VersionOptions,
1110 "credopts": options.CredentialsOptions,
1113 takes_args = ['displayname']
1115 takes_options = [
1116 Option("-H", help="LDB URL for database or target server", type=str),
1117 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1120 def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
1121 versionopts=None):
1123 self.lp = sambaopts.get_loadparm()
1124 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1126 net = Net(creds=self.creds, lp=self.lp)
1128 # We need to know writable DC to setup SMB connection
1129 if H and H.startswith('ldap://'):
1130 dc_hostname = H[7:]
1131 self.url = H
1132 flags = (nbt.NBT_SERVER_LDAP |
1133 nbt.NBT_SERVER_DS |
1134 nbt.NBT_SERVER_WRITABLE)
1135 cldap_ret = net.finddc(address=dc_hostname, flags=flags)
1136 else:
1137 flags = (nbt.NBT_SERVER_LDAP |
1138 nbt.NBT_SERVER_DS |
1139 nbt.NBT_SERVER_WRITABLE)
1140 cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
1141 dc_hostname = cldap_ret.pdc_dns_name
1142 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1144 samdb_connect(self)
1146 msg = get_gpo_info(self.samdb, displayname=displayname)
1147 if msg.count > 0:
1148 raise CommandError("A GPO already existing with name '%s'" % displayname)
1150 # Create new GUID
1151 guid = str(uuid.uuid4())
1152 gpo = "{%s}" % guid.upper()
1154 self.gpo_name = gpo
1156 realm = cldap_ret.dns_domain
1157 unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1159 # Create GPT
1160 if tmpdir is None:
1161 tmpdir = "/tmp"
1162 if not os.path.isdir(tmpdir):
1163 raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
1164 self.tmpdir = tmpdir
1166 localdir = os.path.join(tmpdir, "policy")
1167 if not os.path.isdir(localdir):
1168 os.mkdir(localdir)
1170 gpodir = os.path.join(localdir, gpo)
1171 self.gpodir = gpodir
1172 if os.path.isdir(gpodir):
1173 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1175 try:
1176 os.mkdir(gpodir)
1177 os.mkdir(os.path.join(gpodir, "Machine"))
1178 os.mkdir(os.path.join(gpodir, "User"))
1179 gpt_contents = "[General]\r\nVersion=0\r\n"
1180 open(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
1181 except Exception as e:
1182 raise CommandError("Error Creating GPO files", e)
1184 # Connect to DC over SMB
1185 [dom_name, service, sharepath] = parse_unc(unc_path)
1186 self.sharepath = sharepath
1187 try:
1188 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1189 except Exception as e:
1190 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1192 self.conn = conn
1194 self.samdb.transaction_start()
1195 try:
1196 # Add cn=<guid>
1197 gpo_dn = get_gpo_dn(self.samdb, gpo)
1199 m = ldb.Message()
1200 m.dn = gpo_dn
1201 m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
1202 self.samdb.add(m)
1204 # Add cn=User,cn=<guid>
1205 m = ldb.Message()
1206 m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
1207 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1208 self.samdb.add(m)
1210 # Add cn=Machine,cn=<guid>
1211 m = ldb.Message()
1212 m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
1213 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1214 self.samdb.add(m)
1216 # Get new security descriptor
1217 ds_sd_flags = ( security.SECINFO_OWNER |
1218 security.SECINFO_GROUP |
1219 security.SECINFO_DACL )
1220 msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
1221 ds_sd_ndr = msg['nTSecurityDescriptor'][0]
1222 ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1224 # Create a file system security descriptor
1225 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1226 sddl = dsacl2fsacl(ds_sd, domain_sid)
1227 fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
1229 # Copy GPO directory
1230 create_directory_hier(conn, sharepath)
1232 # Set ACL
1233 sio = ( security.SECINFO_OWNER |
1234 security.SECINFO_GROUP |
1235 security.SECINFO_DACL |
1236 security.SECINFO_PROTECTED_DACL )
1237 conn.set_acl(sharepath, fs_sd, sio)
1239 # Copy GPO files over SMB
1240 copy_directory_local_to_remote(conn, gpodir, sharepath)
1242 m = ldb.Message()
1243 m.dn = gpo_dn
1244 m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1245 m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1246 m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1247 m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1248 m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1249 controls=["permissive_modify:0"]
1250 self.samdb.modify(m, controls=controls)
1251 except Exception:
1252 self.samdb.transaction_cancel()
1253 raise
1254 else:
1255 self.samdb.transaction_commit()
1257 self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1260 class cmd_restore(cmd_create):
1261 """Restore a GPO to a new container."""
1263 synopsis = "%prog <displayname> <backup location> [options]"
1265 takes_optiongroups = {
1266 "sambaopts": options.SambaOptions,
1267 "versionopts": options.VersionOptions,
1268 "credopts": options.CredentialsOptions,
1271 takes_args = ['displayname', 'backup']
1273 takes_options = [
1274 Option("-H", help="LDB URL for database or target server", type=str),
1275 Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1276 Option("--entities", help="File defining XML entities to insert into DOCTYPE header", type=str)
1279 def restore_from_backup_to_local_dir(self, sourcedir, targetdir, dtd_header=''):
1280 SUFFIX = '.SAMBABACKUP'
1282 if not os.path.exists(targetdir):
1283 os.mkdir(targetdir)
1285 l_dirs = [ sourcedir ]
1286 r_dirs = [ targetdir ]
1287 while l_dirs:
1288 l_dir = l_dirs.pop()
1289 r_dir = r_dirs.pop()
1291 dirlist = os.listdir(l_dir)
1292 dirlist.sort()
1293 for e in dirlist:
1294 l_name = os.path.join(l_dir, e)
1295 r_name = os.path.join(r_dir, e)
1297 if os.path.isdir(l_name):
1298 l_dirs.append(l_name)
1299 r_dirs.append(r_name)
1300 if not os.path.exists(r_name):
1301 os.mkdir(r_name)
1302 else:
1303 if l_name.endswith('.xml'):
1304 # Restore the xml file if possible
1306 # Get the filename to find the parser
1307 to_parse = os.path.basename(l_name)[:-4]
1309 parser = find_parser(to_parse)
1310 try:
1311 with open(l_name, 'r') as ltemp:
1312 data = ltemp.read()
1313 # Load the XML file with the DTD (entity) header
1314 parser.load_xml(ET.fromstring(dtd_header + data))
1316 # Write out the substituted files in the output
1317 # location, ready to copy over.
1318 parser.write_binary(r_name[:-4])
1320 except GPNoParserException:
1321 # In the failure case, we fallback
1322 original_file = l_name[:-4] + SUFFIX
1323 shutil.copy2(original_file, r_name[:-4])
1325 self.outf.write('WARNING: No such parser for %s\n' % to_parse)
1326 self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1327 except:
1328 import traceback
1329 traceback.print_exc()
1331 # In the failure case, we fallback
1332 original_file = l_name[:-4] + SUFFIX
1333 shutil.copy2(original_file, r_name[:-4])
1335 self.outf.write('WARNING: Error during parsing for %s\n' % l_name)
1336 self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1338 def run(self, displayname, backup, H=None, tmpdir=None, entities=None, sambaopts=None, credopts=None,
1339 versionopts=None):
1341 dtd_header = ''
1343 if not os.path.exists(backup):
1344 raise CommandError("Backup directory does not exist %s" % backup)
1346 if entities is not None:
1347 # DOCTYPE name is meant to match root element, but ElementTree does
1348 # not seem to care, so this seems to be enough.
1350 dtd_header = '<!DOCTYPE foobar [\n'
1352 if not os.path.exists(entities):
1353 raise CommandError("Entities file does not exist %s" %
1354 entities)
1355 with open(entities, 'r') as entities_file:
1356 entities_content = entities_file.read()
1358 # Do a basic regex test of the entities file format
1359 if re.match('(\s*<!ENTITY\s*[a-zA-Z0-9_]+\s*.*?>)+\s*\Z',
1360 entities_content, flags=re.MULTILINE) is None:
1361 raise CommandError("Entities file does not appear to "
1362 "conform to format\n"
1363 'e.g. <!ENTITY entity "value">')
1364 dtd_header += entities_content.strip()
1366 dtd_header += '\n]>\n'
1368 super(cmd_restore, self).run(displayname, H, tmpdir, sambaopts,
1369 credopts, versionopts)
1371 try:
1372 # Iterate over backup files and restore with DTD
1373 self.restore_from_backup_to_local_dir(backup, self.gpodir,
1374 dtd_header)
1376 # Copy GPO files over SMB
1377 copy_directory_local_to_remote(self.conn, self.gpodir,
1378 self.sharepath,
1379 ignore_existing=True)
1381 except Exception as e:
1382 import traceback
1383 traceback.print_exc()
1384 self.outf.write(str(e) + '\n')
1386 self.outf.write("Failed to restore GPO -- deleting...\n")
1387 cmd = cmd_del()
1388 cmd.run(self.gpo_name, H, sambaopts, credopts, versionopts)
1390 raise CommandError("Failed to restore: %s" % e)
1393 class cmd_del(Command):
1394 """Delete a GPO."""
1396 synopsis = "%prog <gpo> [options]"
1398 takes_optiongroups = {
1399 "sambaopts": options.SambaOptions,
1400 "versionopts": options.VersionOptions,
1401 "credopts": options.CredentialsOptions,
1404 takes_args = ['gpo']
1406 takes_options = [
1407 Option("-H", help="LDB URL for database or target server", type=str),
1410 def run(self, gpo, H=None, sambaopts=None, credopts=None,
1411 versionopts=None):
1413 self.lp = sambaopts.get_loadparm()
1414 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1416 # We need to know writable DC to setup SMB connection
1417 if H and H.startswith('ldap://'):
1418 dc_hostname = H[7:]
1419 self.url = H
1420 else:
1421 dc_hostname = netcmd_finddc(self.lp, self.creds)
1422 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1424 samdb_connect(self)
1426 # Check if valid GPO
1427 try:
1428 msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1429 unc_path = msg['gPCFileSysPath'][0]
1430 except Exception:
1431 raise CommandError("GPO '%s' does not exist" % gpo)
1433 # Connect to DC over SMB
1434 [dom_name, service, sharepath] = parse_unc(unc_path)
1435 try:
1436 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1437 except Exception as e:
1438 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1440 self.samdb.transaction_start()
1441 try:
1442 # Check for existing links
1443 msg = get_gpo_containers(self.samdb, gpo)
1445 if len(msg):
1446 self.outf.write("GPO %s is linked to containers\n" % gpo)
1447 for m in msg:
1448 del_gpo_link(self.samdb, m['dn'], gpo)
1449 self.outf.write(" Removed link from %s.\n" % m['dn'])
1451 # Remove LDAP entries
1452 gpo_dn = get_gpo_dn(self.samdb, gpo)
1453 self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1454 self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1455 self.samdb.delete(gpo_dn)
1457 # Remove GPO files
1458 conn.deltree(sharepath)
1460 except Exception:
1461 self.samdb.transaction_cancel()
1462 raise
1463 else:
1464 self.samdb.transaction_commit()
1466 self.outf.write("GPO %s deleted.\n" % gpo)
1469 class cmd_aclcheck(Command):
1470 """Check all GPOs have matching LDAP and DS ACLs."""
1472 synopsis = "%prog [options]"
1474 takes_optiongroups = {
1475 "sambaopts": options.SambaOptions,
1476 "versionopts": options.VersionOptions,
1477 "credopts": options.CredentialsOptions,
1480 takes_options = [
1481 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1482 metavar="URL", dest="H")
1485 def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1487 self.lp = sambaopts.get_loadparm()
1488 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1490 self.url = dc_url(self.lp, self.creds, H)
1492 # We need to know writable DC to setup SMB connection
1493 if H and H.startswith('ldap://'):
1494 dc_hostname = H[7:]
1495 self.url = H
1496 else:
1497 dc_hostname = netcmd_finddc(self.lp, self.creds)
1498 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1500 samdb_connect(self)
1502 msg = get_gpo_info(self.samdb, None)
1504 for m in msg:
1505 # verify UNC path
1506 unc = m['gPCFileSysPath'][0]
1507 try:
1508 [dom_name, service, sharepath] = parse_unc(unc)
1509 except ValueError:
1510 raise CommandError("Invalid GPO path (%s)" % unc)
1512 # SMB connect to DC
1513 try:
1514 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1515 except Exception:
1516 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1518 fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1520 ds_sd_ndr = m['nTSecurityDescriptor'][0]
1521 ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1523 # Create a file system security descriptor
1524 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1525 expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1527 if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1528 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1531 class cmd_gpo(SuperCommand):
1532 """Group Policy Object (GPO) management."""
1534 subcommands = {}
1535 subcommands["listall"] = cmd_listall()
1536 subcommands["list"] = cmd_list()
1537 subcommands["show"] = cmd_show()
1538 subcommands["getlink"] = cmd_getlink()
1539 subcommands["setlink"] = cmd_setlink()
1540 subcommands["dellink"] = cmd_dellink()
1541 subcommands["listcontainers"] = cmd_listcontainers()
1542 subcommands["getinheritance"] = cmd_getinheritance()
1543 subcommands["setinheritance"] = cmd_setinheritance()
1544 subcommands["fetch"] = cmd_fetch()
1545 subcommands["create"] = cmd_create()
1546 subcommands["del"] = cmd_del()
1547 subcommands["aclcheck"] = cmd_aclcheck()
1548 subcommands["backup"] = cmd_backup()
1549 subcommands["restore"] = cmd_restore()