3 # implement samba_tool gpo commands
5 # Copyright Andrew Tridgell 2010
6 # Copyright Giampaolo Lauria 2011 <lauria2@yahoo.com>
7 # Copyright Amitay Isaacs 2011 <amitay@gmail.com>
9 # based on C implementation by Guenther Deschner and Wilco Baan Hofman
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import samba.getopt as options
29 from samba.auth import system_session
30 from samba.netcmd import (
36 from samba.samdb import SamDB
37 from samba import drs_utils, nttime2string, dsdb, dcerpc
38 from samba.dcerpc import misc
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
48 def samdb_connect(ctx):
49 '''make a ldap connection to the server'''
51 ctx.samdb = SamDB(url=ctx.url,
52 session_info=system_session(),
53 credentials=ctx.creds, lp=ctx.lp)
55 raise CommandError("LDAP connection to %s failed " % ctx.url, e)
58 def attr_default(msg, attrname, default):
59 '''get an attribute from a ldap msg with a default'''
61 return msg[attrname][0]
65 def gpo_flags_string(value):
66 '''return gpo flags string'''
67 flags = policy.get_gpo_flags(value)
75 def gplink_options_string(value):
76 '''return gplink options string'''
77 options = policy.get_gplink_options(value)
81 ret = ' '.join(options)
85 def parse_gplink(gplink):
86 '''parse a gPLink into an array of dn and options'''
93 if len(d) != 2 or not d[0].startswith("[LDAP://"):
94 raise RuntimeError("Badly formed gPLink '%s'" % g)
95 ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
99 def encode_gplink(gplist):
100 '''Encode an array of dn and options into gPLink string'''
103 ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
107 def dc_url(lp, creds, url=None, dc=None):
108 '''If URL is not specified, return URL for writable DC.
109 If dc is provided, use that to construct ldap URL'''
114 dc = netcmd_finddc(lp, creds)
116 raise RunTimeError("Could not find a DC for domain", e)
121 def get_gpo_dn(samdb, gpo):
122 '''Construct the DN for gpo'''
124 dn = samdb.get_default_basedn()
125 dn.add_child(ldb.Dn(samdb, "CN=Policies,DC=System"))
126 dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
130 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None):
131 '''Get GPO information using gpo, displayname or dn'''
133 policies_dn = samdb.get_default_basedn()
134 policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
136 base_dn = policies_dn
137 search_expr = "(objectClass=groupPolicyContainer)"
138 search_scope = ldb.SCOPE_ONELEVEL
141 search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
143 if displayname is not None:
144 search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
148 search_scope = ldb.SCOPE_BASE
151 msg = samdb.search(base=base_dn, scope=search_scope,
152 expression=search_expr,
153 attrs=['nTSecurityDescriptor',
161 mesg = "Cannot get information for GPO %s" % gpo
163 mesg = "Cannot get information for GPOs"
164 raise CommandError(mesg, e)
170 '''Parse UNC string into a hostname, a service, and a filepath'''
171 if unc.startswith('\\\\') and unc.startswith('//'):
173 tmp = unc[2:].split('/', 2)
176 tmp = unc[2:].split('\\', 2)
182 def copy_directory_remote_to_local(conn, remotedir, localdir):
183 if not os.path.isdir(localdir):
185 r_dirs = [ remotedir ]
186 l_dirs = [ localdir ]
191 dirlist = conn.list(r_dir)
193 r_name = r_dir + '\\' + e['name']
194 l_name = os.path.join(l_dir, e['name'])
196 if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
197 r_dirs.append(r_name)
198 l_dirs.append(l_name)
201 data = conn.loadfile(r_name)
202 file(l_name, 'w').write(data)
205 def copy_directory_local_to_remote(conn, localdir, remotedir):
206 if not conn.chkpath(remotedir):
207 conn.mkdir(remotedir)
208 l_dirs = [ localdir ]
209 r_dirs = [ remotedir ]
214 dirlist = os.listdir(l_dir)
216 l_name = os.path.join(l_dir, e)
217 r_name = r_dir + '\\' + e
219 if os.path.isdir(l_name):
220 l_dirs.append(l_name)
221 r_dirs.append(r_name)
224 data = file(l_name, 'r').read()
225 conn.savefile(r_name, data)
228 def create_directory_hier(conn, remotedir):
229 elems = remotedir.replace('/', '\\').split('\\')
232 path = path + '\\' + e
233 if not conn.chkpath(path):
237 class cmd_listall(Command):
240 synopsis = "%prog gpo listall [options]"
243 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
244 metavar="URL", dest="H")
247 def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
249 self.lp = sambaopts.get_loadparm()
250 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
252 self.url = dc_url(self.lp, self.creds, H)
256 msg = get_gpo_info(self.samdb, None)
259 print("GPO : %s" % m['name'][0])
260 print("display name : %s" % m['displayName'][0])
261 print("path : %s" % m['gPCFileSysPath'][0])
262 print("dn : %s" % m.dn)
263 print("version : %s" % attr_default(m, 'versionNumber', '0'))
264 print("flags : %s" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
268 class cmd_list(Command):
269 """list GPOs for an account"""
271 synopsis = "%prog gpo list <username> [options]"
273 takes_args = [ 'username' ]
276 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
277 metavar="URL", dest="H")
280 def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
282 self.lp = sambaopts.get_loadparm()
283 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
285 self.url = dc_url(self.lp, self.creds, H)
290 msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
291 (ldb.binary_encode(username),ldb.binary_encode(username)))
294 raise CommandError("Failed to find account %s" % username, e)
296 # check if its a computer account
298 msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
299 is_computer = 'computer' in msg['objectClass']
301 raise CommandError("Failed to find objectClass for user %s" % username, e)
303 session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
304 AUTH_SESSION_INFO_AUTHENTICATED )
306 # When connecting to a remote server, don't look up the local privilege DB
307 if self.url is not None and self.url.startswith('ldap'):
308 session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
310 session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
311 session_info_flags=session_info_flags)
313 token = session.security_token
318 dn = ldb.Dn(self.samdb, str(user_dn)).parent()
320 msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
322 glist = parse_gplink(msg['gPLink'][0])
324 if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
326 if g['options'] & dsdb.GPLINK_OPT_DISABLE:
330 gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
331 attrs=['name', 'displayName', 'flags',
332 'ntSecurityDescriptor'])
334 print("Failed to fetch gpo object %s" % g['dn'])
337 secdesc_ndr = gmsg[0]['ntSecurityDescriptor'][0]
338 secdesc = ndr_unpack(dcerpc.security.descriptor, secdesc_ndr)
341 samba.security.access_check(secdesc, token,
342 dcerpc.security.SEC_STD_READ_CONTROL |
343 dcerpc.security.SEC_ADS_LIST |
344 dcerpc.security.SEC_ADS_READ_PROP)
346 print("Failed access check on %s" % msg.dn)
349 # check the flags on the GPO
350 flags = int(attr_default(gmsg[0], 'flags', 0))
351 if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
353 if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
355 gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
357 # check if this blocks inheritance
358 gpoptions = int(attr_default(msg, 'gPOptions', 0))
359 if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
362 if dn == self.samdb.get_default_basedn():
371 print("GPOs for %s %s" % (msg_str, username))
373 print(" %s %s" % (g[0], g[1]))
376 class cmd_show(Command):
377 """Show information for a GPO"""
379 synopsis = "%prog gpo show <gpo> [options]"
381 takes_optiongroups = {
382 "sambaopts": options.SambaOptions,
383 "versionopts": options.VersionOptions,
384 "credopts": options.CredentialsOptions,
387 takes_args = [ 'gpo' ]
390 Option("-H", help="LDB URL for database or target server", type=str)
393 def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
395 self.lp = sambaopts.get_loadparm()
396 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
398 self.url = dc_url(self.lp, self.creds, H)
403 msg = get_gpo_info(self.samdb, gpo)[0]
405 raise CommandError("GPO %s does not exist" % gpo, e)
407 secdesc_ndr = msg['ntSecurityDescriptor'][0]
408 secdesc = ndr_unpack(dcerpc.security.descriptor, secdesc_ndr)
410 print("GPO : %s" % msg['name'][0])
411 print("display name : %s" % msg['displayName'][0])
412 print("path : %s" % msg['gPCFileSysPath'][0])
413 print("dn : %s" % msg.dn)
414 print("version : %s" % attr_default(msg, 'versionNumber', '0'))
415 print("flags : %s" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
416 print("ACL : %s" % secdesc.as_sddl())
420 class cmd_getlink(Command):
421 """List GPO Links for a container"""
423 synopsis = "%prog gpo getlink <container_dn> [options]"
425 takes_optiongroups = {
426 "sambaopts": options.SambaOptions,
427 "versionopts": options.VersionOptions,
428 "credopts": options.CredentialsOptions,
431 takes_args = [ 'container_dn' ]
434 Option("-H", help="LDB URL for database or target server", type=str)
437 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
440 self.lp = sambaopts.get_loadparm()
441 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
443 self.url = dc_url(self.lp, self.creds, H)
448 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
449 expression="(objectClass=*)",
452 raise CommandError("Could not find Container DN %s (%s)" % container_dn, e)
455 print("GPO(s) linked to DN %s" % container_dn)
456 gplist = parse_gplink(msg['gPLink'][0])
458 msg = get_gpo_info(self.samdb, dn=g['dn'])
459 print(" GPO : %s" % msg[0]['name'][0])
460 print(" Name : %s" % msg[0]['displayName'][0])
461 print(" Options : %s" % gplink_options_string(g['options']))
464 print("No GPO(s) linked to DN=%s" % container_dn)
467 class cmd_setlink(Command):
468 """Add or Update a GPO link to a container"""
470 synopsis = "%prog gpo setlink <container_dn> <gpo> [options]"
472 takes_optiongroups = {
473 "sambaopts": options.SambaOptions,
474 "versionopts": options.VersionOptions,
475 "credopts": options.CredentialsOptions,
478 takes_args = [ 'container_dn', 'gpo' ]
481 Option("-H", help="LDB URL for database or target server", type=str),
482 Option("--disable", dest="disabled", default=False, action='store_true',
483 help="Disable policy"),
484 Option("--enforce", dest="enforced", default=False, action='store_true',
485 help="Enforce policy")
488 def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
489 sambaopts=None, credopts=None, versionopts=None):
491 self.lp = sambaopts.get_loadparm()
492 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
494 self.url = dc_url(self.lp, self.creds, H)
500 gplink_options |= dsdb.GPLINK_OPT_DISABLE
502 gplink_options |= dsdb.GPLINK_OPT_ENFORCE
504 # Check if valid GPO DN
506 msg = get_gpo_info(self.samdb, gpo=gpo)[0]
508 raise CommandError("GPO %s does not exist" % gpo_dn, e)
509 gpo_dn = get_gpo_dn(self.samdb, gpo)
511 # Check if valid Container DN
513 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
514 expression="(objectClass=*)",
517 raise CommandError("Could not find container DN %s" % container_dn, e)
519 # Update existing GPlinks or Add new one
520 existing_gplink = False
522 gplist = parse_gplink(msg['gPLink'][0])
523 existing_gplink = True
526 if g['dn'].lower() == gpo_dn.lower():
527 g['options'] = gplink_options
531 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
534 gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
536 gplink_str = encode_gplink(gplist)
539 m.dn = ldb.Dn(self.samdb, container_dn)
542 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
544 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
549 raise CommandError("Error adding GPO Link", e)
551 print("Added/Updated GPO link")
552 cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
555 class cmd_dellink(Command):
556 """Delete GPO link from a container"""
558 synopsis = "%prog gpo dellink <container_dn> <gpo> [options]"
560 takes_optiongroups = {
561 "sambaopts": options.SambaOptions,
562 "versionopts": options.VersionOptions,
563 "credopts": options.CredentialsOptions,
566 takes_args = [ 'container_dn', 'gpo' ]
569 Option("-H", help="LDB URL for database or target server", type=str),
572 def run(self, container_dn, gpo_dn, H=None, sambaopts=None, credopts=None,
575 self.lp = sambaopts.get_loadparm()
576 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
578 self.url = dc_url(self.lp, self.creds, H)
584 msg = get_gpo_info(self.sambdb, gpo=gpo)[0]
586 raise CommandError("GPO %s does not exist" % gpo, e)
587 gpo_dn = get_gpo_dn(self.samdb, gpo)
589 # Check if valid Container DN and get existing GPlinks
591 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
592 expression="(objectClass=*)",
595 raise CommandError("Could not find container DN %s" % dn, e)
598 gplist = parse_gplink(msg['gPLink'][0])
600 if g['dn'].lower() == gpo_dn.lower():
604 raise CommandError("Specified GPO is not linked to this container");
607 m.dn = ldb.Dn(self.samdb, container_dn)
610 gplink_str = encode_gplink(gplist)
611 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
613 m['new_value'] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, 'gPLink')
618 raise CommandError("Error Removing GPO Link (%s)" % e)
620 print("Deleted GPO link.")
621 cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
624 class cmd_getinheritance(Command):
625 """Get inheritance flag for a container"""
627 synopsis = "%prog gpo getinheritance <container_dn> [options]"
629 takes_optiongroups = {
630 "sambaopts": options.SambaOptions,
631 "versionopts": options.VersionOptions,
632 "credopts": options.CredentialsOptions,
635 takes_args = [ 'container_dn' ]
638 Option("-H", help="LDB URL for database or target server", type=str)
641 def run(self, container_dn, H=None, sambaopts=None, credopts=None,
645 self.lp = sambaopts.get_loadparm()
647 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
652 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
653 expression="(objectClass=*)",
654 attrs=['gPOptions'])[0]
656 raise CommandError("Could not find Container DN %s" % container_dn, e)
659 if 'gPOptions' in msg:
660 inheritance = int(msg['gPOptions'][0]);
662 if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
663 print("Container has GPO_BLOCK_INHERITANCE")
665 print("Container has GPO_INHERIT")
668 class cmd_setinheritance(Command):
669 """Set inheritance flag on a container"""
671 synopsis = "%prog gpo setinheritance <container_dn> <block|inherit> [options]"
673 takes_optiongroups = {
674 "sambaopts": options.SambaOptions,
675 "versionopts": options.VersionOptions,
676 "credopts": options.CredentialsOptions,
679 takes_args = [ 'container_dn', 'inherit_state' ]
682 Option("-H", help="LDB URL for database or target server", type=str)
685 def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
688 if inherit_state.lower() == 'block':
689 inheritance = dsdb.GPO_BLOCK_INHERITANCE
690 elif inherit_state.lower() == 'inherit':
691 inheritance = dsdb.GPO_INHERIT
693 raise CommandError("Unknown inheritance state (%s)" % inherit_state)
696 self.lp = sambaopts.get_loadparm()
698 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
703 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
704 expression="(objectClass=*)",
705 attrs=['gPOptions'])[0]
707 raise CommandError("Could not find Container DN %s" % container_dn, e)
710 m.dn = ldb.Dn(self.samdb, container_dn)
712 if 'gPOptions' in msg:
713 m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
715 m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions');
720 raise CommandError("Error setting inheritance state %s" % inherit_state, e)
723 class cmd_fetch(Command):
726 synopsis = "%prog gpo fetch <gpo> [options]"
728 takes_optiongroups = {
729 "sambaopts": options.SambaOptions,
730 "versionopts": options.VersionOptions,
731 "credopts": options.CredentialsOptions,
734 takes_args = [ 'gpo' ]
737 Option("-H", help="LDB URL for database or target server", type=str),
738 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
741 def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
743 self.lp = sambaopts.get_loadparm()
744 self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
746 dc_hostname = netcmd_finddc(self.lp, self.creds)
747 self.url = dc_url(self.lp, self.creds, H, dc=dc_hostname)
751 msg = get_gpo_info(self.samdb, gpo)[0]
753 raise CommandError("GPO %s does not exist" % gpo)
756 unc = msg['gPCFileSysPath'][0]
758 [dom_name, service, sharepath] = parse_unc(unc)
760 raise CommandError("Invalid GPO path (%s)" % unc)
764 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
766 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
771 if not os.path.isdir(tmpdir):
772 raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
774 localdir = os.path.join(tmpdir, "policy")
775 if not os.path.isdir(localdir):
778 gpodir = os.path.join(localdir, gpo)
779 if os.path.isdir(gpodir):
780 raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
784 copy_directory_remote_to_local(conn, sharepath, gpodir)
786 raise CommandError("Error copying GPO from DC", e)
787 print('GPO copied to %s' % gpodir)
790 class cmd_create(Command):
793 class cmd_setacl(Command):
794 """Set ACL on a GPO"""
797 class cmd_gpo(SuperCommand):
798 """Group Policy Object (GPO) commands"""
801 subcommands["listall"] = cmd_listall()
802 subcommands["list"] = cmd_list()
803 subcommands["show"] = cmd_show()
804 subcommands["getlink"] = cmd_getlink()
805 subcommands["setlink"] = cmd_setlink()
806 subcommands["dellink"] = cmd_dellink()
807 subcommands["getinheritance"] = cmd_getinheritance()
808 subcommands["setinheritance"] = cmd_setinheritance()
809 subcommands["fetch"] = cmd_fetch()
810 subcommands["create"] = cmd_create()
811 subcommands["setacl"] = cmd_setacl()