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/>.
23 import samba.getopt as options
26 import xml.etree.ElementTree as ET
29 from samba.auth import system_session
30 from samba.netcmd import (
36 from samba.samdb import SamDB
37 from samba import dsdb
38 from samba.dcerpc import security
39 from samba.ndr import ndr_unpack
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
46 from samba import NTSTATUSError
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 GPIniParser, GPTIniParser, GPFDeploy1IniParser
54 from samba.gp_parse.gp_csv import GPAuditCsvParser
55 from samba.gp_parse.gp_inf import GptTmplInfParser
56 from samba.gp_parse.gp_aas import GPAasParser
59 def samdb_connect(ctx):
60 '''make a ldap connection to the server'''
62 ctx.samdb = SamDB(url=ctx.url,
63 session_info=system_session(),
64 credentials=ctx.creds, lp=ctx.lp)
65 except Exception as e:
66 raise CommandError("LDAP connection to %s failed " % ctx.url, e)
69 def attr_default(msg, attrname, default):
70 '''get an attribute from a ldap msg with a default'''
72 return msg[attrname][0]
76 def gpo_flags_string(value):
77 '''return gpo flags string'''
78 flags = policy.get_gpo_flags(value)
86 def gplink_options_string(value):
87 '''return gplink options string'''
88 options = policy.get_gplink_options(value)
92 ret = ' '.join(options)
96 def parse_gplink(gplink):
97 '''parse a gPLink into an array of dn and options'''
104 if len(d) != 2 or not d[0].startswith("[LDAP://"):
105 raise RuntimeError("Badly formed gPLink '%s'" % g)
106 ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
110 def encode_gplink(gplist):
111 '''Encode an array of dn and options into gPLink string'''
114 ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
118 def dc_url(lp, creds, url=None, dc=None):
119 '''If URL is not specified, return URL for writable DC.
120 If dc is provided, use that to construct ldap URL'''
125 dc = netcmd_finddc(lp, creds)
126 except Exception as e:
127 raise RuntimeError("Could not find a DC for domain", e)
132 def get_gpo_dn(samdb, gpo):
133 '''Construct the DN for gpo'''
135 dn = samdb.get_default_basedn()
136 dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
137 dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
141 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None,
142 sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL|security.SECINFO_SACL):
143 '''Get GPO information using gpo, displayname or dn'''
145 policies_dn = samdb.get_default_basedn()
146 policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
148 base_dn = policies_dn
149 search_expr = "(objectClass=groupPolicyContainer)"
150 search_scope = ldb.SCOPE_ONELEVEL
153 search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
155 if displayname is not None:
156 search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
160 search_scope = ldb.SCOPE_BASE
163 msg = samdb.search(base=base_dn, scope=search_scope,
164 expression=search_expr,
165 attrs=['nTSecurityDescriptor',
171 controls=['sd_flags:1:%d' % sd_flags])
172 except Exception as e:
174 mesg = "Cannot get information for GPO %s" % gpo
176 mesg = "Cannot get information for GPOs"
177 raise CommandError(mesg, e)
182 def get_gpo_containers(samdb, gpo):
183 '''lists dn of containers for a GPO'''
185 search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
187 msg = samdb.search(expression=search_expr, attrs=['gPLink'])
188 except Exception as e:
189 raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
194 def del_gpo_link(samdb, container_dn, gpo):
195 '''delete GPO link for the container'''
196 # Check if valid Container DN and get existing GPlinks
198 msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
199 expression="(objectClass=*)",
201 except Exception as e:
202 raise CommandError("Container '%s' does not exist" % container_dn, e)
205 gpo_dn = str(get_gpo_dn(samdb, gpo))
207 gplist = parse_gplink(msg['gPLink'][0])
209 if g['dn'].lower() == gpo_dn.lower():
214 raise CommandError("No GPO(s) linked to this container")
217 raise CommandError("GPO '%s' not linked to this container" % gpo)
222 gplink_str = encode_gplink(gplist)
223 m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
225 m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
228 except Exception as e:
229 raise CommandError("Error removing GPO from container", e)
233 '''Parse UNC string into a hostname, a service, and a filepath'''
234 if unc.startswith('\\\\') and unc.startswith('//'):
235 raise ValueError("UNC doesn't start with \\\\ or //")
236 tmp = unc[2:].split('/', 2)
239 tmp = unc[2:].split('\\', 2)
242 raise ValueError("Invalid UNC string: %s" % unc)
245 def find_parser(name, flags=re.IGNORECASE):
246 if re.match('fdeploy1\.ini$', name, flags=flags):
247 return GPFDeploy1IniParser()
248 if re.match('audit\.csv$', name, flags=flags):
249 return GPAuditCsvParser()
250 if re.match('GptTmpl\.inf$', name, flags=flags):
251 return GptTmplInfParser()
252 if re.match('GPT\.INI$', name, flags=flags):
253 return GPTIniParser()
254 if re.match('.*\.ini$', name, flags=flags):
256 if re.match('.*\.pol$', name, flags=flags):
258 if re.match('.*\.aas$', name, flags=flags):
264 def backup_directory_remote_to_local(conn, remotedir, localdir):
265 SUFFIX = '.SAMBABACKUP'
266 if not os.path.isdir(localdir):
268 r_dirs = [ remotedir ]
269 l_dirs = [ localdir ]
274 dirlist = conn.list(r_dir, attribs=attr_flags)
277 r_name = r_dir + '\\' + e['name']
278 l_name = os.path.join(l_dir, e['name'])
280 if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
281 r_dirs.append(r_name)
282 l_dirs.append(l_name)
285 data = conn.loadfile(r_name)
286 with file(l_name + SUFFIX, 'w') as f:
289 parser = find_parser(e['name'])
291 parser.write_xml(l_name + '.xml')
294 attr_flags = smb.FILE_ATTRIBUTE_SYSTEM | \
295 smb.FILE_ATTRIBUTE_DIRECTORY | \
296 smb.FILE_ATTRIBUTE_ARCHIVE | \
297 smb.FILE_ATTRIBUTE_HIDDEN
299 def copy_directory_remote_to_local(conn, remotedir, localdir):
300 if not os.path.isdir(localdir):
302 r_dirs = [ remotedir ]
303 l_dirs = [ localdir ]
308 dirlist = conn.list(r_dir, attribs=attr_flags)
311 r_name = r_dir + '\\' + e['name']
312 l_name = os.path.join(l_dir, e['name'])
314 if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
315 r_dirs.append(r_name)
316 l_dirs.append(l_name)
319 data = conn.loadfile(r_name)
320 open(l_name, 'w').write(data)
323 def copy_directory_local_to_remote(conn, localdir, remotedir,
324 ignore_existing=False):
325 if not conn.chkpath(remotedir):
326 conn.mkdir(remotedir)
327 l_dirs = [ localdir ]
328 r_dirs = [ remotedir ]
333 dirlist = os.listdir(l_dir)
336 l_name = os.path.join(l_dir, e)
337 r_name = r_dir + '\\' + e
339 if os.path.isdir(l_name):
340 l_dirs.append(l_name)
341 r_dirs.append(r_name)
344 except NTSTATUSError:
345 if not ignore_existing:
348 data = open(l_name, 'r').read()
349 conn.savefile(r_name, data)
352 def create_directory_hier(conn, remotedir):
353 elems = remotedir.replace('/', '\\').split('\\')
356 path = path + '\\' + e
357 if not conn.chkpath(path):
361 class cmd_listall(Command):
364 synopsis = "%prog [options]"
366 takes_optiongroups = {
367 "sambaopts": options.SambaOptions,
368 "versionopts": options.VersionOptions,
369 "credopts": options.CredentialsOptions,
373 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
374 metavar="URL", dest="H")
377 def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
379 self.lp = sambaopts.get_loadparm()
380 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
382 self.url = dc_url(self.lp, self.creds, H)
386 msg = get_gpo_info(self.samdb, None)
389 self.outf.write("GPO : %s\n" % m['name'][0])
390 self.outf.write("display name : %s\n" % m['displayName'][0])
391 self.outf.write("path : %s\n" % m['gPCFileSysPath'][0])
392 self.outf.write("dn : %s\n" % m.dn)
393 self.outf.write("version : %s\n" % attr_default(m, 'versionNumber', '0'))
394 self.outf.write("flags : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
395 self.outf.write("\n")
398 class cmd_list(Command):
399 """List GPOs for an account."""
401 synopsis = "%prog <username> [options]"
403 takes_args = ['username']
404 takes_optiongroups = {
405 "sambaopts": options.SambaOptions,
406 "versionopts": options.VersionOptions,
407 "credopts": options.CredentialsOptions,
411 Option("-H", "--URL", help="LDB URL for database or target server",
412 type=str, metavar="URL", dest="H")
415 def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
417 self.lp = sambaopts.get_loadparm()
418 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
420 self.url = dc_url(self.lp, self.creds, H)
425 msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
426 (ldb.binary_encode(username),ldb.binary_encode(username)))
429 raise CommandError("Failed to find account %s" % username)
431 # check if its a computer account
433 msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
434 is_computer = 'computer' in msg['objectClass']
436 raise CommandError("Failed to find objectClass for user %s" % username)
438 session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
439 AUTH_SESSION_INFO_AUTHENTICATED )
441 # When connecting to a remote server, don't look up the local privilege DB
442 if self.url is not None and self.url.startswith('ldap'):
443 session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
445 session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
446 session_info_flags=session_info_flags)
448 token = session.security_token
453 dn = ldb.Dn(self.samdb, str(user_dn)).parent()
455 msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
457 glist = parse_gplink(msg['gPLink'][0])
459 if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
461 if g['options'] & dsdb.GPLINK_OPT_DISABLE:
465 sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
466 gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
467 attrs=['name', 'displayName', 'flags',
468 'nTSecurityDescriptor'],
469 controls=['sd_flags:1:%d' % sd_flags])
470 secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
471 secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
473 self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
478 samba.security.access_check(secdesc, token,
479 security.SEC_STD_READ_CONTROL |
480 security.SEC_ADS_LIST |
481 security.SEC_ADS_READ_PROP)
483 self.outf.write("Failed access check on %s\n" % msg.dn)
486 # check the flags on the GPO
487 flags = int(attr_default(gmsg[0], 'flags', 0))
488 if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
490 if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
492 gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
494 # check if this blocks inheritance
495 gpoptions = int(attr_default(msg, 'gPOptions', 0))
496 if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
499 if dn == self.samdb.get_default_basedn():
508 self.outf.write("GPOs for %s %s\n" % (msg_str, username))
510 self.outf.write(" %s %s\n" % (g[0], g[1]))
513 class cmd_show(Command):
514 """Show information for a GPO."""
516 synopsis = "%prog <gpo> [options]"
518 takes_optiongroups = {
519 "sambaopts": options.SambaOptions,
520 "versionopts": options.VersionOptions,
521 "credopts": options.CredentialsOptions,
527 Option("-H", help="LDB URL for database or target server", type=str)
530 def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
532 self.lp = sambaopts.get_loadparm()
533 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
535 self.url = dc_url(self.lp, self.creds, H)
540 msg = get_gpo_info(self.samdb, gpo)[0]
542 raise CommandError("GPO '%s' does not exist" % gpo)
545 secdesc_ndr = msg['nTSecurityDescriptor'][0]
546 secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
547 secdesc_sddl = secdesc.as_sddl()
549 secdesc_sddl = "<hidden>"
551 self.outf.write("GPO : %s\n" % msg['name'][0])
552 self.outf.write("display name : %s\n" % msg['displayName'][0])
553 self.outf.write("path : %s\n" % msg['gPCFileSysPath'][0])
554 self.outf.write("dn : %s\n" % msg.dn)
555 self.outf.write("version : %s\n" % attr_default(msg, 'versionNumber', '0'))
556 self.outf.write("flags : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
557 self.outf.write("ACL : %s\n" % secdesc_sddl)
558 self.outf.write("\n")
561 class cmd_getlink(Command):
562 """List GPO Links for a container."""
564 synopsis = "%prog <container_dn> [options]"
566 takes_optiongroups = {
567 "sambaopts": options.SambaOptions,
568 "versionopts": options.VersionOptions,
569 "credopts": options.CredentialsOptions,
572 takes_args = ['container_dn']
575 Option("-H", help="LDB URL for database or target server", type=str)
578 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
581 self.lp = sambaopts.get_loadparm()
582 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
584 self.url = dc_url(self.lp, self.creds, H)
589 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
590 expression="(objectClass=*)",
593 raise CommandError("Container '%s' does not exist" % container_dn)
596 self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
597 gplist = parse_gplink(msg['gPLink'][0])
599 msg = get_gpo_info(self.samdb, dn=g['dn'])
600 self.outf.write(" GPO : %s\n" % msg[0]['name'][0])
601 self.outf.write(" Name : %s\n" % msg[0]['displayName'][0])
602 self.outf.write(" Options : %s\n" % gplink_options_string(g['options']))
603 self.outf.write("\n")
605 self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
608 class cmd_setlink(Command):
609 """Add or update a GPO link to a container."""
611 synopsis = "%prog <container_dn> <gpo> [options]"
613 takes_optiongroups = {
614 "sambaopts": options.SambaOptions,
615 "versionopts": options.VersionOptions,
616 "credopts": options.CredentialsOptions,
619 takes_args = ['container_dn', 'gpo']
622 Option("-H", help="LDB URL for database or target server", type=str),
623 Option("--disable", dest="disabled", default=False, action='store_true',
624 help="Disable policy"),
625 Option("--enforce", dest="enforced", default=False, action='store_true',
626 help="Enforce policy")
629 def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
630 sambaopts=None, credopts=None, versionopts=None):
632 self.lp = sambaopts.get_loadparm()
633 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
635 self.url = dc_url(self.lp, self.creds, H)
641 gplink_options |= dsdb.GPLINK_OPT_DISABLE
643 gplink_options |= dsdb.GPLINK_OPT_ENFORCE
645 # Check if valid GPO DN
647 msg = get_gpo_info(self.samdb, gpo=gpo)[0]
649 raise CommandError("GPO '%s' does not exist" % gpo)
650 gpo_dn = str(get_gpo_dn(self.samdb, gpo))
652 # Check if valid Container DN
654 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
655 expression="(objectClass=*)",
658 raise CommandError("Container '%s' does not exist" % container_dn)
660 # Update existing GPlinks or Add new one
661 existing_gplink = False
663 gplist = parse_gplink(msg['gPLink'][0])
664 existing_gplink = True
667 if g['dn'].lower() == gpo_dn.lower():
668 g['options'] = gplink_options
672 raise CommandError("GPO '%s' already linked to this container" % gpo)
674 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
677 gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
679 gplink_str = encode_gplink(gplist)
682 m.dn = ldb.Dn(self.samdb, container_dn)
685 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
687 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
691 except Exception as e:
692 raise CommandError("Error adding GPO Link", e)
694 self.outf.write("Added/Updated GPO link\n")
695 cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
698 class cmd_dellink(Command):
699 """Delete GPO link from a container."""
701 synopsis = "%prog <container_dn> <gpo> [options]"
703 takes_optiongroups = {
704 "sambaopts": options.SambaOptions,
705 "versionopts": options.VersionOptions,
706 "credopts": options.CredentialsOptions,
709 takes_args = ['container', 'gpo']
712 Option("-H", help="LDB URL for database or target server", type=str),
715 def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
718 self.lp = sambaopts.get_loadparm()
719 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
721 self.url = dc_url(self.lp, self.creds, H)
727 get_gpo_info(self.samdb, gpo=gpo)[0]
729 raise CommandError("GPO '%s' does not exist" % gpo)
731 container_dn = ldb.Dn(self.samdb, container)
732 del_gpo_link(self.samdb, container_dn, gpo)
733 self.outf.write("Deleted GPO link.\n")
734 cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
737 class cmd_listcontainers(Command):
738 """List all linked containers for a GPO."""
740 synopsis = "%prog <gpo> [options]"
742 takes_optiongroups = {
743 "sambaopts": options.SambaOptions,
744 "versionopts": options.VersionOptions,
745 "credopts": options.CredentialsOptions,
751 Option("-H", help="LDB URL for database or target server", type=str)
754 def run(self, gpo, H=None, sambaopts=None, credopts=None,
757 self.lp = sambaopts.get_loadparm()
758 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
760 self.url = dc_url(self.lp, self.creds, H)
764 msg = get_gpo_containers(self.samdb, gpo)
766 self.outf.write("Container(s) using GPO %s\n" % gpo)
768 self.outf.write(" DN: %s\n" % m['dn'])
770 self.outf.write("No Containers using GPO %s\n" % gpo)
773 class cmd_getinheritance(Command):
774 """Get inheritance flag for a container."""
776 synopsis = "%prog <container_dn> [options]"
778 takes_optiongroups = {
779 "sambaopts": options.SambaOptions,
780 "versionopts": options.VersionOptions,
781 "credopts": options.CredentialsOptions,
784 takes_args = ['container_dn']
787 Option("-H", help="LDB URL for database or target server", type=str)
790 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
793 self.lp = sambaopts.get_loadparm()
794 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
796 self.url = dc_url(self.lp, self.creds, H)
801 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
802 expression="(objectClass=*)",
803 attrs=['gPOptions'])[0]
805 raise CommandError("Container '%s' does not exist" % container_dn)
808 if 'gPOptions' in msg:
809 inheritance = int(msg['gPOptions'][0])
811 if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
812 self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
814 self.outf.write("Container has GPO_INHERIT\n")
817 class cmd_setinheritance(Command):
818 """Set inheritance flag on a container."""
820 synopsis = "%prog <container_dn> <block|inherit> [options]"
822 takes_optiongroups = {
823 "sambaopts": options.SambaOptions,
824 "versionopts": options.VersionOptions,
825 "credopts": options.CredentialsOptions,
828 takes_args = [ 'container_dn', 'inherit_state' ]
831 Option("-H", help="LDB URL for database or target server", type=str)
834 def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
837 if inherit_state.lower() == 'block':
838 inheritance = dsdb.GPO_BLOCK_INHERITANCE
839 elif inherit_state.lower() == 'inherit':
840 inheritance = dsdb.GPO_INHERIT
842 raise CommandError("Unknown inheritance state (%s)" % inherit_state)
844 self.lp = sambaopts.get_loadparm()
845 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
847 self.url = dc_url(self.lp, self.creds, H)
851 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
852 expression="(objectClass=*)",
853 attrs=['gPOptions'])[0]
855 raise CommandError("Container '%s' does not exist" % container_dn)
858 m.dn = ldb.Dn(self.samdb, container_dn)
860 if 'gPOptions' in msg:
861 m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
863 m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
867 except Exception as e:
868 raise CommandError("Error setting inheritance state %s" % inherit_state, e)
871 class cmd_fetch(Command):
872 """Download a GPO."""
874 synopsis = "%prog <gpo> [options]"
876 takes_optiongroups = {
877 "sambaopts": options.SambaOptions,
878 "versionopts": options.VersionOptions,
879 "credopts": options.CredentialsOptions,
885 Option("-H", help="LDB URL for database or target server", type=str),
886 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
889 def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
891 self.lp = sambaopts.get_loadparm()
892 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
894 # We need to know writable DC to setup SMB connection
895 if H and H.startswith('ldap://'):
899 dc_hostname = netcmd_finddc(self.lp, self.creds)
900 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
904 msg = get_gpo_info(self.samdb, gpo)[0]
906 raise CommandError("GPO '%s' does not exist" % gpo)
909 unc = msg['gPCFileSysPath'][0]
911 [dom_name, service, sharepath] = parse_unc(unc)
913 raise CommandError("Invalid GPO path (%s)" % unc)
917 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
919 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
924 if not os.path.isdir(tmpdir):
925 raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
927 localdir = os.path.join(tmpdir, "policy")
928 if not os.path.isdir(localdir):
931 gpodir = os.path.join(localdir, gpo)
932 if os.path.isdir(gpodir):
933 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
937 copy_directory_remote_to_local(conn, sharepath, gpodir)
938 except Exception as e:
939 # FIXME: Catch more specific exception
940 raise CommandError("Error copying GPO from DC", e)
941 self.outf.write('GPO copied to %s\n' % gpodir)
944 class cmd_backup(Command):
947 synopsis = "%prog <gpo> [options]"
949 takes_optiongroups = {
950 "sambaopts": options.SambaOptions,
951 "versionopts": options.VersionOptions,
952 "credopts": options.CredentialsOptions,
958 Option("-H", help="LDB URL for database or target server", type=str),
959 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
962 def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
964 self.lp = sambaopts.get_loadparm()
965 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
967 # We need to know writable DC to setup SMB connection
968 if H and H.startswith('ldap://'):
972 dc_hostname = netcmd_finddc(self.lp, self.creds)
973 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
977 msg = get_gpo_info(self.samdb, gpo)[0]
979 raise CommandError("GPO '%s' does not exist" % gpo)
982 unc = msg['gPCFileSysPath'][0]
984 [dom_name, service, sharepath] = parse_unc(unc)
986 raise CommandError("Invalid GPO path (%s)" % unc)
990 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
992 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
997 if not os.path.isdir(tmpdir):
998 raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
1000 localdir = os.path.join(tmpdir, "policy")
1001 if not os.path.isdir(localdir):
1004 gpodir = os.path.join(localdir, gpo)
1005 if os.path.isdir(gpodir):
1006 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1010 backup_directory_remote_to_local(conn, sharepath, gpodir)
1011 except Exception as e:
1012 # FIXME: Catch more specific exception
1013 raise CommandError("Error copying GPO from DC", e)
1014 self.outf.write('GPO copied to %s\n' % gpodir)
1017 class cmd_create(Command):
1018 """Create an empty GPO."""
1020 synopsis = "%prog <displayname> [options]"
1022 takes_optiongroups = {
1023 "sambaopts": options.SambaOptions,
1024 "versionopts": options.VersionOptions,
1025 "credopts": options.CredentialsOptions,
1028 takes_args = ['displayname']
1031 Option("-H", help="LDB URL for database or target server", type=str),
1032 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1035 def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
1038 self.lp = sambaopts.get_loadparm()
1039 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1041 net = Net(creds=self.creds, lp=self.lp)
1043 # We need to know writable DC to setup SMB connection
1044 if H and H.startswith('ldap://'):
1047 flags = (nbt.NBT_SERVER_LDAP |
1049 nbt.NBT_SERVER_WRITABLE)
1050 cldap_ret = net.finddc(address=dc_hostname, flags=flags)
1052 flags = (nbt.NBT_SERVER_LDAP |
1054 nbt.NBT_SERVER_WRITABLE)
1055 cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
1056 dc_hostname = cldap_ret.pdc_dns_name
1057 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1061 msg = get_gpo_info(self.samdb, displayname=displayname)
1063 raise CommandError("A GPO already existing with name '%s'" % displayname)
1066 guid = str(uuid.uuid4())
1067 gpo = "{%s}" % guid.upper()
1071 realm = cldap_ret.dns_domain
1072 unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1077 if not os.path.isdir(tmpdir):
1078 raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
1079 self.tmpdir = tmpdir
1081 localdir = os.path.join(tmpdir, "policy")
1082 if not os.path.isdir(localdir):
1085 gpodir = os.path.join(localdir, gpo)
1086 self.gpodir = gpodir
1087 if os.path.isdir(gpodir):
1088 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
1092 os.mkdir(os.path.join(gpodir, "Machine"))
1093 os.mkdir(os.path.join(gpodir, "User"))
1094 gpt_contents = "[General]\r\nVersion=0\r\n"
1095 open(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
1096 except Exception as e:
1097 raise CommandError("Error Creating GPO files", e)
1099 # Connect to DC over SMB
1100 [dom_name, service, sharepath] = parse_unc(unc_path)
1101 self.sharepath = sharepath
1103 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1104 except Exception as e:
1105 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1109 self.samdb.transaction_start()
1112 gpo_dn = get_gpo_dn(self.samdb, gpo)
1116 m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
1119 # Add cn=User,cn=<guid>
1121 m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
1122 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1125 # Add cn=Machine,cn=<guid>
1127 m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
1128 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1131 # Get new security descriptor
1132 ds_sd_flags = ( security.SECINFO_OWNER |
1133 security.SECINFO_GROUP |
1134 security.SECINFO_DACL )
1135 msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
1136 ds_sd_ndr = msg['nTSecurityDescriptor'][0]
1137 ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1139 # Create a file system security descriptor
1140 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1141 sddl = dsacl2fsacl(ds_sd, domain_sid)
1142 fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
1144 # Copy GPO directory
1145 create_directory_hier(conn, sharepath)
1148 sio = ( security.SECINFO_OWNER |
1149 security.SECINFO_GROUP |
1150 security.SECINFO_DACL |
1151 security.SECINFO_PROTECTED_DACL )
1152 conn.set_acl(sharepath, fs_sd, sio)
1154 # Copy GPO files over SMB
1155 copy_directory_local_to_remote(conn, gpodir, sharepath)
1159 m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1160 m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1161 m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1162 m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1163 m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1164 controls=["permissive_modify:0"]
1165 self.samdb.modify(m, controls=controls)
1167 self.samdb.transaction_cancel()
1170 self.samdb.transaction_commit()
1172 self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1175 class cmd_restore(cmd_create):
1176 """Restore a GPO to a new container."""
1178 synopsis = "%prog <displayname> <backup location> [options]"
1180 takes_optiongroups = {
1181 "sambaopts": options.SambaOptions,
1182 "versionopts": options.VersionOptions,
1183 "credopts": options.CredentialsOptions,
1186 takes_args = ['displayname', 'backup']
1189 Option("-H", help="LDB URL for database or target server", type=str),
1190 Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1191 Option("--entities", help="File defining XML entities to insert into DOCTYPE header", type=str)
1194 def restore_from_backup_to_local_dir(self, sourcedir, targetdir, dtd_header=''):
1195 SUFFIX = '.SAMBABACKUP'
1197 if not os.path.exists(targetdir):
1200 l_dirs = [ sourcedir ]
1201 r_dirs = [ targetdir ]
1203 l_dir = l_dirs.pop()
1204 r_dir = r_dirs.pop()
1206 dirlist = os.listdir(l_dir)
1208 l_name = os.path.join(l_dir, e)
1209 r_name = os.path.join(r_dir, e)
1211 if os.path.isdir(l_name):
1212 l_dirs.append(l_name)
1213 r_dirs.append(r_name)
1214 if not os.path.exists(r_name):
1217 if l_name.endswith('.xml'):
1218 # Restore the xml file if possible
1220 # Get the filename to find the parser
1221 to_parse = os.path.basename(l_name)[:-4]
1223 parser = find_parser(to_parse)
1225 with open(l_name, 'r') as ltemp:
1227 # Load the XML file with the DTD (entity) header
1228 parser.load_xml(ET.fromstring(dtd_header + data))
1230 # Write out the substituted files in the output
1231 # location, ready to copy over.
1232 parser.write_binary(r_name[:-4])
1234 except GPNoParserException:
1235 # In the failure case, we fallback
1236 original_file = l_name[:-4] + SUFFIX
1237 shutil.copy2(original_file, r_name[:-4])
1239 self.outf.write('WARNING: No such parser for %s\n' % to_parse)
1240 self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1243 traceback.print_exc()
1245 # In the failure case, we fallback
1246 original_file = l_name[:-4] + SUFFIX
1247 shutil.copy2(original_file, r_name[:-4])
1249 self.outf.write('WARNING: Error during parsing for %s\n' % l_name)
1250 self.outf.write('WARNING: Falling back to simple copy-restore.\n')
1252 def run(self, displayname, backup, H=None, tmpdir=None, entities=None, sambaopts=None, credopts=None,
1257 if not os.path.exists(backup):
1258 raise CommandError("Backup directory does not exist %s" % backup)
1260 if entities is not None:
1261 # DOCTYPE name is meant to match root element, but ElementTree does
1262 # not seem to care, so this seems to be enough.
1264 dtd_header = '<!DOCTYPE foobar [\n'
1266 if not os.path.exists(entities):
1267 raise CommandError("Entities file does not exist %s" %
1269 with open(entities, 'r') as entities_file:
1270 entities_content = entities_file.read()
1272 # Do a basic regex test of the entities file format
1273 if re.match('(\s*<!ENTITY\s*[a-zA-Z0-9_]+\s*.*?>)+\s*\Z',
1274 entities_content, flags=re.MULTILINE) is None:
1275 raise CommandError("Entities file does not appear to "
1276 "conform to format\n"
1277 'e.g. <!ENTITY entity "value">')
1278 dtd_header += entities_content.strip()
1280 dtd_header += '\n]>\n'
1282 super(cmd_restore, self).run(displayname, H, tmpdir, sambaopts,
1283 credopts, versionopts)
1286 # Iterate over backup files and restore with DTD
1287 self.restore_from_backup_to_local_dir(backup, self.gpodir,
1290 # Copy GPO files over SMB
1291 copy_directory_local_to_remote(self.conn, self.gpodir,
1293 ignore_existing=True)
1295 except Exception as e:
1297 traceback.print_exc()
1298 self.outf.write(str(e) + '\n')
1300 self.outf.write("Failed to restore GPO -- deleting...\n")
1302 cmd.run(self.gpo_name, H, sambaopts, credopts, versionopts)
1304 raise CommandError("Failed to restore: %s" % e)
1307 class cmd_del(Command):
1310 synopsis = "%prog <gpo> [options]"
1312 takes_optiongroups = {
1313 "sambaopts": options.SambaOptions,
1314 "versionopts": options.VersionOptions,
1315 "credopts": options.CredentialsOptions,
1318 takes_args = ['gpo']
1321 Option("-H", help="LDB URL for database or target server", type=str),
1324 def run(self, gpo, H=None, sambaopts=None, credopts=None,
1327 self.lp = sambaopts.get_loadparm()
1328 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1330 # We need to know writable DC to setup SMB connection
1331 if H and H.startswith('ldap://'):
1335 dc_hostname = netcmd_finddc(self.lp, self.creds)
1336 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1340 # Check if valid GPO
1342 msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1343 unc_path = msg['gPCFileSysPath'][0]
1345 raise CommandError("GPO '%s' does not exist" % gpo)
1347 # Connect to DC over SMB
1348 [dom_name, service, sharepath] = parse_unc(unc_path)
1350 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1351 except Exception as e:
1352 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1354 self.samdb.transaction_start()
1356 # Check for existing links
1357 msg = get_gpo_containers(self.samdb, gpo)
1360 self.outf.write("GPO %s is linked to containers\n" % gpo)
1362 del_gpo_link(self.samdb, m['dn'], gpo)
1363 self.outf.write(" Removed link from %s.\n" % m['dn'])
1365 # Remove LDAP entries
1366 gpo_dn = get_gpo_dn(self.samdb, gpo)
1367 self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1368 self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1369 self.samdb.delete(gpo_dn)
1372 conn.deltree(sharepath)
1375 self.samdb.transaction_cancel()
1378 self.samdb.transaction_commit()
1380 self.outf.write("GPO %s deleted.\n" % gpo)
1383 class cmd_aclcheck(Command):
1384 """Check all GPOs have matching LDAP and DS ACLs."""
1386 synopsis = "%prog [options]"
1388 takes_optiongroups = {
1389 "sambaopts": options.SambaOptions,
1390 "versionopts": options.VersionOptions,
1391 "credopts": options.CredentialsOptions,
1395 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1396 metavar="URL", dest="H")
1399 def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1401 self.lp = sambaopts.get_loadparm()
1402 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1404 self.url = dc_url(self.lp, self.creds, H)
1406 # We need to know writable DC to setup SMB connection
1407 if H and H.startswith('ldap://'):
1411 dc_hostname = netcmd_finddc(self.lp, self.creds)
1412 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1416 msg = get_gpo_info(self.samdb, None)
1420 unc = m['gPCFileSysPath'][0]
1422 [dom_name, service, sharepath] = parse_unc(unc)
1424 raise CommandError("Invalid GPO path (%s)" % unc)
1428 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1430 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1432 fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1434 ds_sd_ndr = m['nTSecurityDescriptor'][0]
1435 ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1437 # Create a file system security descriptor
1438 domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1439 expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1441 if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1442 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1445 class cmd_gpo(SuperCommand):
1446 """Group Policy Object (GPO) management."""
1449 subcommands["listall"] = cmd_listall()
1450 subcommands["list"] = cmd_list()
1451 subcommands["show"] = cmd_show()
1452 subcommands["getlink"] = cmd_getlink()
1453 subcommands["setlink"] = cmd_setlink()
1454 subcommands["dellink"] = cmd_dellink()
1455 subcommands["listcontainers"] = cmd_listcontainers()
1456 subcommands["getinheritance"] = cmd_getinheritance()
1457 subcommands["setinheritance"] = cmd_setinheritance()
1458 subcommands["fetch"] = cmd_fetch()
1459 subcommands["create"] = cmd_create()
1460 subcommands["del"] = cmd_del()
1461 subcommands["aclcheck"] = cmd_aclcheck()
1462 subcommands["backup"] = cmd_backup()
1463 subcommands["restore"] = cmd_restore()