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
30 from samba
.auth
import system_session
31 from samba
.netcmd
import (
37 from samba
.samdb
import SamDB
38 from samba
import dsdb
39 from samba
.dcerpc
import security
40 from samba
.ndr
import ndr_unpack
, ndr_pack
41 from samba
.dcerpc
import preg
44 from samba
.auth
import AUTH_SESSION_INFO_DEFAULT_GROUPS
, AUTH_SESSION_INFO_AUTHENTICATED
, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
45 from samba
.netcmd
.common
import netcmd_finddc
46 from samba
import policy
47 from samba
.samba3
import libsmb_samba_internal
as libsmb
48 from samba
import NTSTATUSError
50 from samba
.ntacls
import dsacl2fsacl
51 from samba
.dcerpc
import nbt
52 from samba
.net
import Net
53 from samba
.gp_parse
import GPParser
, GPNoParserException
, GPGeneralizeException
54 from samba
.gp_parse
.gp_pol
import GPPolParser
55 from samba
.gp_parse
.gp_ini
import (
61 from samba
.gp_parse
.gp_csv
import GPAuditCsvParser
62 from samba
.gp_parse
.gp_inf
import GptTmplInfParser
63 from samba
.gp_parse
.gp_aas
import GPAasParser
64 from samba
import param
65 from samba
.netcmd
.common
import attr_default
66 from samba
.common
import get_bytes
, get_string
67 from configparser
import ConfigParser
68 from io
import StringIO
, BytesIO
69 from samba
.gp
.vgp_files_ext
import calc_mode
, stat_from_mode
72 from samba
.registry
import str_regtype
73 from samba
.ntstatus
import (
74 NT_STATUS_OBJECT_NAME_INVALID
,
75 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
76 NT_STATUS_OBJECT_PATH_NOT_FOUND
,
77 NT_STATUS_OBJECT_NAME_COLLISION
,
78 NT_STATUS_ACCESS_DENIED
80 from samba
.netcmd
.gpcommon
import (
81 create_directory_hier
,
85 from samba
.policies
import RegistryGroupPolicies
86 from samba
.dcerpc
.misc
import REG_MULTI_SZ
87 from samba
.gp
.gpclass
import register_gp_extension
, list_gp_extensions
, \
88 unregister_gp_extension
91 def gpo_flags_string(value
):
92 """return gpo flags string"""
93 flags
= policy
.get_gpo_flags(value
)
101 def gplink_options_string(value
):
102 """return gplink options string"""
103 options
= policy
.get_gplink_options(value
)
107 ret
= ' '.join(options
)
111 def parse_gplink(gplink
):
112 """parse a gPLink into an array of dn and options"""
115 if gplink
.strip() == '':
118 a
= gplink
.split(']')
123 if len(d
) != 2 or not d
[0].startswith("[LDAP://"):
124 raise RuntimeError("Badly formed gPLink '%s'" % g
)
125 ret
.append({'dn': d
[0][8:], 'options': int(d
[1])})
129 def encode_gplink(gplist
):
130 """Encode an array of dn and options into gPLink string"""
131 ret
= "".join("[LDAP://%s;%d]" % (g
['dn'], g
['options']) for g
in gplist
)
135 def dc_url(lp
, creds
, url
=None, dc
=None):
136 """If URL is not specified, return URL for writable DC.
137 If dc is provided, use that to construct ldap URL"""
142 dc
= netcmd_finddc(lp
, creds
)
143 except Exception as e
:
144 raise RuntimeError("Could not find a DC for domain", e
)
149 def get_gpo_info(samdb
, gpo
=None, displayname
=None, dn
=None,
150 sd_flags
=(security
.SECINFO_OWNER |
151 security
.SECINFO_GROUP |
152 security
.SECINFO_DACL |
153 security
.SECINFO_SACL
)):
154 """Get GPO information using gpo, displayname or dn"""
156 policies_dn
= samdb
.get_default_basedn()
157 policies_dn
.add_child(ldb
.Dn(samdb
, "CN=Policies,CN=System"))
159 base_dn
= policies_dn
160 search_expr
= "(objectClass=groupPolicyContainer)"
161 search_scope
= ldb
.SCOPE_ONELEVEL
164 search_expr
= "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb
.binary_encode(gpo
)
166 if displayname
is not None:
167 search_expr
= "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb
.binary_encode(displayname
)
171 search_scope
= ldb
.SCOPE_BASE
174 msg
= samdb
.search(base
=base_dn
, scope
=search_scope
,
175 expression
=search_expr
,
176 attrs
=['nTSecurityDescriptor',
182 'gPCMachineExtensionNames',
183 'gPCUserExtensionNames'],
184 controls
=['sd_flags:1:%d' % sd_flags
])
185 except Exception as e
:
187 mesg
= "Cannot get information for GPO %s" % gpo
189 mesg
= "Cannot get information for GPOs"
190 raise CommandError(mesg
, e
)
195 def get_gpo_containers(samdb
, gpo
):
196 """lists dn of containers for a GPO"""
198 search_expr
= "(&(objectClass=*)(gPLink=*%s*))" % gpo
200 msg
= samdb
.search(expression
=search_expr
, attrs
=['gPLink'])
201 except Exception as e
:
202 raise CommandError("Could not find container(s) with GPO %s" % gpo
, e
)
207 def del_gpo_link(samdb
, container_dn
, gpo
):
208 """delete GPO link for the container"""
209 # Check if valid Container DN and get existing GPlinks
211 msg
= samdb
.search(base
=container_dn
, scope
=ldb
.SCOPE_BASE
,
212 expression
="(objectClass=*)",
214 except Exception as e
:
215 raise CommandError("Container '%s' does not exist" % container_dn
, e
)
218 gpo_dn
= str(get_gpo_dn(samdb
, gpo
))
220 gplist
= parse_gplink(str(msg
['gPLink'][0]))
222 if g
['dn'].lower() == gpo_dn
.lower():
227 raise CommandError("No GPO(s) linked to this container")
230 raise CommandError("GPO '%s' not linked to this container" % gpo
)
235 gplink_str
= encode_gplink(gplist
)
236 m
['r0'] = ldb
.MessageElement(gplink_str
, ldb
.FLAG_MOD_REPLACE
, 'gPLink')
238 m
['d0'] = ldb
.MessageElement(msg
['gPLink'][0], ldb
.FLAG_MOD_DELETE
, 'gPLink')
241 except Exception as e
:
242 raise CommandError("Error removing GPO from container", e
)
246 """Parse UNC string into a hostname, a service, and a filepath"""
248 if unc
.startswith('\\\\'):
249 tmp
= unc
[2:].split('\\', 2)
250 elif unc
.startswith('//'):
251 tmp
= unc
[2:].split('/', 2)
254 raise ValueError("Invalid UNC string: %s" % unc
)
259 def find_parser(name
, flags
=re
.IGNORECASE
):
260 if re
.match(r
'fdeploy1\.ini$', name
, flags
=flags
):
261 return GPFDeploy1IniParser()
262 if re
.match(r
'audit\.csv$', name
, flags
=flags
):
263 return GPAuditCsvParser()
264 if re
.match(r
'GptTmpl\.inf$', name
, flags
=flags
):
265 return GptTmplInfParser()
266 if re
.match(r
'GPT\.INI$', name
, flags
=flags
):
267 return GPTIniParser()
268 if re
.match(r
'scripts\.ini$', name
, flags
=flags
):
269 return GPScriptsIniParser()
270 if re
.match(r
'psscripts\.ini$', name
, flags
=flags
):
271 return GPScriptsIniParser()
272 if re
.match(r
'GPE\.INI$', name
, flags
=flags
):
273 # This file does not appear in the protocol specifications!
275 # It appears to be a legacy file used to maintain gPCUserExtensionNames
276 # and gPCMachineExtensionNames. We should just copy the file as binary.
278 if re
.match(r
'.*\.ini$', name
, flags
=flags
):
280 if re
.match(r
'.*\.pol$', name
, flags
=flags
):
282 if re
.match(r
'.*\.aas$', name
, flags
=flags
):
288 def backup_directory_remote_to_local(conn
, remotedir
, localdir
):
289 SUFFIX
= '.SAMBABACKUP'
290 if not os
.path
.isdir(localdir
):
292 r_dirs
= [ remotedir
]
293 l_dirs
= [ localdir
]
298 dirlist
= conn
.list(r_dir
, attribs
=attr_flags
)
299 dirlist
.sort(key
=lambda x
: x
['name'])
301 r_name
= r_dir
+ '\\' + e
['name']
302 l_name
= os
.path
.join(l_dir
, e
['name'])
304 if e
['attrib'] & libsmb
.FILE_ATTRIBUTE_DIRECTORY
:
305 r_dirs
.append(r_name
)
306 l_dirs
.append(l_name
)
309 data
= conn
.loadfile(r_name
)
310 with
open(l_name
+ SUFFIX
, 'wb') as f
:
313 parser
= find_parser(e
['name'])
315 parser
.write_xml(l_name
+ '.xml')
318 attr_flags
= libsmb
.FILE_ATTRIBUTE_SYSTEM | \
319 libsmb
.FILE_ATTRIBUTE_DIRECTORY | \
320 libsmb
.FILE_ATTRIBUTE_ARCHIVE | \
321 libsmb
.FILE_ATTRIBUTE_HIDDEN
324 def copy_directory_remote_to_local(conn
, remotedir
, localdir
):
325 if not os
.path
.isdir(localdir
):
333 dirlist
= conn
.list(r_dir
, attribs
=attr_flags
)
334 dirlist
.sort(key
=lambda x
: x
['name'])
336 r_name
= r_dir
+ '\\' + e
['name']
337 l_name
= os
.path
.join(l_dir
, e
['name'])
339 if e
['attrib'] & libsmb
.FILE_ATTRIBUTE_DIRECTORY
:
340 r_dirs
.append(r_name
)
341 l_dirs
.append(l_name
)
344 data
= conn
.loadfile(r_name
)
345 open(l_name
, 'wb').write(data
)
348 def copy_directory_local_to_remote(conn
, localdir
, remotedir
,
349 ignore_existing_dir
=False,
350 keep_existing_files
=False):
351 if not conn
.chkpath(remotedir
):
352 conn
.mkdir(remotedir
)
359 dirlist
= os
.listdir(l_dir
)
362 l_name
= os
.path
.join(l_dir
, e
)
363 r_name
= r_dir
+ '\\' + e
365 if os
.path
.isdir(l_name
):
366 l_dirs
.append(l_name
)
367 r_dirs
.append(r_name
)
370 except NTSTATUSError
:
371 if not ignore_existing_dir
:
374 if keep_existing_files
:
376 conn
.loadfile(r_name
)
378 except NTSTATUSError
:
381 data
= open(l_name
, 'rb').read()
382 conn
.savefile(r_name
, data
)
385 class GPOCommand(Command
):
386 def construct_tmpdir(self
, tmpdir
, gpo
):
387 """Ensure that the temporary directory structure used in fetch,
388 backup, create, and restore is consistent.
390 If --tmpdir is used the named directory must be present, which may
391 contain a 'policy' subdirectory, but 'policy' must not itself have
392 a subdirectory with the gpo name. The policy and gpo directories
395 If --tmpdir is not used, a temporary directory is securely created.
398 tmpdir
= tempfile
.mkdtemp()
399 print("Using temporary directory %s (use --tmpdir to change)" % tmpdir
,
402 if not os
.path
.isdir(tmpdir
):
403 raise CommandError("Temporary directory '%s' does not exist" % tmpdir
)
405 localdir
= os
.path
.join(tmpdir
, "policy")
406 if not os
.path
.isdir(localdir
):
409 gpodir
= os
.path
.join(localdir
, gpo
)
410 if os
.path
.isdir(gpodir
):
412 "GPO directory '%s' already exists, refusing to overwrite" % gpodir
)
416 except (IOError, OSError) as e
:
417 raise CommandError("Error creating teporary GPO directory", e
)
419 return tmpdir
, gpodir
421 def samdb_connect(self
):
422 """make a ldap connection to the server"""
424 self
.samdb
= SamDB(url
=self
.url
,
425 session_info
=system_session(),
426 credentials
=self
.creds
, lp
=self
.lp
)
427 except Exception as e
:
428 raise CommandError("LDAP connection to %s failed " % self
.url
, e
)
431 class cmd_listall(GPOCommand
):
434 synopsis
= "%prog [options]"
436 takes_optiongroups
= {
437 "sambaopts": options
.SambaOptions
,
438 "versionopts": options
.VersionOptions
,
439 "credopts": options
.CredentialsOptions
,
443 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
444 metavar
="URL", dest
="H")
447 def run(self
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
449 self
.lp
= sambaopts
.get_loadparm()
450 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
452 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
456 msg
= get_gpo_info(self
.samdb
, None)
459 self
.outf
.write("GPO : %s\n" % m
['name'][0])
460 self
.outf
.write("display name : %s\n" % m
['displayName'][0])
461 self
.outf
.write("path : %s\n" % m
['gPCFileSysPath'][0])
462 self
.outf
.write("dn : %s\n" % m
.dn
)
463 self
.outf
.write("version : %s\n" % attr_default(m
, 'versionNumber', '0'))
464 self
.outf
.write("flags : %s\n" % gpo_flags_string(int(attr_default(m
, 'flags', 0))))
465 self
.outf
.write("\n")
468 class cmd_list(GPOCommand
):
469 """List GPOs for an account."""
471 synopsis
= "%prog <username|machinename> [options]"
473 takes_args
= ['accountname']
474 takes_optiongroups
= {
475 "sambaopts": options
.SambaOptions
,
476 "versionopts": options
.VersionOptions
,
477 "credopts": options
.CredentialsOptions
,
481 Option("-H", "--URL", help="LDB URL for database or target server",
482 type=str, metavar
="URL", dest
="H")
485 def run(self
, accountname
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
487 self
.lp
= sambaopts
.get_loadparm()
488 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
490 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
495 msg
= self
.samdb
.search(expression
='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
496 (ldb
.binary_encode(accountname
), ldb
.binary_encode(accountname
)))
499 raise CommandError("Failed to find account %s" % accountname
)
501 # check if its a computer account
503 msg
= self
.samdb
.search(base
=user_dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['objectClass'])[0]
504 is_computer
= 'computer' in msg
['objectClass']
506 raise CommandError("Failed to find objectClass for %s" % accountname
)
508 session_info_flags
= (AUTH_SESSION_INFO_DEFAULT_GROUPS |
509 AUTH_SESSION_INFO_AUTHENTICATED
)
511 # When connecting to a remote server, don't look up the local privilege DB
512 if self
.url
is not None and self
.url
.startswith('ldap'):
513 session_info_flags |
= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
515 session
= samba
.auth
.user_session(self
.samdb
, lp_ctx
=self
.lp
, dn
=user_dn
,
516 session_info_flags
=session_info_flags
)
518 token
= session
.security_token
523 dn
= ldb
.Dn(self
.samdb
, str(user_dn
)).parent()
525 msg
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, attrs
=['gPLink', 'gPOptions'])[0]
527 glist
= parse_gplink(str(msg
['gPLink'][0]))
529 if not inherit
and not (g
['options'] & dsdb
.GPLINK_OPT_ENFORCE
):
531 if g
['options'] & dsdb
.GPLINK_OPT_DISABLE
:
535 sd_flags
= (security
.SECINFO_OWNER |
536 security
.SECINFO_GROUP |
537 security
.SECINFO_DACL
)
538 gmsg
= self
.samdb
.search(base
=g
['dn'], scope
=ldb
.SCOPE_BASE
,
539 attrs
=['name', 'displayName', 'flags',
540 'nTSecurityDescriptor'],
541 controls
=['sd_flags:1:%d' % sd_flags
])
542 secdesc_ndr
= gmsg
[0]['nTSecurityDescriptor'][0]
543 secdesc
= ndr_unpack(security
.descriptor
, secdesc_ndr
)
545 self
.outf
.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
550 samba
.security
.access_check(secdesc
, token
,
551 security
.SEC_STD_READ_CONTROL |
552 security
.SEC_ADS_LIST |
553 security
.SEC_ADS_READ_PROP
)
555 self
.outf
.write("Failed access check on %s\n" % msg
.dn
)
558 # check the flags on the GPO
559 flags
= int(attr_default(gmsg
[0], 'flags', 0))
560 if is_computer
and (flags
& dsdb
.GPO_FLAG_MACHINE_DISABLE
):
562 if not is_computer
and (flags
& dsdb
.GPO_FLAG_USER_DISABLE
):
564 gpos
.append((gmsg
[0]['displayName'][0], gmsg
[0]['name'][0]))
566 # check if this blocks inheritance
567 gpoptions
= int(attr_default(msg
, 'gPOptions', 0))
568 if gpoptions
& dsdb
.GPO_BLOCK_INHERITANCE
:
571 if dn
== self
.samdb
.get_default_basedn():
580 self
.outf
.write("GPOs for %s %s\n" % (msg_str
, accountname
))
582 self
.outf
.write(" %s %s\n" % (g
[0], g
[1]))
585 class cmd_show(GPOCommand
):
586 """Show information for a GPO."""
588 synopsis
= "%prog <gpo> [options]"
590 takes_optiongroups
= {
591 "sambaopts": options
.SambaOptions
,
592 "versionopts": options
.VersionOptions
,
593 "credopts": options
.CredentialsOptions
,
599 Option("-H", help="LDB URL for database or target server", type=str)
602 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
604 self
.lp
= sambaopts
.get_loadparm()
605 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
607 # We need to know writable DC to setup SMB connection
608 if H
and H
.startswith('ldap://'):
612 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
613 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
618 msg
= get_gpo_info(self
.samdb
, gpo
)[0]
620 raise CommandError("GPO '%s' does not exist" % gpo
)
623 secdesc_ndr
= msg
['nTSecurityDescriptor'][0]
624 secdesc
= ndr_unpack(security
.descriptor
, secdesc_ndr
)
625 secdesc_sddl
= secdesc
.as_sddl()
627 secdesc_sddl
= "<hidden>"
629 self
.outf
.write("GPO : %s\n" % msg
['name'][0])
630 self
.outf
.write("display name : %s\n" % msg
['displayName'][0])
631 self
.outf
.write("path : %s\n" % msg
['gPCFileSysPath'][0])
632 if 'gPCMachineExtensionNames' in msg
:
633 self
.outf
.write("Machine Exts : %s\n" % msg
['gPCMachineExtensionNames'][0])
634 if 'gPCUserExtensionNames' in msg
:
635 self
.outf
.write("User Exts : %s\n" % msg
['gPCUserExtensionNames'][0])
636 self
.outf
.write("dn : %s\n" % msg
.dn
)
637 self
.outf
.write("version : %s\n" % attr_default(msg
, 'versionNumber', '0'))
638 self
.outf
.write("flags : %s\n" % gpo_flags_string(int(attr_default(msg
, 'flags', 0))))
639 self
.outf
.write("ACL : %s\n" % secdesc_sddl
)
642 conn
= smb_connection(dc_hostname
,
647 realm
= self
.lp
.get('realm')
648 pol_file
= '\\'.join([realm
.lower(), 'Policies', gpo
,
651 for policy_class
in ['MACHINE', 'USER']:
653 pol_data
= ndr_unpack(preg
.file,
654 conn
.loadfile(pol_file
% policy_class
))
655 except NTSTATUSError
as e
:
656 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
657 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
658 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
659 continue # The file doesn't exist, so there is nothing to list
660 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
661 raise CommandError("The authenticated user does "
662 "not have sufficient privileges")
665 for entry
in pol_data
.entries
:
666 if entry
.valuename
== "**delvals.":
669 defs
['keyname'] = entry
.keyname
670 defs
['valuename'] = entry
.valuename
671 defs
['class'] = policy_class
672 defs
['type'] = str_regtype(entry
.type)
673 defs
['data'] = entry
.data
674 # Bytes aren't JSON serializable
675 if type(defs
['data']) == bytes
:
676 if entry
.type == REG_MULTI_SZ
:
677 data
= defs
['data'].decode('utf-16-le')
678 defs
['data'] = data
.rstrip('\x00').split('\x00')
680 defs
['data'] = list(defs
['data'])
681 policy_defs
.append(defs
)
682 self
.outf
.write("Policies :\n")
683 json
.dump(policy_defs
, self
.outf
, indent
=4)
684 self
.outf
.write("\n")
687 class cmd_load(GPOCommand
):
688 """Load policies onto a GPO.
690 Reads json from standard input until EOF, unless a json formatted
691 file is provided via --content.
696 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
697 "valuename": "StartPage",
703 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
710 "keyname": "Software\\Microsoft\\Internet Explorer\\Toolbar",
711 "valuename": "IEToolbar",
713 "type": "REG_BINARY",
717 "keyname": "Software\\Policies\\Microsoft\\InputPersonalization",
718 "valuename": "RestrictImplicitTextCollection",
725 Valid class attributes: MACHINE|USER|BOTH
726 Data arrays are interpreted as bytes.
728 The --machine-ext-name and --user-ext-name options are multi-value inputs
729 which respectively set the gPCMachineExtensionNames and gPCUserExtensionNames
730 ldap attributes on the GPO. These attributes must be set to the correct GUID
731 names for Windows Group Policy to work correctly. These GUIDs represent
732 the client side extensions to apply on the machine. Linux Group Policy does
733 not enforce this constraint.
734 {35378EAC-683F-11D2-A89A-00C04FBBCFA2} is provided by default, which
735 enables most Registry policies.
738 synopsis
= "%prog <gpo> [options]"
740 takes_optiongroups
= {
741 "sambaopts": options
.SambaOptions
,
742 "versionopts": options
.VersionOptions
,
743 "credopts": options
.CredentialsOptions
,
749 Option("-H", help="LDB URL for database or target server", type=str),
750 Option("--content", help="JSON file of policy inputs", type=str),
751 Option("--machine-ext-name",
752 action
="append", dest
="machine_exts",
753 default
=['{35378EAC-683F-11D2-A89A-00C04FBBCFA2}'],
754 help="A machine extension name to add to gPCMachineExtensionNames"),
755 Option("--user-ext-name",
756 action
="append", dest
="user_exts",
757 default
=['{35378EAC-683F-11D2-A89A-00C04FBBCFA2}'],
758 help="A user extension name to add to gPCUserExtensionNames"),
759 Option("--replace", action
='store_true', default
=False,
760 help="Replace the existing Group Policies, rather than merging")
763 def run(self
, gpo
, H
=None, content
=None,
766 replace
=False, sambaopts
=None, credopts
=None, versionopts
=None):
767 if machine_exts
is None:
768 machine_exts
= ['{35378EAC-683F-11D2-A89A-00C04FBBCFA2}']
769 if user_exts
is None:
770 user_exts
= ['{35378EAC-683F-11D2-A89A-00C04FBBCFA2}']
772 policy_defs
= json
.loads(sys
.stdin
.read())
773 elif os
.path
.exists(content
):
774 with
open(content
, 'rb') as r
:
775 policy_defs
= json
.load(r
)
777 raise CommandError("The JSON content file does not exist")
779 self
.lp
= sambaopts
.get_loadparm()
780 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
781 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
783 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
784 for ext_name
in machine_exts
:
785 reg
.register_extension_name(ext_name
, 'gPCMachineExtensionNames')
786 for ext_name
in user_exts
:
787 reg
.register_extension_name(ext_name
, 'gPCUserExtensionNames')
790 reg
.replace_s(policy_defs
)
792 reg
.merge_s(policy_defs
)
793 except NTSTATUSError
as e
:
794 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
795 raise CommandError("The authenticated user does "
796 "not have sufficient privileges")
801 class cmd_remove(GPOCommand
):
802 """Remove policies from a GPO.
804 Reads json from standard input until EOF, unless a json formatted
805 file is provided via --content.
810 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
811 "valuename": "StartPage",
815 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
820 "keyname": "Software\\Microsoft\\Internet Explorer\\Toolbar",
821 "valuename": "IEToolbar",
825 "keyname": "Software\\Policies\\Microsoft\\InputPersonalization",
826 "valuename": "RestrictImplicitTextCollection",
831 Valid class attributes: MACHINE|USER|BOTH
834 synopsis
= "%prog <gpo> [options]"
836 takes_optiongroups
= {
837 "sambaopts": options
.SambaOptions
,
838 "versionopts": options
.VersionOptions
,
839 "credopts": options
.CredentialsOptions
,
845 Option("-H", help="LDB URL for database or target server", type=str),
846 Option("--content", help="JSON file of policy inputs", type=str),
847 Option("--machine-ext-name",
848 action
="append", default
=[], dest
="machine_exts",
849 help="A machine extension name to remove from gPCMachineExtensionNames"),
850 Option("--user-ext-name",
851 action
="append", default
=[], dest
="user_exts",
852 help="A user extension name to remove from gPCUserExtensionNames")
855 def run(self
, gpo
, H
=None, content
=None, machine_exts
=None, user_exts
=None,
856 sambaopts
=None, credopts
=None, versionopts
=None):
857 if machine_exts
is None:
859 if user_exts
is None:
862 policy_defs
= json
.loads(sys
.stdin
.read())
863 elif os
.path
.exists(content
):
864 with
open(content
, 'rb') as r
:
865 policy_defs
= json
.load(r
)
867 raise CommandError("The JSON content file does not exist")
869 self
.lp
= sambaopts
.get_loadparm()
870 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
871 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
873 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
874 for ext_name
in machine_exts
:
875 reg
.unregister_extension_name(ext_name
, 'gPCMachineExtensionNames')
876 for ext_name
in user_exts
:
877 reg
.unregister_extension_name(ext_name
, 'gPCUserExtensionNames')
879 reg
.remove_s(policy_defs
)
880 except NTSTATUSError
as e
:
881 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
882 raise CommandError("The authenticated user does "
883 "not have sufficient privileges")
888 class cmd_getlink(GPOCommand
):
889 """List GPO Links for a container."""
891 synopsis
= "%prog <container_dn> [options]"
893 takes_optiongroups
= {
894 "sambaopts": options
.SambaOptions
,
895 "versionopts": options
.VersionOptions
,
896 "credopts": options
.CredentialsOptions
,
899 takes_args
= ['container_dn']
902 Option("-H", help="LDB URL for database or target server", type=str)
905 def run(self
, container_dn
, H
=None, sambaopts
=None, credopts
=None,
908 self
.lp
= sambaopts
.get_loadparm()
909 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
911 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
916 msg
= self
.samdb
.search(base
=container_dn
, scope
=ldb
.SCOPE_BASE
,
917 expression
="(objectClass=*)",
920 raise CommandError("Container '%s' does not exist" % container_dn
)
922 if 'gPLink' in msg
and msg
['gPLink']:
923 self
.outf
.write("GPO(s) linked to DN %s\n" % container_dn
)
924 gplist
= parse_gplink(str(msg
['gPLink'][0]))
926 msg
= get_gpo_info(self
.samdb
, dn
=g
['dn'])
927 self
.outf
.write(" GPO : %s\n" % msg
[0]['name'][0])
928 self
.outf
.write(" Name : %s\n" % msg
[0]['displayName'][0])
929 self
.outf
.write(" Options : %s\n" % gplink_options_string(g
['options']))
930 self
.outf
.write("\n")
932 self
.outf
.write("No GPO(s) linked to DN=%s\n" % container_dn
)
935 class cmd_setlink(GPOCommand
):
936 """Add or update a GPO link to a container."""
938 synopsis
= "%prog <container_dn> <gpo> [options]"
940 takes_optiongroups
= {
941 "sambaopts": options
.SambaOptions
,
942 "versionopts": options
.VersionOptions
,
943 "credopts": options
.CredentialsOptions
,
946 takes_args
= ['container_dn', 'gpo']
949 Option("-H", help="LDB URL for database or target server", type=str),
950 Option("--disable", dest
="disabled", default
=False, action
='store_true',
951 help="Disable policy"),
952 Option("--enforce", dest
="enforced", default
=False, action
='store_true',
953 help="Enforce policy")
956 def run(self
, container_dn
, gpo
, H
=None, disabled
=False, enforced
=False,
957 sambaopts
=None, credopts
=None, versionopts
=None):
959 self
.lp
= sambaopts
.get_loadparm()
960 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
962 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
968 gplink_options |
= dsdb
.GPLINK_OPT_DISABLE
970 gplink_options |
= dsdb
.GPLINK_OPT_ENFORCE
972 # Check if valid GPO DN
974 get_gpo_info(self
.samdb
, gpo
=gpo
)[0]
976 raise CommandError("GPO '%s' does not exist" % gpo
)
977 gpo_dn
= str(get_gpo_dn(self
.samdb
, gpo
))
979 # Check if valid Container DN
981 msg
= self
.samdb
.search(base
=container_dn
, scope
=ldb
.SCOPE_BASE
,
982 expression
="(objectClass=*)",
985 raise CommandError("Container '%s' does not exist" % container_dn
)
987 # Update existing GPlinks or Add new one
988 existing_gplink
= False
990 gplist
= parse_gplink(str(msg
['gPLink'][0]))
991 existing_gplink
= True
994 if g
['dn'].lower() == gpo_dn
.lower():
995 g
['options'] = gplink_options
999 raise CommandError("GPO '%s' already linked to this container" % gpo
)
1001 gplist
.insert(0, {'dn': gpo_dn
, 'options': gplink_options
})
1004 gplist
.append({'dn': gpo_dn
, 'options': gplink_options
})
1006 gplink_str
= encode_gplink(gplist
)
1009 m
.dn
= ldb
.Dn(self
.samdb
, container_dn
)
1012 m
['new_value'] = ldb
.MessageElement(gplink_str
, ldb
.FLAG_MOD_REPLACE
, 'gPLink')
1014 m
['new_value'] = ldb
.MessageElement(gplink_str
, ldb
.FLAG_MOD_ADD
, 'gPLink')
1017 self
.samdb
.modify(m
)
1018 except Exception as e
:
1019 raise CommandError("Error adding GPO Link", e
)
1021 self
.outf
.write("Added/Updated GPO link\n")
1022 cmd_getlink().run(container_dn
, H
, sambaopts
, credopts
, versionopts
)
1025 class cmd_dellink(GPOCommand
):
1026 """Delete GPO link from a container."""
1028 synopsis
= "%prog <container_dn> <gpo> [options]"
1030 takes_optiongroups
= {
1031 "sambaopts": options
.SambaOptions
,
1032 "versionopts": options
.VersionOptions
,
1033 "credopts": options
.CredentialsOptions
,
1036 takes_args
= ['container', 'gpo']
1039 Option("-H", help="LDB URL for database or target server", type=str),
1042 def run(self
, container
, gpo
, H
=None, sambaopts
=None, credopts
=None,
1045 self
.lp
= sambaopts
.get_loadparm()
1046 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1048 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
1050 self
.samdb_connect()
1052 # Check if valid GPO
1054 get_gpo_info(self
.samdb
, gpo
=gpo
)[0]
1056 raise CommandError("GPO '%s' does not exist" % gpo
)
1058 container_dn
= ldb
.Dn(self
.samdb
, container
)
1059 del_gpo_link(self
.samdb
, container_dn
, gpo
)
1060 self
.outf
.write("Deleted GPO link.\n")
1061 cmd_getlink().run(container_dn
, H
, sambaopts
, credopts
, versionopts
)
1064 class cmd_listcontainers(GPOCommand
):
1065 """List all linked containers for a GPO."""
1067 synopsis
= "%prog <gpo> [options]"
1069 takes_optiongroups
= {
1070 "sambaopts": options
.SambaOptions
,
1071 "versionopts": options
.VersionOptions
,
1072 "credopts": options
.CredentialsOptions
,
1075 takes_args
= ['gpo']
1078 Option("-H", help="LDB URL for database or target server", type=str)
1081 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None,
1084 self
.lp
= sambaopts
.get_loadparm()
1085 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1087 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
1089 self
.samdb_connect()
1091 msg
= get_gpo_containers(self
.samdb
, gpo
)
1093 self
.outf
.write("Container(s) using GPO %s\n" % gpo
)
1095 self
.outf
.write(" DN: %s\n" % m
['dn'])
1097 self
.outf
.write("No Containers using GPO %s\n" % gpo
)
1100 class cmd_getinheritance(GPOCommand
):
1101 """Get inheritance flag for a container."""
1103 synopsis
= "%prog <container_dn> [options]"
1105 takes_optiongroups
= {
1106 "sambaopts": options
.SambaOptions
,
1107 "versionopts": options
.VersionOptions
,
1108 "credopts": options
.CredentialsOptions
,
1111 takes_args
= ['container_dn']
1114 Option("-H", help="LDB URL for database or target server", type=str)
1117 def run(self
, container_dn
, H
=None, sambaopts
=None, credopts
=None,
1120 self
.lp
= sambaopts
.get_loadparm()
1121 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1123 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
1125 self
.samdb_connect()
1128 msg
= self
.samdb
.search(base
=container_dn
, scope
=ldb
.SCOPE_BASE
,
1129 expression
="(objectClass=*)",
1130 attrs
=['gPOptions'])[0]
1132 raise CommandError("Container '%s' does not exist" % container_dn
)
1135 if 'gPOptions' in msg
:
1136 inheritance
= int(msg
['gPOptions'][0])
1138 if inheritance
== dsdb
.GPO_BLOCK_INHERITANCE
:
1139 self
.outf
.write("Container has GPO_BLOCK_INHERITANCE\n")
1141 self
.outf
.write("Container has GPO_INHERIT\n")
1144 class cmd_setinheritance(GPOCommand
):
1145 """Set inheritance flag on a container."""
1147 synopsis
= "%prog <container_dn> <block|inherit> [options]"
1149 takes_optiongroups
= {
1150 "sambaopts": options
.SambaOptions
,
1151 "versionopts": options
.VersionOptions
,
1152 "credopts": options
.CredentialsOptions
,
1155 takes_args
= ['container_dn', 'inherit_state']
1158 Option("-H", help="LDB URL for database or target server", type=str)
1161 def run(self
, container_dn
, inherit_state
, H
=None, sambaopts
=None, credopts
=None,
1164 if inherit_state
.lower() == 'block':
1165 inheritance
= dsdb
.GPO_BLOCK_INHERITANCE
1166 elif inherit_state
.lower() == 'inherit':
1167 inheritance
= dsdb
.GPO_INHERIT
1169 raise CommandError("Unknown inheritance state (%s)" % inherit_state
)
1171 self
.lp
= sambaopts
.get_loadparm()
1172 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1174 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
1176 self
.samdb_connect()
1178 msg
= self
.samdb
.search(base
=container_dn
, scope
=ldb
.SCOPE_BASE
,
1179 expression
="(objectClass=*)",
1180 attrs
=['gPOptions'])[0]
1182 raise CommandError("Container '%s' does not exist" % container_dn
)
1185 m
.dn
= ldb
.Dn(self
.samdb
, container_dn
)
1187 if 'gPOptions' in msg
:
1188 m
['new_value'] = ldb
.MessageElement(str(inheritance
), ldb
.FLAG_MOD_REPLACE
, 'gPOptions')
1190 m
['new_value'] = ldb
.MessageElement(str(inheritance
), ldb
.FLAG_MOD_ADD
, 'gPOptions')
1193 self
.samdb
.modify(m
)
1194 except Exception as e
:
1195 raise CommandError("Error setting inheritance state %s" % inherit_state
, e
)
1198 class cmd_fetch(GPOCommand
):
1199 """Download a GPO."""
1201 synopsis
= "%prog <gpo> [options]"
1203 takes_optiongroups
= {
1204 "sambaopts": options
.SambaOptions
,
1205 "versionopts": options
.VersionOptions
,
1206 "credopts": options
.CredentialsOptions
,
1209 takes_args
= ['gpo']
1212 Option("-H", help="LDB URL for database or target server", type=str),
1213 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1216 def run(self
, gpo
, H
=None, tmpdir
=None, sambaopts
=None, credopts
=None, versionopts
=None):
1218 self
.lp
= sambaopts
.get_loadparm()
1219 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1221 # We need to know writable DC to setup SMB connection
1222 if H
and H
.startswith('ldap://'):
1226 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1227 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1229 self
.samdb_connect()
1231 msg
= get_gpo_info(self
.samdb
, gpo
)[0]
1233 raise CommandError("GPO '%s' does not exist" % gpo
)
1236 unc
= str(msg
['gPCFileSysPath'][0])
1238 [dom_name
, service
, sharepath
] = parse_unc(unc
)
1240 raise CommandError("Invalid GPO path (%s)" % unc
)
1243 conn
= smb_connection(dc_hostname
, service
, lp
=self
.lp
,
1247 tmpdir
, gpodir
= self
.construct_tmpdir(tmpdir
, gpo
)
1250 copy_directory_remote_to_local(conn
, sharepath
, gpodir
)
1251 except Exception as e
:
1252 # FIXME: Catch more specific exception
1253 raise CommandError("Error copying GPO from DC", e
)
1254 self
.outf
.write('GPO copied to %s\n' % gpodir
)
1257 class cmd_backup(GPOCommand
):
1260 synopsis
= "%prog <gpo> [options]"
1262 takes_optiongroups
= {
1263 "sambaopts": options
.SambaOptions
,
1264 "versionopts": options
.VersionOptions
,
1265 "credopts": options
.CredentialsOptions
,
1268 takes_args
= ['gpo']
1271 Option("-H", help="LDB URL for database or target server", type=str),
1272 Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1273 Option("--generalize", help="Generalize XML entities to restore",
1274 default
=False, action
='store_true'),
1275 Option("--entities", help="File to export defining XML entities for the restore",
1276 dest
='ent_file', type=str)
1279 def run(self
, gpo
, H
=None, tmpdir
=None, generalize
=False, sambaopts
=None,
1280 credopts
=None, versionopts
=None, ent_file
=None):
1282 self
.lp
= sambaopts
.get_loadparm()
1283 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1285 # We need to know writable DC to setup SMB connection
1286 if H
and H
.startswith('ldap://'):
1290 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1291 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1293 self
.samdb_connect()
1295 msg
= get_gpo_info(self
.samdb
, gpo
)[0]
1297 raise CommandError("GPO '%s' does not exist" % gpo
)
1300 unc
= str(msg
['gPCFileSysPath'][0])
1302 [dom_name
, service
, sharepath
] = parse_unc(unc
)
1304 raise CommandError("Invalid GPO path (%s)" % unc
)
1307 conn
= smb_connection(dc_hostname
, service
, lp
=self
.lp
,
1311 tmpdir
, gpodir
= self
.construct_tmpdir(tmpdir
, gpo
)
1314 backup_directory_remote_to_local(conn
, sharepath
, gpodir
)
1315 except Exception as e
:
1316 # FIXME: Catch more specific exception
1317 raise CommandError("Error copying GPO from DC", e
)
1319 self
.outf
.write('GPO copied to %s\n' % gpodir
)
1322 self
.outf
.write('\nAttempting to generalize XML entities:\n')
1323 entities
= cmd_backup
.generalize_xml_entities(self
.outf
, gpodir
,
1326 ents
= "".join('<!ENTITY {} "{}\n">'.format(ent
[1].strip('&;'), ent
[0]) \
1327 for ent
in sorted(entities
.items(), key
=operator
.itemgetter(1)))
1330 with
open(ent_file
, 'w') as f
:
1332 self
.outf
.write('Entities successfully written to %s\n' %
1335 self
.outf
.write('\nEntities:\n')
1336 self
.outf
.write(ents
)
1338 # Backup the enabled GPO extension names
1339 for ext
in ('gPCMachineExtensionNames', 'gPCUserExtensionNames'):
1341 with
open(os
.path
.join(gpodir
, ext
+ '.SAMBAEXT'), 'wb') as f
:
1342 f
.write(msg
[ext
][0])
1345 def generalize_xml_entities(outf
, sourcedir
, targetdir
):
1348 if not os
.path
.exists(targetdir
):
1351 l_dirs
= [ sourcedir
]
1352 r_dirs
= [ targetdir
]
1354 l_dir
= l_dirs
.pop()
1355 r_dir
= r_dirs
.pop()
1357 dirlist
= os
.listdir(l_dir
)
1360 l_name
= os
.path
.join(l_dir
, e
)
1361 r_name
= os
.path
.join(r_dir
, e
)
1363 if os
.path
.isdir(l_name
):
1364 l_dirs
.append(l_name
)
1365 r_dirs
.append(r_name
)
1366 if not os
.path
.exists(r_name
):
1369 if l_name
.endswith('.xml'):
1370 # Restore the xml file if possible
1372 # Get the filename to find the parser
1373 to_parse
= os
.path
.basename(l_name
)[:-4]
1375 parser
= find_parser(to_parse
)
1377 with
open(l_name
, 'r') as ltemp
:
1380 concrete_xml
= ET
.fromstring(data
)
1381 found_entities
= parser
.generalize_xml(concrete_xml
, r_name
, entities
)
1382 except GPGeneralizeException
:
1383 outf
.write('SKIPPING: Generalizing failed for %s\n' % to_parse
)
1386 # No need to generalize non-xml files.
1388 # TODO This could be improved with xml files stored in
1389 # the renamed backup file (with custom extension) by
1390 # inlining them into the exported backups.
1391 if not os
.path
.samefile(l_name
, r_name
):
1392 shutil
.copy2(l_name
, r_name
)
1397 class cmd_create(GPOCommand
):
1398 """Create an empty GPO."""
1400 synopsis
= "%prog <displayname> [options]"
1402 takes_optiongroups
= {
1403 "sambaopts": options
.SambaOptions
,
1404 "versionopts": options
.VersionOptions
,
1405 "credopts": options
.CredentialsOptions
,
1408 takes_args
= ['displayname']
1411 Option("-H", help="LDB URL for database or target server", type=str),
1412 Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
1415 def run(self
, displayname
, H
=None, tmpdir
=None, sambaopts
=None, credopts
=None,
1418 self
.lp
= sambaopts
.get_loadparm()
1419 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1421 net
= Net(creds
=self
.creds
, lp
=self
.lp
)
1423 # We need to know writable DC to setup SMB connection
1424 if H
and H
.startswith('ldap://'):
1427 flags
= (nbt
.NBT_SERVER_LDAP |
1429 nbt
.NBT_SERVER_WRITABLE
)
1430 cldap_ret
= net
.finddc(address
=dc_hostname
, flags
=flags
)
1432 flags
= (nbt
.NBT_SERVER_LDAP |
1434 nbt
.NBT_SERVER_WRITABLE
)
1435 cldap_ret
= net
.finddc(domain
=self
.lp
.get('realm'), flags
=flags
)
1436 dc_hostname
= cldap_ret
.pdc_dns_name
1437 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1439 self
.samdb_connect()
1441 msg
= get_gpo_info(self
.samdb
, displayname
=displayname
)
1443 raise CommandError("A GPO already existing with name '%s'" % displayname
)
1446 guid
= str(uuid
.uuid4())
1447 gpo
= "{%s}" % guid
.upper()
1451 realm
= cldap_ret
.dns_domain
1452 unc_path
= "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm
, realm
, gpo
)
1455 self
.tmpdir
, gpodir
= self
.construct_tmpdir(tmpdir
, gpo
)
1456 self
.gpodir
= gpodir
1459 os
.mkdir(os
.path
.join(gpodir
, "Machine"))
1460 os
.mkdir(os
.path
.join(gpodir
, "User"))
1461 gpt_contents
= "[General]\r\nVersion=0\r\n"
1462 open(os
.path
.join(gpodir
, "GPT.INI"), "w").write(gpt_contents
)
1463 except Exception as e
:
1464 raise CommandError("Error Creating GPO files", e
)
1466 # Connect to DC over SMB
1467 [dom_name
, service
, sharepath
] = parse_unc(unc_path
)
1468 self
.sharepath
= sharepath
1469 conn
= smb_connection(dc_hostname
, service
, lp
=self
.lp
,
1474 self
.samdb
.transaction_start()
1477 gpo_dn
= get_gpo_dn(self
.samdb
, gpo
)
1481 m
['a01'] = ldb
.MessageElement("groupPolicyContainer", ldb
.FLAG_MOD_ADD
, "objectClass")
1484 # Add cn=User,cn=<guid>
1486 m
.dn
= ldb
.Dn(self
.samdb
, "CN=User,%s" % str(gpo_dn
))
1487 m
['a01'] = ldb
.MessageElement("container", ldb
.FLAG_MOD_ADD
, "objectClass")
1490 # Add cn=Machine,cn=<guid>
1492 m
.dn
= ldb
.Dn(self
.samdb
, "CN=Machine,%s" % str(gpo_dn
))
1493 m
['a01'] = ldb
.MessageElement("container", ldb
.FLAG_MOD_ADD
, "objectClass")
1496 # Get new security descriptor
1497 ds_sd_flags
= (security
.SECINFO_OWNER |
1498 security
.SECINFO_GROUP |
1499 security
.SECINFO_DACL
)
1500 msg
= get_gpo_info(self
.samdb
, gpo
=gpo
, sd_flags
=ds_sd_flags
)[0]
1501 ds_sd_ndr
= msg
['nTSecurityDescriptor'][0]
1502 ds_sd
= ndr_unpack(security
.descriptor
, ds_sd_ndr
).as_sddl()
1504 # Create a file system security descriptor
1505 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1506 sddl
= dsacl2fsacl(ds_sd
, domain_sid
)
1507 fs_sd
= security
.descriptor
.from_sddl(sddl
, domain_sid
)
1509 # Copy GPO directory
1510 create_directory_hier(conn
, sharepath
)
1513 sio
= (security
.SECINFO_OWNER |
1514 security
.SECINFO_GROUP |
1515 security
.SECINFO_DACL |
1516 security
.SECINFO_PROTECTED_DACL
)
1517 conn
.set_acl(sharepath
, fs_sd
, sio
)
1519 # Copy GPO files over SMB
1520 copy_directory_local_to_remote(conn
, gpodir
, sharepath
)
1524 m
['a02'] = ldb
.MessageElement(displayname
, ldb
.FLAG_MOD_REPLACE
, "displayName")
1525 m
['a03'] = ldb
.MessageElement(unc_path
, ldb
.FLAG_MOD_REPLACE
, "gPCFileSysPath")
1526 m
['a05'] = ldb
.MessageElement("0", ldb
.FLAG_MOD_REPLACE
, "versionNumber")
1527 m
['a07'] = ldb
.MessageElement("2", ldb
.FLAG_MOD_REPLACE
, "gpcFunctionalityVersion")
1528 m
['a04'] = ldb
.MessageElement("0", ldb
.FLAG_MOD_REPLACE
, "flags")
1529 controls
= ["permissive_modify:0"]
1530 self
.samdb
.modify(m
, controls
=controls
)
1532 self
.samdb
.transaction_cancel()
1535 self
.samdb
.transaction_commit()
1538 # Without --tmpdir, we created one in /tmp/. It must go.
1539 shutil
.rmtree(self
.tmpdir
)
1541 self
.outf
.write("GPO '%s' created as %s\n" % (displayname
, gpo
))
1544 class cmd_restore(cmd_create
):
1545 """Restore a GPO to a new container."""
1547 synopsis
= "%prog <displayname> <backup location> [options]"
1549 takes_optiongroups
= {
1550 "sambaopts": options
.SambaOptions
,
1551 "versionopts": options
.VersionOptions
,
1552 "credopts": options
.CredentialsOptions
,
1555 takes_args
= ['displayname', 'backup']
1558 Option("-H", help="LDB URL for database or target server", type=str),
1559 Option("--tmpdir", help="Temporary directory for copying policy files", type=str),
1560 Option("--entities", help="File defining XML entities to insert into DOCTYPE header", type=str),
1561 Option("--restore-metadata", help="Keep the old GPT.INI file and associated version number",
1562 default
=False, action
="store_true")
1565 def restore_from_backup_to_local_dir(self
, sourcedir
, targetdir
, dtd_header
=''):
1566 SUFFIX
= '.SAMBABACKUP'
1568 if not os
.path
.exists(targetdir
):
1571 l_dirs
= [ sourcedir
]
1572 r_dirs
= [ targetdir
]
1574 l_dir
= l_dirs
.pop()
1575 r_dir
= r_dirs
.pop()
1577 dirlist
= os
.listdir(l_dir
)
1580 l_name
= os
.path
.join(l_dir
, e
)
1581 r_name
= os
.path
.join(r_dir
, e
)
1583 if os
.path
.isdir(l_name
):
1584 l_dirs
.append(l_name
)
1585 r_dirs
.append(r_name
)
1586 if not os
.path
.exists(r_name
):
1589 if l_name
.endswith('.xml'):
1590 # Restore the xml file if possible
1592 # Get the filename to find the parser
1593 to_parse
= os
.path
.basename(l_name
)[:-4]
1595 parser
= find_parser(to_parse
)
1597 with
open(l_name
, 'r') as ltemp
:
1599 xml_head
= '<?xml version="1.0" encoding="utf-8"?>'
1601 if data
.startswith(xml_head
):
1602 # It appears that sometimes the DTD rejects
1603 # the xml header being after it.
1604 data
= data
[len(xml_head
):]
1606 # Load the XML file with the DTD (entity) header
1607 parser
.load_xml(ET
.fromstring(xml_head
+ dtd_header
+ data
))
1609 parser
.load_xml(ET
.fromstring(dtd_header
+ data
))
1611 # Write out the substituted files in the output
1612 # location, ready to copy over.
1613 parser
.write_binary(r_name
[:-4])
1615 except GPNoParserException
:
1616 # In the failure case, we fallback
1617 original_file
= l_name
[:-4] + SUFFIX
1618 shutil
.copy2(original_file
, r_name
[:-4])
1620 self
.outf
.write('WARNING: No such parser for %s\n' % to_parse
)
1621 self
.outf
.write('WARNING: Falling back to simple copy-restore.\n')
1624 traceback
.print_exc()
1626 # In the failure case, we fallback
1627 original_file
= l_name
[:-4] + SUFFIX
1628 shutil
.copy2(original_file
, r_name
[:-4])
1630 self
.outf
.write('WARNING: Error during parsing for %s\n' % l_name
)
1631 self
.outf
.write('WARNING: Falling back to simple copy-restore.\n')
1633 def run(self
, displayname
, backup
, H
=None, tmpdir
=None, entities
=None, sambaopts
=None, credopts
=None,
1634 versionopts
=None, restore_metadata
=None):
1638 if not os
.path
.exists(backup
):
1639 raise CommandError("Backup directory does not exist %s" % backup
)
1641 if entities
is not None:
1642 # DOCTYPE name is meant to match root element, but ElementTree does
1643 # not seem to care, so this seems to be enough.
1645 dtd_header
= '<!DOCTYPE foobar [\n'
1647 if not os
.path
.exists(entities
):
1648 raise CommandError("Entities file does not exist %s" %
1650 with
open(entities
, 'r') as entities_file
:
1651 entities_content
= entities_file
.read()
1653 # Do a basic regex test of the entities file format
1654 if re
.match(r
'(\s*<!ENTITY\s*[a-zA-Z0-9_]+\s*.*?>)+\s*\Z',
1655 entities_content
, flags
=re
.MULTILINE
) is None:
1656 raise CommandError("Entities file does not appear to "
1657 "conform to format\n"
1658 'e.g. <!ENTITY entity "value">')
1659 dtd_header
+= entities_content
.strip()
1661 dtd_header
+= '\n]>\n'
1663 super().run(displayname
, H
, tmpdir
, sambaopts
, credopts
, versionopts
)
1668 self
.tmpdir
, gpodir
= self
.construct_tmpdir(tmpdir
, self
.gpo_name
)
1669 self
.gpodir
= gpodir
1671 # Iterate over backup files and restore with DTD
1672 self
.restore_from_backup_to_local_dir(backup
, self
.gpodir
,
1675 keep_new_files
= not restore_metadata
1677 # Copy GPO files over SMB
1678 copy_directory_local_to_remote(self
.conn
, self
.gpodir
,
1680 ignore_existing_dir
=True,
1681 keep_existing_files
=keep_new_files
)
1683 gpo_dn
= get_gpo_dn(self
.samdb
, self
.gpo_name
)
1685 # Restore the enabled extensions
1686 for ext
in ('gPCMachineExtensionNames', 'gPCUserExtensionNames'):
1687 ext_file
= os
.path
.join(backup
, ext
+ '.SAMBAEXT')
1688 if os
.path
.exists(ext_file
):
1689 with
open(ext_file
, 'rb') as f
:
1694 m
[ext
] = ldb
.MessageElement(data
, ldb
.FLAG_MOD_REPLACE
,
1697 self
.samdb
.modify(m
)
1700 # Without --tmpdir, we created one in /tmp/. It must go.
1701 shutil
.rmtree(self
.tmpdir
)
1703 except Exception as e
:
1705 traceback
.print_exc()
1706 self
.outf
.write(str(e
) + '\n')
1708 self
.outf
.write("Failed to restore GPO -- deleting...\n")
1710 cmd
.run(self
.gpo_name
, H
, sambaopts
, credopts
, versionopts
)
1712 raise CommandError("Failed to restore: %s" % e
)
1715 class cmd_del(GPOCommand
):
1718 synopsis
= "%prog <gpo> [options]"
1720 takes_optiongroups
= {
1721 "sambaopts": options
.SambaOptions
,
1722 "versionopts": options
.VersionOptions
,
1723 "credopts": options
.CredentialsOptions
,
1726 takes_args
= ['gpo']
1729 Option("-H", help="LDB URL for database or target server", type=str),
1732 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None,
1735 self
.lp
= sambaopts
.get_loadparm()
1736 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1738 # We need to know writable DC to setup SMB connection
1739 if H
and H
.startswith('ldap://'):
1743 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1744 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1746 self
.samdb_connect()
1748 # Check if valid GPO
1750 msg
= get_gpo_info(self
.samdb
, gpo
=gpo
)[0]
1751 unc_path
= str(msg
['gPCFileSysPath'][0])
1753 raise CommandError("GPO '%s' does not exist" % gpo
)
1755 # Connect to DC over SMB
1756 [dom_name
, service
, sharepath
] = parse_unc(unc_path
)
1757 conn
= smb_connection(dc_hostname
, service
, lp
=self
.lp
,
1760 self
.samdb
.transaction_start()
1762 # Check for existing links
1763 msg
= get_gpo_containers(self
.samdb
, gpo
)
1766 self
.outf
.write("GPO %s is linked to containers\n" % gpo
)
1768 del_gpo_link(self
.samdb
, m
['dn'], gpo
)
1769 self
.outf
.write(" Removed link from %s.\n" % m
['dn'])
1771 # Remove LDAP entries
1772 gpo_dn
= get_gpo_dn(self
.samdb
, gpo
)
1773 self
.samdb
.delete(ldb
.Dn(self
.samdb
, "CN=User,%s" % str(gpo_dn
)))
1774 self
.samdb
.delete(ldb
.Dn(self
.samdb
, "CN=Machine,%s" % str(gpo_dn
)))
1775 self
.samdb
.delete(gpo_dn
)
1778 conn
.deltree(sharepath
)
1781 self
.samdb
.transaction_cancel()
1784 self
.samdb
.transaction_commit()
1786 self
.outf
.write("GPO %s deleted.\n" % gpo
)
1789 class cmd_aclcheck(GPOCommand
):
1790 """Check all GPOs have matching LDAP and DS ACLs."""
1792 synopsis
= "%prog [options]"
1794 takes_optiongroups
= {
1795 "sambaopts": options
.SambaOptions
,
1796 "versionopts": options
.VersionOptions
,
1797 "credopts": options
.CredentialsOptions
,
1801 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1802 metavar
="URL", dest
="H")
1805 def run(self
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
1807 self
.lp
= sambaopts
.get_loadparm()
1808 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1810 self
.url
= dc_url(self
.lp
, self
.creds
, H
)
1812 # We need to know writable DC to setup SMB connection
1813 if H
and H
.startswith('ldap://'):
1817 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1818 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1820 self
.samdb_connect()
1822 msg
= get_gpo_info(self
.samdb
, None)
1826 unc
= str(m
['gPCFileSysPath'][0])
1828 [dom_name
, service
, sharepath
] = parse_unc(unc
)
1830 raise CommandError("Invalid GPO path (%s)" % unc
)
1833 conn
= smb_connection(dc_hostname
, service
, lp
=self
.lp
,
1836 fs_sd
= conn
.get_acl(sharepath
, security
.SECINFO_OWNER | security
.SECINFO_GROUP | security
.SECINFO_DACL
, security
.SEC_FLAG_MAXIMUM_ALLOWED
)
1838 if 'nTSecurityDescriptor' not in m
:
1839 raise CommandError("Could not read nTSecurityDescriptor. "
1840 "This requires an Administrator account")
1842 ds_sd_ndr
= m
['nTSecurityDescriptor'][0]
1843 ds_sd
= ndr_unpack(security
.descriptor
, ds_sd_ndr
).as_sddl()
1845 # Create a file system security descriptor
1846 domain_sid
= security
.dom_sid(self
.samdb
.get_domain_sid())
1847 expected_fs_sddl
= dsacl2fsacl(ds_sd
, domain_sid
)
1849 if (fs_sd
.as_sddl(domain_sid
) != expected_fs_sddl
):
1850 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd
.as_sddl(domain_sid
), sharepath
, expected_fs_sddl
))
1852 class cmd_admxload(Command
):
1853 """Loads samba admx files to sysvol"""
1855 synopsis
= "%prog [options]"
1857 takes_optiongroups
= {
1858 "sambaopts": options
.SambaOptions
,
1859 "versionopts": options
.VersionOptions
,
1860 "credopts": options
.CredentialsOptions
,
1864 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1865 metavar
="URL", dest
="H"),
1866 Option("--admx-dir", help="Directory where admx templates are stored",
1867 type=str, default
=os
.path
.join(param
.data_dir(), 'samba/admx'))
1870 def run(self
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None,
1872 self
.lp
= sambaopts
.get_loadparm()
1873 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1875 # We need to know writable DC to setup SMB connection
1876 if H
and H
.startswith('ldap://'):
1880 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1881 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1884 conn
= smb_connection(dc_hostname
,
1889 smb_dir
= '\\'.join([self
.lp
.get('realm').lower(),
1890 'Policies', 'PolicyDefinitions'])
1893 except NTSTATUSError
as e
:
1894 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
1895 raise CommandError("The authenticated user does "
1896 "not have sufficient privileges")
1897 elif e
.args
[0] != NT_STATUS_OBJECT_NAME_COLLISION
:
1900 for dirname
, dirs
, files
in os
.walk(admx_dir
):
1902 path_in_admx
= dirname
.replace(admx_dir
, '')
1903 full_path
= os
.path
.join(dirname
, fname
)
1904 sub_dir
= '\\'.join([smb_dir
, path_in_admx
]).replace('/', '\\')
1905 smb_path
= '\\'.join([sub_dir
, fname
])
1907 create_directory_hier(conn
, sub_dir
)
1908 except NTSTATUSError
as e
:
1909 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
1910 raise CommandError("The authenticated user does "
1911 "not have sufficient privileges")
1912 elif e
.args
[0] != NT_STATUS_OBJECT_NAME_COLLISION
:
1914 with
open(full_path
, 'rb') as f
:
1916 conn
.savefile(smb_path
, f
.read())
1917 except NTSTATUSError
as e
:
1918 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
1919 raise CommandError("The authenticated user does "
1920 "not have sufficient privileges")
1921 self
.outf
.write('Installing ADMX templates to the Central Store '
1922 'prevents Windows from displaying its own templates '
1923 'in the Group Policy Management Console. You will '
1924 'need to install these templates '
1925 'from https://www.microsoft.com/en-us/download/102157 '
1926 'to continue using Windows Administrative Templates.\n')
1928 class cmd_add_sudoers(GPOCommand
):
1929 """Adds a Samba Sudoers Group Policy to the sysvol
1931 This command adds a sudo rule to the sysvol for applying to winbind clients.
1933 The command argument indicates the final field in the sudo rule.
1934 The user argument indicates the user specified in the parentheses.
1935 The users and groups arguments are comma separated lists, which are combined to
1936 form the first field in the sudo rule.
1937 The --passwd argument specifies whether the sudo entry will require a password
1938 be specified. The default is False, meaning the NOPASSWD field will be
1939 specified in the sudo entry.
1942 samba-tool gpo manage sudoers add {31B2F340-016D-11D2-945F-00C04FB984F9} ALL ALL fakeu fakeg
1944 The example command will generate the following sudoers entry:
1945 fakeu,fakeg% ALL=(ALL) NOPASSWD: ALL
1948 synopsis
= "%prog <gpo> <command> <user> <users> [groups] [options]"
1950 takes_optiongroups
= {
1951 "sambaopts": options
.SambaOptions
,
1952 "versionopts": options
.VersionOptions
,
1953 "credopts": options
.CredentialsOptions
,
1957 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1958 metavar
="URL", dest
="H"),
1959 Option("--passwd", action
='store_true', default
=False,
1960 help="Specify to indicate that sudo entry must provide a password")
1963 takes_args
= ["gpo", "command", "user", "users", "groups?"]
1965 def run(self
, gpo
, command
, user
, users
, groups
=None, passwd
=None,
1966 H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
1967 self
.lp
= sambaopts
.get_loadparm()
1968 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
1970 # We need to know writable DC to setup SMB connection
1971 if H
and H
.startswith('ldap://'):
1975 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
1976 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
1979 conn
= smb_connection(dc_hostname
,
1984 self
.samdb_connect()
1985 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
1987 realm
= self
.lp
.get('realm')
1988 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
1989 'MACHINE\\VGP\\VTLA\\Sudo',
1990 'SudoersConfiguration'])
1991 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
1993 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
1994 policysetting
= xml_data
.getroot().find('policysetting')
1995 data
= policysetting
.find('data')
1996 except NTSTATUSError
as e
:
1997 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
1998 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
1999 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2000 # The file doesn't exist, so create the xml structure
2001 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
2002 policysetting
= ET
.SubElement(xml_data
.getroot(),
2004 pv
= ET
.SubElement(policysetting
, 'version')
2006 name
= ET
.SubElement(policysetting
, 'name')
2007 name
.text
= 'Sudo Policy'
2008 description
= ET
.SubElement(policysetting
, 'description')
2009 description
.text
= 'Sudoers File Configuration Policy'
2010 apply_mode
= ET
.SubElement(policysetting
, 'apply_mode')
2011 apply_mode
.text
= 'merge'
2012 data
= ET
.SubElement(policysetting
, 'data')
2013 load_plugin
= ET
.SubElement(data
, 'load_plugin')
2014 load_plugin
.text
= 'true'
2015 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2016 raise CommandError("The authenticated user does "
2017 "not have sufficient privileges")
2021 sudoers_entry
= ET
.SubElement(data
, 'sudoers_entry')
2023 ET
.SubElement(sudoers_entry
, 'password')
2024 command_elm
= ET
.SubElement(sudoers_entry
, 'command')
2025 command_elm
.text
= command
2026 user_elm
= ET
.SubElement(sudoers_entry
, 'user')
2027 user_elm
.text
= user
2028 listelement
= ET
.SubElement(sudoers_entry
, 'listelement')
2029 for u
in users
.split(','):
2030 principal
= ET
.SubElement(listelement
, 'principal')
2032 principal
.attrib
['type'] = 'user'
2033 if groups
is not None:
2034 for g
in groups
.split():
2035 principal
= ET
.SubElement(listelement
, 'principal')
2037 principal
.attrib
['type'] = 'group'
2040 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
2043 create_directory_hier(conn
, vgp_dir
)
2044 conn
.savefile(vgp_xml
, out
.read())
2045 reg
.increment_gpt_ini(machine_changed
=True)
2046 except NTSTATUSError
as e
:
2047 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2048 raise CommandError("The authenticated user does "
2049 "not have sufficient privileges")
2052 class cmd_list_sudoers(Command
):
2053 """List Samba Sudoers Group Policy from the sysvol
2055 This command lists sudo rules from the sysvol that will be applied to winbind clients.
2058 samba-tool gpo manage sudoers list {31B2F340-016D-11D2-945F-00C04FB984F9}
2061 synopsis
= "%prog <gpo> [options]"
2063 takes_optiongroups
= {
2064 "sambaopts": options
.SambaOptions
,
2065 "versionopts": options
.VersionOptions
,
2066 "credopts": options
.CredentialsOptions
,
2070 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2071 metavar
="URL", dest
="H"),
2074 takes_args
= ["gpo"]
2076 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2077 self
.lp
= sambaopts
.get_loadparm()
2078 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2080 # We need to know writable DC to setup SMB connection
2081 if H
and H
.startswith('ldap://'):
2085 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2086 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2089 conn
= smb_connection(dc_hostname
,
2094 realm
= self
.lp
.get('realm')
2095 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2096 'MACHINE\\VGP\\VTLA\\Sudo',
2097 'SudoersConfiguration\\manifest.xml'])
2099 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
2100 except NTSTATUSError
as e
:
2101 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2102 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2103 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2104 # The file doesn't exist, so there is nothing to list
2106 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2107 raise CommandError("The authenticated user does "
2108 "not have sufficient privileges")
2112 if xml_data
is not None:
2113 policy
= xml_data
.find('policysetting')
2114 data
= policy
.find('data')
2115 for entry
in data
.findall('sudoers_entry'):
2116 command
= entry
.find('command').text
2117 user
= entry
.find('user').text
2118 listelements
= entry
.findall('listelement')
2120 for listelement
in listelements
:
2121 principals
.extend(listelement
.findall('principal'))
2122 if len(principals
) > 0:
2123 uname
= ','.join([u
.text
if u
.attrib
['type'] == 'user' \
2124 else '%s%%' % u
.text
for u
in principals
])
2127 nopassword
= entry
.find('password') is None
2128 np_entry
= ' NOPASSWD:' if nopassword
else ''
2129 p
= '%s ALL=(%s)%s %s' % (uname
, user
, np_entry
, command
)
2130 self
.outf
.write('%s\n' % p
)
2132 pol_file
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2133 'MACHINE\\Registry.pol'])
2135 pol_data
= ndr_unpack(preg
.file, conn
.loadfile(pol_file
))
2136 except NTSTATUSError
as e
:
2137 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2138 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2139 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2140 return # The file doesn't exist, so there is nothing to list
2141 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2142 raise CommandError("The authenticated user does "
2143 "not have sufficient privileges")
2146 # Also list the policies set from the GPME
2147 keyname
= b
'Software\\Policies\\Samba\\Unix Settings\\Sudo Rights'
2148 for entry
in pol_data
.entries
:
2149 if get_bytes(entry
.keyname
) == keyname
and \
2150 get_string(entry
.data
).strip():
2151 self
.outf
.write('%s\n' % entry
.data
)
2153 class cmd_remove_sudoers(GPOCommand
):
2154 """Removes a Samba Sudoers Group Policy from the sysvol
2156 This command removes a sudo rule from the sysvol from applying to winbind clients.
2159 samba-tool gpo manage sudoers remove {31B2F340-016D-11D2-945F-00C04FB984F9} 'fakeu ALL=(ALL) NOPASSWD: ALL'
2162 synopsis
= "%prog <gpo> <entry> [options]"
2164 takes_optiongroups
= {
2165 "sambaopts": options
.SambaOptions
,
2166 "versionopts": options
.VersionOptions
,
2167 "credopts": options
.CredentialsOptions
,
2171 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2172 metavar
="URL", dest
="H"),
2175 takes_args
= ["gpo", "entry"]
2177 def run(self
, gpo
, entry
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2178 self
.lp
= sambaopts
.get_loadparm()
2179 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2181 # We need to know writable DC to setup SMB connection
2182 if H
and H
.startswith('ldap://'):
2186 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2187 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2190 conn
= smb_connection(dc_hostname
,
2195 self
.samdb_connect()
2196 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
2198 realm
= self
.lp
.get('realm')
2199 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2200 'MACHINE\\VGP\\VTLA\\Sudo',
2201 'SudoersConfiguration'])
2202 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
2204 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
2205 policysetting
= xml_data
.getroot().find('policysetting')
2206 data
= policysetting
.find('data')
2207 except NTSTATUSError
as e
:
2208 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2209 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2210 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2212 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2213 raise CommandError("The authenticated user does "
2214 "not have sufficient privileges")
2218 pol_file
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2219 'MACHINE\\Registry.pol'])
2221 pol_data
= ndr_unpack(preg
.file, conn
.loadfile(pol_file
))
2222 except NTSTATUSError
as e
:
2223 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2224 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2225 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2227 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2228 raise CommandError("The authenticated user does "
2229 "not have sufficient privileges")
2234 for e
in data
.findall('sudoers_entry') if data
else []:
2235 command
= e
.find('command').text
2236 user
= e
.find('user').text
2237 listelements
= e
.findall('listelement')
2239 for listelement
in listelements
:
2240 principals
.extend(listelement
.findall('principal'))
2241 if len(principals
) > 0:
2242 uname
= ','.join([u
.text
if u
.attrib
['type'] == 'user' \
2243 else '%s%%' % u
.text
for u
in principals
])
2246 nopassword
= e
.find('password') is None
2247 np_entry
= ' NOPASSWD:' if nopassword
else ''
2248 p
= '%s ALL=(%s)%s %s' % (uname
, user
, np_entry
, command
)
2251 if entry
in entries
.keys():
2252 data
.remove(entries
[entry
])
2255 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
2258 create_directory_hier(conn
, vgp_dir
)
2259 conn
.savefile(vgp_xml
, out
.read())
2260 reg
.increment_gpt_ini(machine_changed
=True)
2261 except NTSTATUSError
as e
:
2262 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2263 raise CommandError("The authenticated user does "
2264 "not have sufficient privileges")
2266 elif entry
in ([e
.data
for e
in pol_data
.entries
] if pol_data
else []):
2267 entries
= [e
for e
in pol_data
.entries
if e
.data
!= entry
]
2268 pol_data
.num_entries
= len(entries
)
2269 pol_data
.entries
= entries
2272 conn
.savefile(pol_file
, ndr_pack(pol_data
))
2273 reg
.increment_gpt_ini(machine_changed
=True)
2274 except NTSTATUSError
as e
:
2275 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2276 raise CommandError("The authenticated user does "
2277 "not have sufficient privileges")
2280 raise CommandError("Cannot remove '%s' because it does not exist" %
2283 class cmd_sudoers(SuperCommand
):
2284 """Manage Sudoers Group Policy Objects"""
2286 subcommands
["add"] = cmd_add_sudoers()
2287 subcommands
["list"] = cmd_list_sudoers()
2288 subcommands
["remove"] = cmd_remove_sudoers()
2290 class cmd_set_security(GPOCommand
):
2291 """Set Samba Security Group Policy to the sysvol
2293 This command sets a security setting to the sysvol for applying to winbind
2294 clients. Not providing a value will unset the policy.
2295 These settings only apply to the ADDC.
2298 samba-tool gpo manage security set {31B2F340-016D-11D2-945F-00C04FB984F9} MaxTicketAge 10
2301 MaxTicketAge Maximum lifetime for user ticket
2304 MaxServiceAge Maximum lifetime for service ticket
2307 MaxRenewAge Maximum lifetime for user ticket renewal
2310 MinimumPasswordAge Minimum password age
2313 MaximumPasswordAge Maximum password age
2316 MinimumPasswordLength Minimum password length
2317 Defined in characters
2319 PasswordComplexity Password must meet complexity requirements
2320 1 is Enabled, 0 is Disabled
2323 synopsis
= "%prog <gpo> [options]"
2325 takes_optiongroups
= {
2326 "sambaopts": options
.SambaOptions
,
2327 "versionopts": options
.VersionOptions
,
2328 "credopts": options
.CredentialsOptions
,
2332 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2333 metavar
="URL", dest
="H"),
2336 takes_args
= ["gpo", "policy", "value?"]
2338 def run(self
, gpo
, policy
, value
=None, H
=None, sambaopts
=None,
2339 credopts
=None, versionopts
=None):
2340 self
.lp
= sambaopts
.get_loadparm()
2341 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2343 # We need to know writable DC to setup SMB connection
2344 if H
and H
.startswith('ldap://'):
2348 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2349 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2352 conn
= smb_connection(dc_hostname
,
2357 self
.samdb_connect()
2358 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
2360 realm
= self
.lp
.get('realm')
2361 inf_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2362 'MACHINE\\Microsoft\\Windows NT\\SecEdit'])
2363 inf_file
= '\\'.join([inf_dir
, 'GptTmpl.inf'])
2365 inf_data
= ConfigParser(interpolation
=None)
2366 inf_data
.optionxform
=str
2367 raw
= conn
.loadfile(inf_file
)
2369 inf_data
.read_file(StringIO(raw
.decode()))
2370 except UnicodeDecodeError:
2371 inf_data
.read_file(StringIO(raw
.decode('utf-16')))
2372 except NTSTATUSError
as e
:
2373 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2374 raise CommandError("The authenticated user does "
2375 "not have sufficient privileges")
2376 if e
.args
[0] not in [NT_STATUS_OBJECT_NAME_INVALID
,
2377 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2378 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2381 section_map
= { 'MaxTicketAge' : 'Kerberos Policy',
2382 'MaxServiceAge' : 'Kerberos Policy',
2383 'MaxRenewAge' : 'Kerberos Policy',
2384 'MinimumPasswordAge' : 'System Access',
2385 'MaximumPasswordAge' : 'System Access',
2386 'MinimumPasswordLength' : 'System Access',
2387 'PasswordComplexity' : 'System Access'
2390 section
= section_map
[policy
]
2391 if not inf_data
.has_section(section
):
2392 inf_data
.add_section(section
)
2393 if value
is not None:
2394 inf_data
.set(section
, policy
, value
)
2396 inf_data
.remove_option(section
, policy
)
2397 if len(inf_data
.options(section
)) == 0:
2398 inf_data
.remove_section(section
)
2403 create_directory_hier(conn
, inf_dir
)
2404 conn
.savefile(inf_file
, get_bytes(out
.getvalue()))
2405 reg
.increment_gpt_ini(machine_changed
=True)
2406 except NTSTATUSError
as e
:
2407 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2408 raise CommandError("The authenticated user does "
2409 "not have sufficient privileges")
2413 class cmd_list_security(Command
):
2414 """List Samba Security Group Policy from the sysvol
2416 This command lists security settings from the sysvol that will be applied to winbind clients.
2417 These settings only apply to the ADDC.
2420 samba-tool gpo manage security list {31B2F340-016D-11D2-945F-00C04FB984F9}
2423 synopsis
= "%prog <gpo> [options]"
2425 takes_optiongroups
= {
2426 "sambaopts": options
.SambaOptions
,
2427 "versionopts": options
.VersionOptions
,
2428 "credopts": options
.CredentialsOptions
,
2432 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2433 metavar
="URL", dest
="H"),
2436 takes_args
= ["gpo"]
2438 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2439 self
.lp
= sambaopts
.get_loadparm()
2440 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2442 # We need to know writable DC to setup SMB connection
2443 if H
and H
.startswith('ldap://'):
2447 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2448 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2451 conn
= smb_connection(dc_hostname
,
2456 realm
= self
.lp
.get('realm')
2457 inf_file
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2458 'MACHINE\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf'])
2460 inf_data
= ConfigParser(interpolation
=None)
2461 inf_data
.optionxform
=str
2462 raw
= conn
.loadfile(inf_file
)
2464 inf_data
.read_file(StringIO(raw
.decode()))
2465 except UnicodeDecodeError:
2466 inf_data
.read_file(StringIO(raw
.decode('utf-16')))
2467 except NTSTATUSError
as e
:
2468 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2469 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2470 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2471 return # The file doesn't exist, so there is nothing to list
2472 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2473 raise CommandError("The authenticated user does "
2474 "not have sufficient privileges")
2477 for section
in inf_data
.sections():
2478 if section
not in ['Kerberos Policy', 'System Access']:
2480 for key
, value
in inf_data
.items(section
):
2481 self
.outf
.write('%s = %s\n' % (key
, value
))
2483 class cmd_security(SuperCommand
):
2484 """Manage Security Group Policy Objects"""
2486 subcommands
["set"] = cmd_set_security()
2487 subcommands
["list"] = cmd_list_security()
2489 class cmd_list_smb_conf(Command
):
2490 """List Samba smb.conf Group Policy from the sysvol
2492 This command lists smb.conf settings from the sysvol that will be applied to winbind clients.
2495 samba-tool gpo manage smb_conf list {31B2F340-016D-11D2-945F-00C04FB984F9}
2498 synopsis
= "%prog <gpo> [options]"
2500 takes_optiongroups
= {
2501 "sambaopts": options
.SambaOptions
,
2502 "versionopts": options
.VersionOptions
,
2503 "credopts": options
.CredentialsOptions
,
2507 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2508 metavar
="URL", dest
="H"),
2511 takes_args
= ["gpo"]
2513 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2514 self
.lp
= sambaopts
.get_loadparm()
2515 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2517 # We need to know writable DC to setup SMB connection
2518 if H
and H
.startswith('ldap://'):
2522 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2523 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2526 conn
= smb_connection(dc_hostname
,
2531 realm
= self
.lp
.get('realm')
2532 pol_file
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2533 'MACHINE\\Registry.pol'])
2535 pol_data
= ndr_unpack(preg
.file, conn
.loadfile(pol_file
))
2536 except NTSTATUSError
as e
:
2537 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2538 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2539 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2540 return # The file doesn't exist, so there is nothing to list
2541 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2542 raise CommandError("The authenticated user does "
2543 "not have sufficient privileges")
2546 keyname
= b
'Software\\Policies\\Samba\\smb_conf'
2547 lp
= param
.LoadParm()
2548 for entry
in pol_data
.entries
:
2549 if get_bytes(entry
.keyname
) == keyname
:
2550 lp
.set(entry
.valuename
, str(entry
.data
))
2551 val
= lp
.get(entry
.valuename
)
2552 self
.outf
.write('%s = %s\n' % (entry
.valuename
, val
))
2554 class cmd_set_smb_conf(GPOCommand
):
2555 """Sets a Samba smb.conf Group Policy to the sysvol
2557 This command sets an smb.conf setting to the sysvol for applying to winbind
2558 clients. Not providing a value will unset the policy.
2561 samba-tool gpo manage smb_conf set {31B2F340-016D-11D2-945F-00C04FB984F9} 'apply gpo policies' yes
2564 synopsis
= "%prog <gpo> <entry> [options]"
2566 takes_optiongroups
= {
2567 "sambaopts": options
.SambaOptions
,
2568 "versionopts": options
.VersionOptions
,
2569 "credopts": options
.CredentialsOptions
,
2573 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2574 metavar
="URL", dest
="H"),
2577 takes_args
= ["gpo", "setting", "value?"]
2579 def run(self
, gpo
, setting
, value
=None, H
=None, sambaopts
=None, credopts
=None,
2581 self
.lp
= sambaopts
.get_loadparm()
2582 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2584 # We need to know writable DC to setup SMB connection
2585 if H
and H
.startswith('ldap://'):
2589 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2590 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2593 conn
= smb_connection(dc_hostname
,
2598 self
.samdb_connect()
2599 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
2601 realm
= self
.lp
.get('realm')
2602 pol_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
, 'MACHINE'])
2603 pol_file
= '\\'.join([pol_dir
, 'Registry.pol'])
2605 pol_data
= ndr_unpack(preg
.file, conn
.loadfile(pol_file
))
2606 except NTSTATUSError
as e
:
2607 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2608 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2609 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2610 pol_data
= preg
.file() # The file doesn't exist
2611 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2612 raise CommandError("The authenticated user does "
2613 "not have sufficient privileges")
2618 if setting
not in [e
.valuename
for e
in pol_data
.entries
]:
2619 raise CommandError("Cannot remove '%s' because it does "
2620 "not exist" % setting
)
2621 entries
= [e
for e
in pol_data
.entries \
2622 if e
.valuename
!= setting
]
2623 pol_data
.entries
= entries
2624 pol_data
.num_entries
= len(entries
)
2626 if get_string(value
).lower() in ['yes', 'true', '1']:
2629 elif get_string(value
).lower() in ['no', 'false', '0']:
2632 elif get_string(value
).isnumeric():
2634 val
= int(get_string(value
))
2637 val
= get_bytes(value
)
2639 e
.keyname
= b
'Software\\Policies\\Samba\\smb_conf'
2640 e
.valuename
= get_bytes(setting
)
2643 entries
= list(pol_data
.entries
)
2645 pol_data
.entries
= entries
2646 pol_data
.num_entries
= len(entries
)
2649 create_directory_hier(conn
, pol_dir
)
2650 conn
.savefile(pol_file
, ndr_pack(pol_data
))
2651 reg
.increment_gpt_ini(machine_changed
=True)
2652 except NTSTATUSError
as e
:
2653 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2654 raise CommandError("The authenticated user does "
2655 "not have sufficient privileges")
2658 class cmd_smb_conf(SuperCommand
):
2659 """Manage smb.conf Group Policy Objects"""
2661 subcommands
["list"] = cmd_list_smb_conf()
2662 subcommands
["set"] = cmd_set_smb_conf()
2664 class cmd_list_symlink(Command
):
2665 """List VGP Symbolic Link Group Policy from the sysvol
2667 This command lists symlink settings from the sysvol that will be applied to winbind clients.
2670 samba-tool gpo manage symlink list {31B2F340-016D-11D2-945F-00C04FB984F9}
2673 synopsis
= "%prog <gpo> [options]"
2675 takes_optiongroups
= {
2676 "sambaopts": options
.SambaOptions
,
2677 "versionopts": options
.VersionOptions
,
2678 "credopts": options
.CredentialsOptions
,
2682 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2683 metavar
="URL", dest
="H"),
2686 takes_args
= ["gpo"]
2688 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2689 self
.lp
= sambaopts
.get_loadparm()
2690 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2692 # We need to know writable DC to setup SMB connection
2693 if H
and H
.startswith('ldap://'):
2697 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2698 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2701 conn
= smb_connection(dc_hostname
,
2706 realm
= self
.lp
.get('realm')
2707 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2708 'MACHINE\\VGP\\VTLA\\Unix',
2709 'Symlink\\manifest.xml'])
2711 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
2712 except NTSTATUSError
as e
:
2713 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2714 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2715 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2716 return # The file doesn't exist, so there is nothing to list
2717 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2718 raise CommandError("The authenticated user does "
2719 "not have sufficient privileges")
2722 policy
= xml_data
.find('policysetting')
2723 data
= policy
.find('data')
2724 for file_properties
in data
.findall('file_properties'):
2725 source
= file_properties
.find('source')
2726 target
= file_properties
.find('target')
2727 self
.outf
.write('ln -s %s %s\n' % (source
.text
, target
.text
))
2729 class cmd_add_symlink(GPOCommand
):
2730 """Adds a VGP Symbolic Link Group Policy to the sysvol
2732 This command adds a symlink setting to the sysvol that will be applied to winbind clients.
2735 samba-tool gpo manage symlink add {31B2F340-016D-11D2-945F-00C04FB984F9} /tmp/source /tmp/target
2738 synopsis
= "%prog <gpo> <source> <target> [options]"
2740 takes_optiongroups
= {
2741 "sambaopts": options
.SambaOptions
,
2742 "versionopts": options
.VersionOptions
,
2743 "credopts": options
.CredentialsOptions
,
2747 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2748 metavar
="URL", dest
="H"),
2751 takes_args
= ["gpo", "source", "target"]
2753 def run(self
, gpo
, source
, target
, H
=None, sambaopts
=None, credopts
=None,
2755 self
.lp
= sambaopts
.get_loadparm()
2756 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2758 # We need to know writable DC to setup SMB connection
2759 if H
and H
.startswith('ldap://'):
2763 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2764 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2767 conn
= smb_connection(dc_hostname
,
2772 self
.samdb_connect()
2773 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
2775 realm
= self
.lp
.get('realm')
2776 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2777 'MACHINE\\VGP\\VTLA\\Unix\\Symlink'])
2778 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
2780 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
2781 policy
= xml_data
.getroot().find('policysetting')
2782 data
= policy
.find('data')
2783 except NTSTATUSError
as e
:
2784 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2785 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2786 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2787 # The file doesn't exist, so create the xml structure
2788 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
2789 policysetting
= ET
.SubElement(xml_data
.getroot(),
2791 pv
= ET
.SubElement(policysetting
, 'version')
2793 name
= ET
.SubElement(policysetting
, 'name')
2794 name
.text
= 'Symlink Policy'
2795 description
= ET
.SubElement(policysetting
, 'description')
2796 description
.text
= 'Specifies symbolic link data'
2797 data
= ET
.SubElement(policysetting
, 'data')
2798 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2799 raise CommandError("The authenticated user does "
2800 "not have sufficient privileges")
2804 file_properties
= ET
.SubElement(data
, 'file_properties')
2805 source_elm
= ET
.SubElement(file_properties
, 'source')
2806 source_elm
.text
= source
2807 target_elm
= ET
.SubElement(file_properties
, 'target')
2808 target_elm
.text
= target
2811 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
2814 create_directory_hier(conn
, vgp_dir
)
2815 conn
.savefile(vgp_xml
, out
.read())
2816 reg
.increment_gpt_ini(machine_changed
=True)
2817 except NTSTATUSError
as e
:
2818 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2819 raise CommandError("The authenticated user does "
2820 "not have sufficient privileges")
2823 class cmd_remove_symlink(GPOCommand
):
2824 """Removes a VGP Symbolic Link Group Policy from the sysvol
2826 This command removes a symlink setting from the sysvol from applying to winbind
2830 samba-tool gpo manage symlink remove {31B2F340-016D-11D2-945F-00C04FB984F9} /tmp/source /tmp/target
2833 synopsis
= "%prog <gpo> <source> <target> [options]"
2835 takes_optiongroups
= {
2836 "sambaopts": options
.SambaOptions
,
2837 "versionopts": options
.VersionOptions
,
2838 "credopts": options
.CredentialsOptions
,
2842 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2843 metavar
="URL", dest
="H"),
2846 takes_args
= ["gpo", "source", "target"]
2848 def run(self
, gpo
, source
, target
, H
=None, sambaopts
=None, credopts
=None,
2850 self
.lp
= sambaopts
.get_loadparm()
2851 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2853 # We need to know writable DC to setup SMB connection
2854 if H
and H
.startswith('ldap://'):
2858 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2859 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2862 conn
= smb_connection(dc_hostname
,
2867 self
.samdb_connect()
2868 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
2870 realm
= self
.lp
.get('realm')
2871 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2872 'MACHINE\\VGP\\VTLA\\Unix\\Symlink'])
2873 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
2875 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
2876 policy
= xml_data
.getroot().find('policysetting')
2877 data
= policy
.find('data')
2878 except NTSTATUSError
as e
:
2879 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2880 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2881 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2882 raise CommandError("Cannot remove link from '%s' to '%s' "
2883 "because it does not exist" % source
, target
)
2884 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2885 raise CommandError("The authenticated user does "
2886 "not have sufficient privileges")
2890 for file_properties
in data
.findall('file_properties'):
2891 source_elm
= file_properties
.find('source')
2892 target_elm
= file_properties
.find('target')
2893 if source_elm
.text
== source
and target_elm
.text
== target
:
2894 data
.remove(file_properties
)
2897 raise CommandError("Cannot remove link from '%s' to '%s' "
2898 "because it does not exist" % source
, target
)
2902 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
2905 create_directory_hier(conn
, vgp_dir
)
2906 conn
.savefile(vgp_xml
, out
.read())
2907 reg
.increment_gpt_ini(machine_changed
=True)
2908 except NTSTATUSError
as e
:
2909 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2910 raise CommandError("The authenticated user does "
2911 "not have sufficient privileges")
2914 class cmd_symlink(SuperCommand
):
2915 """Manage symlink Group Policy Objects"""
2917 subcommands
["list"] = cmd_list_symlink()
2918 subcommands
["add"] = cmd_add_symlink()
2919 subcommands
["remove"] = cmd_remove_symlink()
2921 class cmd_list_files(Command
):
2922 """List VGP Files Group Policy from the sysvol
2924 This command lists files which will be copied from the sysvol and applied to winbind clients.
2927 samba-tool gpo manage files list {31B2F340-016D-11D2-945F-00C04FB984F9}
2930 synopsis
= "%prog <gpo> [options]"
2932 takes_optiongroups
= {
2933 "sambaopts": options
.SambaOptions
,
2934 "versionopts": options
.VersionOptions
,
2935 "credopts": options
.CredentialsOptions
,
2939 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
2940 metavar
="URL", dest
="H"),
2943 takes_args
= ["gpo"]
2945 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
2946 self
.lp
= sambaopts
.get_loadparm()
2947 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
2949 # We need to know writable DC to setup SMB connection
2950 if H
and H
.startswith('ldap://'):
2954 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
2955 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
2958 conn
= smb_connection(dc_hostname
,
2963 realm
= self
.lp
.get('realm')
2964 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
2965 'MACHINE\\VGP\\VTLA\\Unix',
2966 'Files\\manifest.xml'])
2968 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
2969 except NTSTATUSError
as e
:
2970 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
2971 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
2972 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
2973 return # The file doesn't exist, so there is nothing to list
2974 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
2975 raise CommandError("The authenticated user does "
2976 "not have sufficient privileges")
2979 policy
= xml_data
.find('policysetting')
2980 data
= policy
.find('data')
2981 for entry
in data
.findall('file_properties'):
2982 source
= entry
.find('source').text
2983 target
= entry
.find('target').text
2984 user
= entry
.find('user').text
2985 group
= entry
.find('group').text
2986 mode
= calc_mode(entry
)
2987 p
= '%s\t%s\t%s\t%s -> %s' % \
2988 (stat_from_mode(mode
), user
, group
, target
, source
)
2989 self
.outf
.write('%s\n' % p
)
2991 class cmd_add_files(GPOCommand
):
2992 """Add VGP Files Group Policy to the sysvol
2994 This command adds files which will be copied from the sysvol and applied to winbind clients.
2997 samba-tool gpo manage files add {31B2F340-016D-11D2-945F-00C04FB984F9} ./source.txt /usr/share/doc/target.txt root root 600
3000 synopsis
= "%prog <gpo> <source> <target> <user> <group> <mode> [options]"
3002 takes_optiongroups
= {
3003 "sambaopts": options
.SambaOptions
,
3004 "versionopts": options
.VersionOptions
,
3005 "credopts": options
.CredentialsOptions
,
3009 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3010 metavar
="URL", dest
="H"),
3013 takes_args
= ["gpo", "source", "target", "user", "group", "mode"]
3015 def run(self
, gpo
, source
, target
, user
, group
, mode
, H
=None,
3016 sambaopts
=None, credopts
=None, versionopts
=None):
3017 self
.lp
= sambaopts
.get_loadparm()
3018 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3020 if not os
.path
.exists(source
):
3021 raise CommandError("Source '%s' does not exist" % source
)
3023 # We need to know writable DC to setup SMB connection
3024 if H
and H
.startswith('ldap://'):
3028 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3029 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3032 conn
= smb_connection(dc_hostname
,
3037 self
.samdb_connect()
3038 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3040 realm
= self
.lp
.get('realm')
3041 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3042 'MACHINE\\VGP\\VTLA\\Unix\\Files'])
3043 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3045 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
3046 policy
= xml_data
.getroot().find('policysetting')
3047 data
= policy
.find('data')
3048 except NTSTATUSError
as e
:
3049 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3050 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3051 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3052 # The file doesn't exist, so create the xml structure
3053 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
3054 policysetting
= ET
.SubElement(xml_data
.getroot(),
3056 pv
= ET
.SubElement(policysetting
, 'version')
3058 name
= ET
.SubElement(policysetting
, 'name')
3060 description
= ET
.SubElement(policysetting
, 'description')
3061 description
.text
= 'Represents file data to set/copy on clients'
3062 data
= ET
.SubElement(policysetting
, 'data')
3063 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3064 raise CommandError("The authenticated user does "
3065 "not have sufficient privileges")
3069 file_properties
= ET
.SubElement(data
, 'file_properties')
3070 source_elm
= ET
.SubElement(file_properties
, 'source')
3071 source_elm
.text
= os
.path
.basename(source
)
3072 target_elm
= ET
.SubElement(file_properties
, 'target')
3073 target_elm
.text
= target
3074 user_elm
= ET
.SubElement(file_properties
, 'user')
3075 user_elm
.text
= user
3076 group_elm
= ET
.SubElement(file_properties
, 'group')
3077 group_elm
.text
= group
3078 for ptype
, shift
in [('user', 6), ('group', 3), ('other', 0)]:
3079 permissions
= ET
.SubElement(file_properties
, 'permissions')
3080 permissions
.set('type', ptype
)
3081 if int(mode
, 8) & (0o4 << shift
):
3082 ET
.SubElement(permissions
, 'read')
3083 if int(mode
, 8) & (0o2 << shift
):
3084 ET
.SubElement(permissions
, 'write')
3085 if int(mode
, 8) & (0o1 << shift
):
3086 ET
.SubElement(permissions
, 'execute')
3089 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3091 source_data
= open(source
, 'rb').read()
3092 sysvol_source
= '\\'.join([vgp_dir
, os
.path
.basename(source
)])
3094 create_directory_hier(conn
, vgp_dir
)
3095 conn
.savefile(vgp_xml
, out
.read())
3096 conn
.savefile(sysvol_source
, source_data
)
3097 reg
.increment_gpt_ini(machine_changed
=True)
3098 except NTSTATUSError
as e
:
3099 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3100 raise CommandError("The authenticated user does "
3101 "not have sufficient privileges")
3104 class cmd_remove_files(GPOCommand
):
3105 """Remove VGP Files Group Policy from the sysvol
3107 This command removes files which would be copied from the sysvol and applied to winbind clients.
3110 samba-tool gpo manage files remove {31B2F340-016D-11D2-945F-00C04FB984F9} /usr/share/doc/target.txt
3113 synopsis
= "%prog <gpo> <target> [options]"
3115 takes_optiongroups
= {
3116 "sambaopts": options
.SambaOptions
,
3117 "versionopts": options
.VersionOptions
,
3118 "credopts": options
.CredentialsOptions
,
3122 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3123 metavar
="URL", dest
="H"),
3126 takes_args
= ["gpo", "target"]
3128 def run(self
, gpo
, target
, H
=None, sambaopts
=None, credopts
=None,
3130 self
.lp
= sambaopts
.get_loadparm()
3131 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3133 # We need to know writable DC to setup SMB connection
3134 if H
and H
.startswith('ldap://'):
3138 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3139 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3142 conn
= smb_connection(dc_hostname
,
3147 self
.samdb_connect()
3148 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3150 realm
= self
.lp
.get('realm')
3151 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3152 'MACHINE\\VGP\\VTLA\\Unix\\Files'])
3153 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3155 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
3156 policy
= xml_data
.getroot().find('policysetting')
3157 data
= policy
.find('data')
3158 except NTSTATUSError
as e
:
3159 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3160 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3161 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3162 raise CommandError("Cannot remove file '%s' "
3163 "because it does not exist" % target
)
3164 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3165 raise CommandError("The authenticated user does "
3166 "not have sufficient privileges")
3170 for file_properties
in data
.findall('file_properties'):
3171 source_elm
= file_properties
.find('source')
3172 target_elm
= file_properties
.find('target')
3173 if target_elm
.text
== target
:
3174 source
= '\\'.join([vgp_dir
, source_elm
.text
])
3176 data
.remove(file_properties
)
3179 raise CommandError("Cannot remove file '%s' "
3180 "because it does not exist" % target
)
3184 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3187 create_directory_hier(conn
, vgp_dir
)
3188 conn
.savefile(vgp_xml
, out
.read())
3189 reg
.increment_gpt_ini(machine_changed
=True)
3190 except NTSTATUSError
as e
:
3191 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3192 raise CommandError("The authenticated user does "
3193 "not have sufficient privileges")
3196 class cmd_files(SuperCommand
):
3197 """Manage Files Group Policy Objects"""
3199 subcommands
["list"] = cmd_list_files()
3200 subcommands
["add"] = cmd_add_files()
3201 subcommands
["remove"] = cmd_remove_files()
3203 class cmd_list_openssh(Command
):
3204 """List VGP OpenSSH Group Policy from the sysvol
3206 This command lists openssh options from the sysvol that will be applied to winbind clients.
3209 samba-tool gpo manage openssh list {31B2F340-016D-11D2-945F-00C04FB984F9}
3212 synopsis
= "%prog <gpo> [options]"
3214 takes_optiongroups
= {
3215 "sambaopts": options
.SambaOptions
,
3216 "versionopts": options
.VersionOptions
,
3217 "credopts": options
.CredentialsOptions
,
3221 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3222 metavar
="URL", dest
="H"),
3225 takes_args
= ["gpo"]
3227 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
3228 self
.lp
= sambaopts
.get_loadparm()
3229 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3231 # We need to know writable DC to setup SMB connection
3232 if H
and H
.startswith('ldap://'):
3236 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3237 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3240 conn
= smb_connection(dc_hostname
,
3245 realm
= self
.lp
.get('realm')
3246 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3247 'MACHINE\\VGP\\VTLA\\SshCfg',
3248 'SshD\\manifest.xml'])
3250 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3251 except NTSTATUSError
as e
:
3252 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3253 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3254 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3255 return # The file doesn't exist, so there is nothing to list
3256 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3257 raise CommandError("The authenticated user does "
3258 "not have sufficient privileges")
3261 policy
= xml_data
.find('policysetting')
3262 data
= policy
.find('data')
3263 configfile
= data
.find('configfile')
3264 for configsection
in configfile
.findall('configsection'):
3265 if configsection
.find('sectionname').text
:
3267 for kv
in configsection
.findall('keyvaluepair'):
3268 self
.outf
.write('%s %s\n' % (kv
.find('key').text
,
3269 kv
.find('value').text
))
3271 class cmd_set_openssh(GPOCommand
):
3272 """Sets a VGP OpenSSH Group Policy to the sysvol
3274 This command sets an openssh setting to the sysvol for applying to winbind
3275 clients. Not providing a value will unset the policy.
3278 samba-tool gpo manage openssh set {31B2F340-016D-11D2-945F-00C04FB984F9} KerberosAuthentication Yes
3281 synopsis
= "%prog <gpo> <setting> [value] [options]"
3283 takes_optiongroups
= {
3284 "sambaopts": options
.SambaOptions
,
3285 "versionopts": options
.VersionOptions
,
3286 "credopts": options
.CredentialsOptions
,
3290 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3291 metavar
="URL", dest
="H"),
3294 takes_args
= ["gpo", "setting", "value?"]
3296 def run(self
, gpo
, setting
, value
=None, H
=None, sambaopts
=None,
3297 credopts
=None, versionopts
=None):
3298 self
.lp
= sambaopts
.get_loadparm()
3299 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3301 # We need to know writable DC to setup SMB connection
3302 if H
and H
.startswith('ldap://'):
3306 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3307 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3310 conn
= smb_connection(dc_hostname
,
3315 self
.samdb_connect()
3316 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3318 realm
= self
.lp
.get('realm')
3319 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3320 'MACHINE\\VGP\\VTLA\\SshCfg\\SshD'])
3321 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3323 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
3324 policy
= xml_data
.getroot().find('policysetting')
3325 data
= policy
.find('data')
3326 configfile
= data
.find('configfile')
3327 except NTSTATUSError
as e
:
3328 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3329 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3330 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3331 # The file doesn't exist, so create the xml structure
3332 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
3333 policysetting
= ET
.SubElement(xml_data
.getroot(),
3335 pv
= ET
.SubElement(policysetting
, 'version')
3337 name
= ET
.SubElement(policysetting
, 'name')
3338 name
.text
= 'Configuration File'
3339 description
= ET
.SubElement(policysetting
, 'description')
3340 description
.text
= 'Represents Unix configuration file settings'
3341 apply_mode
= ET
.SubElement(policysetting
, 'apply_mode')
3342 apply_mode
.text
= 'merge'
3343 data
= ET
.SubElement(policysetting
, 'data')
3344 configfile
= ET
.SubElement(data
, 'configfile')
3345 configsection
= ET
.SubElement(configfile
, 'configsection')
3346 ET
.SubElement(configsection
, 'sectionname')
3347 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3348 raise CommandError("The authenticated user does "
3349 "not have sufficient privileges")
3353 if value
is not None:
3354 for configsection
in configfile
.findall('configsection'):
3355 if configsection
.find('sectionname').text
:
3356 continue # Ignore Quest SSH settings
3358 for kv
in configsection
.findall('keyvaluepair'):
3359 settings
[kv
.find('key')] = kv
3360 if setting
in settings
.keys():
3361 settings
[setting
].text
= value
3363 keyvaluepair
= ET
.SubElement(configsection
, 'keyvaluepair')
3364 key
= ET
.SubElement(keyvaluepair
, 'key')
3366 dvalue
= ET
.SubElement(keyvaluepair
, 'value')
3369 for configsection
in configfile
.findall('configsection'):
3370 if configsection
.find('sectionname').text
:
3371 continue # Ignore Quest SSH settings
3373 for kv
in configsection
.findall('keyvaluepair'):
3374 settings
[kv
.find('key').text
] = kv
3375 if setting
in settings
.keys():
3376 configsection
.remove(settings
[setting
])
3378 raise CommandError("Cannot remove '%s' because it does " \
3379 "not exist" % setting
)
3382 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3385 create_directory_hier(conn
, vgp_dir
)
3386 conn
.savefile(vgp_xml
, out
.read())
3387 reg
.increment_gpt_ini(machine_changed
=True)
3388 except NTSTATUSError
as e
:
3389 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3390 raise CommandError("The authenticated user does "
3391 "not have sufficient privileges")
3394 class cmd_openssh(SuperCommand
):
3395 """Manage OpenSSH Group Policy Objects"""
3397 subcommands
["list"] = cmd_list_openssh()
3398 subcommands
["set"] = cmd_set_openssh()
3400 class cmd_list_startup(Command
):
3401 """List VGP Startup Script Group Policy from the sysvol
3403 This command lists the startup script policies currently set on the sysvol.
3406 samba-tool gpo manage scripts startup list {31B2F340-016D-11D2-945F-00C04FB984F9}
3409 synopsis
= "%prog <gpo> [options]"
3411 takes_optiongroups
= {
3412 "sambaopts": options
.SambaOptions
,
3413 "versionopts": options
.VersionOptions
,
3414 "credopts": options
.CredentialsOptions
,
3418 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3419 metavar
="URL", dest
="H"),
3422 takes_args
= ["gpo"]
3424 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
3425 self
.lp
= sambaopts
.get_loadparm()
3426 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3428 # We need to know writable DC to setup SMB connection
3429 if H
and H
.startswith('ldap://'):
3433 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3434 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3437 conn
= smb_connection(dc_hostname
,
3442 realm
= self
.lp
.get('realm')
3443 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3444 'MACHINE\\VGP\\VTLA\\Unix',
3445 'Scripts\\Startup\\manifest.xml'])
3447 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3448 except NTSTATUSError
as e
:
3449 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3450 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3451 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3452 return # The file doesn't exist, so there is nothing to list
3453 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3454 raise CommandError("The authenticated user does "
3455 "not have sufficient privileges")
3458 policy
= xml_data
.find('policysetting')
3459 data
= policy
.find('data')
3460 for listelement
in data
.findall('listelement'):
3461 script
= listelement
.find('script')
3462 script_path
= '\\'.join(['\\', realm
.lower(), 'Policies', gpo
,
3463 'MACHINE\\VGP\\VTLA\\Unix\\Scripts',
3464 'Startup', script
.text
])
3465 parameters
= listelement
.find('parameters')
3466 run_as
= listelement
.find('run_as')
3467 if run_as
is not None:
3468 run_as
= run_as
.text
3471 if parameters
is not None:
3472 parameters
= parameters
.text
3475 self
.outf
.write('@reboot %s %s %s\n' % (run_as
, script_path
,
3478 class cmd_add_startup(GPOCommand
):
3479 """Adds VGP Startup Script Group Policy to the sysvol
3481 This command adds a startup script policy to the sysvol.
3484 samba-tool gpo manage scripts startup add {31B2F340-016D-11D2-945F-00C04FB984F9} test_script.sh '\\-n \\-p all'
3487 synopsis
= "%prog <gpo> <script> [args] [run_as] [options]"
3489 takes_optiongroups
= {
3490 "sambaopts": options
.SambaOptions
,
3491 "versionopts": options
.VersionOptions
,
3492 "credopts": options
.CredentialsOptions
,
3496 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3497 metavar
="URL", dest
="H"),
3498 Option("--run-once", dest
="run_once", default
=False, action
='store_true',
3499 help="Whether to run the script only once"),
3502 takes_args
= ["gpo", "script", "args?", "run_as?"]
3504 def run(self
, gpo
, script
, args
=None, run_as
=None, run_once
=None,
3505 H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
3506 self
.lp
= sambaopts
.get_loadparm()
3507 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3509 if not os
.path
.exists(script
):
3510 raise CommandError("Script '%s' does not exist" % script
)
3512 # We need to know writable DC to setup SMB connection
3513 if H
and H
.startswith('ldap://'):
3517 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3518 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3521 conn
= smb_connection(dc_hostname
,
3526 self
.samdb_connect()
3527 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3529 realm
= self
.lp
.get('realm')
3530 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3531 'MACHINE\\VGP\\VTLA\\Unix\\Scripts\\Startup'])
3532 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3534 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
3535 policy
= xml_data
.getroot().find('policysetting')
3536 data
= policy
.find('data')
3537 except NTSTATUSError
as e
:
3538 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3539 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3540 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3541 # The file doesn't exist, so create the xml structure
3542 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
3543 policysetting
= ET
.SubElement(xml_data
.getroot(),
3545 pv
= ET
.SubElement(policysetting
, 'version')
3547 name
= ET
.SubElement(policysetting
, 'name')
3548 name
.text
= 'Unix Scripts'
3549 description
= ET
.SubElement(policysetting
, 'description')
3550 description
.text
= \
3551 'Represents Unix scripts to run on Group Policy clients'
3552 data
= ET
.SubElement(policysetting
, 'data')
3553 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3554 raise CommandError("The authenticated user does "
3555 "not have sufficient privileges")
3559 script_data
= open(script
, 'rb').read()
3560 listelement
= ET
.SubElement(data
, 'listelement')
3561 script_elm
= ET
.SubElement(listelement
, 'script')
3562 script_elm
.text
= os
.path
.basename(script
)
3563 hash = ET
.SubElement(listelement
, 'hash')
3564 hash.text
= hashlib
.md5(script_data
).hexdigest().upper()
3565 if args
is not None:
3566 parameters
= ET
.SubElement(listelement
, 'parameters')
3567 parameters
.text
= args
.strip('"').strip("'").replace('\\-', '-')
3568 if run_as
is not None:
3569 run_as_elm
= ET
.SubElement(listelement
, 'run_as')
3570 run_as_elm
.text
= run_as
3572 ET
.SubElement(listelement
, 'run_once')
3575 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3577 sysvol_script
= '\\'.join([vgp_dir
, os
.path
.basename(script
)])
3579 create_directory_hier(conn
, vgp_dir
)
3580 conn
.savefile(vgp_xml
, out
.read())
3581 conn
.savefile(sysvol_script
, script_data
)
3582 reg
.increment_gpt_ini(machine_changed
=True)
3583 except NTSTATUSError
as e
:
3584 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3585 raise CommandError("The authenticated user does "
3586 "not have sufficient privileges")
3589 class cmd_remove_startup(GPOCommand
):
3590 """Removes VGP Startup Script Group Policy from the sysvol
3592 This command removes a startup script policy from the sysvol.
3595 samba-tool gpo manage scripts startup remove {31B2F340-016D-11D2-945F-00C04FB984F9} test_script.sh
3598 synopsis
= "%prog <gpo> <script> [options]"
3600 takes_optiongroups
= {
3601 "sambaopts": options
.SambaOptions
,
3602 "versionopts": options
.VersionOptions
,
3603 "credopts": options
.CredentialsOptions
,
3607 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3608 metavar
="URL", dest
="H"),
3611 takes_args
= ["gpo", "script"]
3613 def run(self
, gpo
, script
, H
=None, sambaopts
=None, credopts
=None,
3615 self
.lp
= sambaopts
.get_loadparm()
3616 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3618 # We need to know writable DC to setup SMB connection
3619 if H
and H
.startswith('ldap://'):
3623 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3624 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3627 conn
= smb_connection(dc_hostname
,
3632 self
.samdb_connect()
3633 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3635 realm
= self
.lp
.get('realm')
3636 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3637 'MACHINE\\VGP\\VTLA\\Unix\\Scripts\\Startup'])
3638 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3640 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
3641 policy
= xml_data
.getroot().find('policysetting')
3642 data
= policy
.find('data')
3643 except NTSTATUSError
as e
:
3644 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3645 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3646 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3647 raise CommandError("Cannot remove script '%s' "
3648 "because it does not exist" % script
)
3649 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3650 raise CommandError("The authenticated user does "
3651 "not have sufficient privileges")
3655 for listelement
in data
.findall('listelement'):
3656 script_elm
= listelement
.find('script')
3657 if script_elm
.text
== os
.path
.basename(script
.replace('\\', '/')):
3658 data
.remove(listelement
)
3661 raise CommandError("Cannot remove script '%s' "
3662 "because it does not exist" % script
)
3665 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3668 create_directory_hier(conn
, vgp_dir
)
3669 conn
.savefile(vgp_xml
, out
.read())
3670 reg
.increment_gpt_ini(machine_changed
=True)
3671 except NTSTATUSError
as e
:
3672 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3673 raise CommandError("The authenticated user does "
3674 "not have sufficient privileges")
3677 class cmd_startup(SuperCommand
):
3678 """Manage Startup Scripts Group Policy Objects"""
3680 subcommands
["list"] = cmd_list_startup()
3681 subcommands
["add"] = cmd_add_startup()
3682 subcommands
["remove"] = cmd_remove_startup()
3684 class cmd_scripts(SuperCommand
):
3685 """Manage Scripts Group Policy Objects"""
3687 subcommands
["startup"] = cmd_startup()
3689 class cmd_list_motd(Command
):
3690 """List VGP MOTD Group Policy from the sysvol
3692 This command lists the Message of the Day from the sysvol that will be applied
3696 samba-tool gpo manage motd list {31B2F340-016D-11D2-945F-00C04FB984F9}
3699 synopsis
= "%prog <gpo> [options]"
3701 takes_optiongroups
= {
3702 "sambaopts": options
.SambaOptions
,
3703 "versionopts": options
.VersionOptions
,
3704 "credopts": options
.CredentialsOptions
,
3708 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3709 metavar
="URL", dest
="H"),
3712 takes_args
= ["gpo"]
3714 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
3715 self
.lp
= sambaopts
.get_loadparm()
3716 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3718 # We need to know writable DC to setup SMB connection
3719 if H
and H
.startswith('ldap://'):
3723 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3724 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3727 conn
= smb_connection(dc_hostname
,
3732 realm
= self
.lp
.get('realm')
3733 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3734 'MACHINE\\VGP\\VTLA\\Unix',
3735 'MOTD\\manifest.xml'])
3737 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3738 except NTSTATUSError
as e
:
3739 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3740 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3741 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3742 return # The file doesn't exist, so there is nothing to list
3743 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3744 raise CommandError("The authenticated user does "
3745 "not have sufficient privileges")
3748 policy
= xml_data
.find('policysetting')
3749 data
= policy
.find('data')
3750 text
= data
.find('text')
3751 self
.outf
.write(text
.text
)
3753 class cmd_set_motd(GPOCommand
):
3754 """Sets a VGP MOTD Group Policy to the sysvol
3756 This command sets the Message of the Day to the sysvol for applying to winbind
3757 clients. Not providing a value will unset the policy.
3760 samba-tool gpo manage motd set {31B2F340-016D-11D2-945F-00C04FB984F9} "Message for today"
3763 synopsis
= "%prog <gpo> [value] [options]"
3765 takes_optiongroups
= {
3766 "sambaopts": options
.SambaOptions
,
3767 "versionopts": options
.VersionOptions
,
3768 "credopts": options
.CredentialsOptions
,
3772 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3773 metavar
="URL", dest
="H"),
3776 takes_args
= ["gpo", "value?"]
3778 def run(self
, gpo
, value
=None, H
=None, sambaopts
=None, credopts
=None,
3780 self
.lp
= sambaopts
.get_loadparm()
3781 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3783 # We need to know writable DC to setup SMB connection
3784 if H
and H
.startswith('ldap://'):
3788 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3789 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3792 conn
= smb_connection(dc_hostname
,
3797 self
.samdb_connect()
3798 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3800 realm
= self
.lp
.get('realm')
3801 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3802 'MACHINE\\VGP\\VTLA\\Unix\\MOTD'])
3803 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3806 conn
.unlink(vgp_xml
)
3807 reg
.increment_gpt_ini(machine_changed
=True)
3811 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3812 except NTSTATUSError
as e
:
3813 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3814 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3815 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3816 # The file doesn't exist, so create the xml structure
3817 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
3818 policysetting
= ET
.SubElement(xml_data
.getroot(),
3820 pv
= ET
.SubElement(policysetting
, 'version')
3822 name
= ET
.SubElement(policysetting
, 'name')
3823 name
.text
= 'Text File'
3824 description
= ET
.SubElement(policysetting
, 'description')
3825 description
.text
= 'Represents a Generic Text File'
3826 apply_mode
= ET
.SubElement(policysetting
, 'apply_mode')
3827 apply_mode
.text
= 'replace'
3828 data
= ET
.SubElement(policysetting
, 'data')
3829 filename
= ET
.SubElement(data
, 'filename')
3830 filename
.text
= 'motd'
3831 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3832 raise CommandError("The authenticated user does "
3833 "not have sufficient privileges")
3837 text
= ET
.SubElement(data
, 'text')
3841 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
3844 create_directory_hier(conn
, vgp_dir
)
3845 conn
.savefile(vgp_xml
, out
.read())
3846 reg
.increment_gpt_ini(machine_changed
=True)
3847 except NTSTATUSError
as e
:
3848 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3849 raise CommandError("The authenticated user does "
3850 "not have sufficient privileges")
3853 class cmd_motd(SuperCommand
):
3854 """Manage Message of the Day Group Policy Objects"""
3856 subcommands
["list"] = cmd_list_motd()
3857 subcommands
["set"] = cmd_set_motd()
3859 class cmd_list_issue(Command
):
3860 """List VGP Issue Group Policy from the sysvol
3862 This command lists the Prelogin Message from the sysvol that will be applied
3866 samba-tool gpo manage issue list {31B2F340-016D-11D2-945F-00C04FB984F9}
3869 synopsis
= "%prog <gpo> [options]"
3871 takes_optiongroups
= {
3872 "sambaopts": options
.SambaOptions
,
3873 "versionopts": options
.VersionOptions
,
3874 "credopts": options
.CredentialsOptions
,
3878 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3879 metavar
="URL", dest
="H"),
3882 takes_args
= ["gpo"]
3884 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
3885 self
.lp
= sambaopts
.get_loadparm()
3886 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3888 # We need to know writable DC to setup SMB connection
3889 if H
and H
.startswith('ldap://'):
3893 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3894 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3897 conn
= smb_connection(dc_hostname
,
3902 realm
= self
.lp
.get('realm')
3903 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3904 'MACHINE\\VGP\\VTLA\\Unix',
3905 'Issue\\manifest.xml'])
3907 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3908 except NTSTATUSError
as e
:
3909 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3910 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3911 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3912 return # The file doesn't exist, so there is nothing to list
3913 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
3914 raise CommandError("The authenticated user does "
3915 "not have sufficient privileges")
3918 policy
= xml_data
.find('policysetting')
3919 data
= policy
.find('data')
3920 text
= data
.find('text')
3921 self
.outf
.write(text
.text
)
3923 class cmd_set_issue(GPOCommand
):
3924 """Sets a VGP Issue Group Policy to the sysvol
3926 This command sets the Prelogin Message to the sysvol for applying to winbind
3927 clients. Not providing a value will unset the policy.
3930 samba-tool gpo manage issue set {31B2F340-016D-11D2-945F-00C04FB984F9} "Welcome to Samba!"
3933 synopsis
= "%prog <gpo> [value] [options]"
3935 takes_optiongroups
= {
3936 "sambaopts": options
.SambaOptions
,
3937 "versionopts": options
.VersionOptions
,
3938 "credopts": options
.CredentialsOptions
,
3942 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3943 metavar
="URL", dest
="H"),
3946 takes_args
= ["gpo", "value?"]
3948 def run(self
, gpo
, value
=None, H
=None, sambaopts
=None, credopts
=None,
3950 self
.lp
= sambaopts
.get_loadparm()
3951 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
3953 # We need to know writable DC to setup SMB connection
3954 if H
and H
.startswith('ldap://'):
3958 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
3959 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
3962 conn
= smb_connection(dc_hostname
,
3967 self
.samdb_connect()
3968 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
3970 realm
= self
.lp
.get('realm')
3971 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
3972 'MACHINE\\VGP\\VTLA\\Unix\\Issue'])
3973 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
3976 conn
.unlink(vgp_xml
)
3977 reg
.increment_gpt_ini(machine_changed
=True)
3981 xml_data
= ET
.fromstring(conn
.loadfile(vgp_xml
))
3982 except NTSTATUSError
as e
:
3983 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
3984 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
3985 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
3986 # The file doesn't exist, so create the xml structure
3987 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
3988 policysetting
= ET
.SubElement(xml_data
.getroot(),
3990 pv
= ET
.SubElement(policysetting
, 'version')
3992 name
= ET
.SubElement(policysetting
, 'name')
3993 name
.text
= 'Text File'
3994 description
= ET
.SubElement(policysetting
, 'description')
3995 description
.text
= 'Represents a Generic Text File'
3996 apply_mode
= ET
.SubElement(policysetting
, 'apply_mode')
3997 apply_mode
.text
= 'replace'
3998 data
= ET
.SubElement(policysetting
, 'data')
3999 filename
= ET
.SubElement(data
, 'filename')
4000 filename
.text
= 'issue'
4001 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4002 raise CommandError("The authenticated user does "
4003 "not have sufficient privileges")
4007 text
= ET
.SubElement(data
, 'text')
4011 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
4014 create_directory_hier(conn
, vgp_dir
)
4015 conn
.savefile(vgp_xml
, out
.read())
4016 reg
.increment_gpt_ini(machine_changed
=True)
4017 except NTSTATUSError
as e
:
4018 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4019 raise CommandError("The authenticated user does "
4020 "not have sufficient privileges")
4023 class cmd_issue(SuperCommand
):
4024 """Manage Issue Group Policy Objects"""
4026 subcommands
["list"] = cmd_list_issue()
4027 subcommands
["set"] = cmd_set_issue()
4029 class cmd_list_access(Command
):
4030 """List VGP Host Access Group Policy from the sysvol
4032 This command lists host access rules from the sysvol that will be applied to winbind clients.
4035 samba-tool gpo manage access list {31B2F340-016D-11D2-945F-00C04FB984F9}
4038 synopsis
= "%prog <gpo> [options]"
4040 takes_optiongroups
= {
4041 "sambaopts": options
.SambaOptions
,
4042 "versionopts": options
.VersionOptions
,
4043 "credopts": options
.CredentialsOptions
,
4047 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4048 metavar
="URL", dest
="H"),
4051 takes_args
= ["gpo"]
4053 def run(self
, gpo
, H
=None, sambaopts
=None, credopts
=None, versionopts
=None):
4054 self
.lp
= sambaopts
.get_loadparm()
4055 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
4057 # We need to know writable DC to setup SMB connection
4058 if H
and H
.startswith('ldap://'):
4062 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
4063 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
4066 conn
= smb_connection(dc_hostname
,
4071 realm
= self
.lp
.get('realm')
4072 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4073 'MACHINE\\VGP\\VTLA\\VAS',
4074 'HostAccessControl\\Allow\\manifest.xml'])
4076 allow
= ET
.fromstring(conn
.loadfile(vgp_xml
))
4077 except NTSTATUSError
as e
:
4078 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
4079 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
4080 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
4081 allow
= None # The file doesn't exist, ignore it
4082 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4083 raise CommandError("The authenticated user does "
4084 "not have sufficient privileges")
4088 if allow
is not None:
4089 policy
= allow
.find('policysetting')
4090 data
= policy
.find('data')
4091 for listelement
in data
.findall('listelement'):
4092 adobject
= listelement
.find('adobject')
4093 name
= adobject
.find('name')
4094 domain
= adobject
.find('domain')
4095 self
.outf
.write('+:%s\\%s:ALL\n' % (domain
.text
, name
.text
))
4097 vgp_xml
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4098 'MACHINE\\VGP\\VTLA\\VAS',
4099 'HostAccessControl\\Deny\\manifest.xml'])
4101 deny
= ET
.fromstring(conn
.loadfile(vgp_xml
))
4102 except NTSTATUSError
as e
:
4103 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
4104 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
4105 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
4106 deny
= None # The file doesn't exist, ignore it
4107 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4108 raise CommandError("The authenticated user does "
4109 "not have sufficient privileges")
4113 if deny
is not None:
4114 policy
= deny
.find('policysetting')
4115 data
= policy
.find('data')
4116 for listelement
in data
.findall('listelement'):
4117 adobject
= listelement
.find('adobject')
4118 name
= adobject
.find('name')
4119 domain
= adobject
.find('domain')
4120 self
.outf
.write('-:%s\\%s:ALL\n' % (domain
.text
, name
.text
))
4122 class cmd_add_access(GPOCommand
):
4123 """Adds a VGP Host Access Group Policy to the sysvol
4125 This command adds a host access setting to the sysvol for applying to winbind
4126 clients. Any time an allow entry is detected by the client, an implicit deny
4127 ALL will be assumed.
4130 samba-tool gpo manage access add {31B2F340-016D-11D2-945F-00C04FB984F9} allow goodguy example.com
4133 synopsis
= "%prog <gpo> <allow/deny> <cn> <domain> [options]"
4135 takes_optiongroups
= {
4136 "sambaopts": options
.SambaOptions
,
4137 "versionopts": options
.VersionOptions
,
4138 "credopts": options
.CredentialsOptions
,
4142 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4143 metavar
="URL", dest
="H"),
4146 takes_args
= ["gpo", "etype", "cn", "domain"]
4148 def run(self
, gpo
, etype
, cn
, domain
, H
=None, sambaopts
=None,
4149 credopts
=None, versionopts
=None):
4150 self
.lp
= sambaopts
.get_loadparm()
4151 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
4153 # We need to know writable DC to setup SMB connection
4154 if H
and H
.startswith('ldap://'):
4158 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
4159 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
4162 conn
= smb_connection(dc_hostname
,
4167 self
.samdb_connect()
4168 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
4170 realm
= self
.lp
.get('realm')
4171 if etype
== 'allow':
4172 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4173 'MACHINE\\VGP\\VTLA\\VAS',
4174 'HostAccessControl\\Allow'])
4175 elif etype
== 'deny':
4176 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4177 'MACHINE\\VGP\\VTLA\\VAS',
4178 'HostAccessControl\\Deny'])
4180 raise CommandError("The entry type must be either 'allow' or "
4181 "'deny'. Unknown type '%s'" % etype
)
4182 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
4184 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
4185 policy
= xml_data
.getroot().find('policysetting')
4186 data
= policy
.find('data')
4187 except NTSTATUSError
as e
:
4188 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
4189 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
4190 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
4191 # The file doesn't exist, so create the xml structure
4192 xml_data
= ET
.ElementTree(ET
.Element('vgppolicy'))
4193 policysetting
= ET
.SubElement(xml_data
.getroot(),
4195 pv
= ET
.SubElement(policysetting
, 'version')
4197 name
= ET
.SubElement(policysetting
, 'name')
4198 name
.text
= 'Host Access Control'
4199 description
= ET
.SubElement(policysetting
, 'description')
4200 description
.text
= 'Represents host access control data (pam_access)'
4201 apply_mode
= ET
.SubElement(policysetting
, 'apply_mode')
4202 apply_mode
.text
= 'merge'
4203 data
= ET
.SubElement(policysetting
, 'data')
4204 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4205 raise CommandError("The authenticated user does "
4206 "not have sufficient privileges")
4210 url
= dc_url(self
.lp
, self
.creds
, dc
=domain
)
4211 samdb
= SamDB(url
=url
, session_info
=system_session(),
4212 credentials
=self
.creds
, lp
=self
.lp
)
4214 res
= samdb
.search(base
=samdb
.domain_dn(),
4215 scope
=ldb
.SCOPE_SUBTREE
,
4216 expression
="(cn=%s)" % cn
,
4217 attrs
=['userPrincipalName',
4221 raise CommandError('Unable to find user or group "%s"' % cn
)
4223 objectclass
= get_string(res
[0]['objectClass'][-1])
4224 if objectclass
not in ['user', 'group']:
4225 raise CommandError('%s is not a user or group' % cn
)
4227 listelement
= ET
.SubElement(data
, 'listelement')
4228 etype
= ET
.SubElement(listelement
, 'type')
4229 etype
.text
= objectclass
.upper()
4230 entry
= ET
.SubElement(listelement
, 'entry')
4231 entry
.text
= '%s\\%s' % (samdb
.domain_netbios_name(),
4232 get_string(res
[0]['samaccountname'][-1]))
4233 if objectclass
== 'group':
4234 groupattr
= ET
.SubElement(data
, 'groupattr')
4235 groupattr
.text
= 'samAccountName'
4236 adobject
= ET
.SubElement(listelement
, 'adobject')
4237 name
= ET
.SubElement(adobject
, 'name')
4238 name
.text
= get_string(res
[0]['samaccountname'][-1])
4239 domain_elm
= ET
.SubElement(adobject
, 'domain')
4240 domain_elm
.text
= domain
4241 etype
= ET
.SubElement(adobject
, 'type')
4242 etype
.text
= objectclass
4245 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
4248 create_directory_hier(conn
, vgp_dir
)
4249 conn
.savefile(vgp_xml
, out
.read())
4250 reg
.increment_gpt_ini(machine_changed
=True)
4251 except NTSTATUSError
as e
:
4252 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4253 raise CommandError("The authenticated user does "
4254 "not have sufficient privileges")
4257 class cmd_remove_access(GPOCommand
):
4258 """Remove a VGP Host Access Group Policy from the sysvol
4260 This command removes a host access setting from the sysvol for applying to
4264 samba-tool gpo manage access remove {31B2F340-016D-11D2-945F-00C04FB984F9} allow goodguy example.com
4267 synopsis
= "%prog <gpo> <allow/deny> <name> <domain> [options]"
4269 takes_optiongroups
= {
4270 "sambaopts": options
.SambaOptions
,
4271 "versionopts": options
.VersionOptions
,
4272 "credopts": options
.CredentialsOptions
,
4276 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4277 metavar
="URL", dest
="H"),
4280 takes_args
= ["gpo", "etype", "name", "domain"]
4282 def run(self
, gpo
, etype
, name
, domain
, H
=None, sambaopts
=None,
4283 credopts
=None, versionopts
=None):
4284 self
.lp
= sambaopts
.get_loadparm()
4285 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
4287 # We need to know writable DC to setup SMB connection
4288 if H
and H
.startswith('ldap://'):
4292 dc_hostname
= netcmd_finddc(self
.lp
, self
.creds
)
4293 self
.url
= dc_url(self
.lp
, self
.creds
, dc
=dc_hostname
)
4296 conn
= smb_connection(dc_hostname
,
4301 self
.samdb_connect()
4302 reg
= RegistryGroupPolicies(gpo
, self
.lp
, self
.creds
, self
.samdb
, H
)
4304 realm
= self
.lp
.get('realm')
4305 if etype
== 'allow':
4306 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4307 'MACHINE\\VGP\\VTLA\\VAS',
4308 'HostAccessControl\\Allow'])
4309 elif etype
== 'deny':
4310 vgp_dir
= '\\'.join([realm
.lower(), 'Policies', gpo
,
4311 'MACHINE\\VGP\\VTLA\\VAS',
4312 'HostAccessControl\\Deny'])
4314 raise CommandError("The entry type must be either 'allow' or "
4315 "'deny'. Unknown type '%s'" % etype
)
4316 vgp_xml
= '\\'.join([vgp_dir
, 'manifest.xml'])
4318 xml_data
= ET
.ElementTree(ET
.fromstring(conn
.loadfile(vgp_xml
)))
4319 policy
= xml_data
.getroot().find('policysetting')
4320 data
= policy
.find('data')
4321 except NTSTATUSError
as e
:
4322 if e
.args
[0] in [NT_STATUS_OBJECT_NAME_INVALID
,
4323 NT_STATUS_OBJECT_NAME_NOT_FOUND
,
4324 NT_STATUS_OBJECT_PATH_NOT_FOUND
]:
4325 raise CommandError("Cannot remove %s entry because it does "
4326 "not exist" % etype
)
4327 elif e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4328 raise CommandError("The authenticated user does "
4329 "not have sufficient privileges")
4333 for listelement
in data
.findall('listelement'):
4334 adobject
= listelement
.find('adobject')
4335 name_elm
= adobject
.find('name')
4336 domain_elm
= adobject
.find('domain')
4337 if name_elm
is not None and name_elm
.text
== name
and \
4338 domain_elm
is not None and domain_elm
.text
== domain
:
4339 data
.remove(listelement
)
4342 raise CommandError("Cannot remove %s entry because it does "
4343 "not exist" % etype
)
4346 xml_data
.write(out
, encoding
='UTF-8', xml_declaration
=True)
4349 create_directory_hier(conn
, vgp_dir
)
4350 conn
.savefile(vgp_xml
, out
.read())
4351 reg
.increment_gpt_ini(machine_changed
=True)
4352 except NTSTATUSError
as e
:
4353 if e
.args
[0] == NT_STATUS_ACCESS_DENIED
:
4354 raise CommandError("The authenticated user does "
4355 "not have sufficient privileges")
4358 class cmd_cse_register(Command
):
4359 """Register a Client Side Extension (CSE) on the current host
4361 This command takes a CSE filename as an argument, and registers it for
4362 applying policy on the current host. This is not necessary for CSEs which
4363 are distributed with the current version of Samba, but is useful for installing
4364 experimental CSEs or custom built CSEs.
4365 The <cse_file> argument MUST be a permanent location for the CSE. The register
4366 command does not copy the file to some other directory. The samba-gpupdate
4367 command will execute the CSE from the exact location specified from this
4371 samba-tool gpo cse register ./gp_chromium_ext.py gp_chromium_ext --machine
4374 synopsis
= "%prog <cse_file> <cse_name> [options]"
4376 takes_optiongroups
= {
4377 "sambaopts": options
.SambaOptions
,
4378 "versionopts": options
.VersionOptions
,
4382 Option("--machine", default
=False, action
='store_true',
4383 help="Whether to register the CSE as Machine policy"),
4384 Option("--user", default
=False, action
='store_true',
4385 help="Whether to register the CSE as User policy"),
4388 takes_args
= ["cse_file", "cse_name"]
4390 def run(self
, cse_file
, cse_name
, machine
=False, user
=False,
4391 sambaopts
=None, versionopts
=None):
4392 self
.lp
= sambaopts
.get_loadparm()
4394 if machine
== False and user
== False:
4395 raise CommandError("Either --machine or --user must be selected")
4397 ext_guid
= "{%s}" % str(uuid
.uuid4())
4398 ext_path
= os
.path
.realpath(cse_file
)
4399 ret
= register_gp_extension(ext_guid
, cse_name
, ext_path
,
4400 smb_conf
=self
.lp
.configfile
,
4401 machine
=machine
, user
=user
)
4403 raise CommandError('Failed to register CSE "%s"' % cse_name
)
4405 class cmd_cse_list(Command
):
4406 """List the registered Client Side Extensions (CSEs) on the current host
4408 This command lists the currently registered CSEs on the host.
4411 samba-tool gpo cse list
4414 synopsis
= "%prog [options]"
4416 takes_optiongroups
= {
4417 "sambaopts": options
.SambaOptions
,
4418 "versionopts": options
.VersionOptions
,
4421 def run(self
, sambaopts
=None, versionopts
=None):
4422 self
.lp
= sambaopts
.get_loadparm()
4424 cses
= list_gp_extensions(self
.lp
.configfile
)
4425 for guid
, gp_ext
in cses
.items():
4426 self
.outf
.write("UniqueGUID : %s\n" % guid
)
4427 self
.outf
.write("FileName : %s\n" % gp_ext
['DllName'])
4428 self
.outf
.write("ProcessGroupPolicy : %s\n" % \
4429 gp_ext
['ProcessGroupPolicy'])
4430 self
.outf
.write("MachinePolicy : %s\n" % \
4431 str(gp_ext
['MachinePolicy']))
4432 self
.outf
.write("UserPolicy : %s\n\n" % \
4433 str(gp_ext
['UserPolicy']))
4435 class cmd_cse_unregister(Command
):
4436 """Unregister a Client Side Extension (CSE) from the current host
4438 This command takes a unique GUID as an argument (representing a registered
4439 CSE), and unregisters it for applying policy on the current host. Use the
4440 `samba-tool gpo cse list` command to determine the unique GUIDs of CSEs.
4443 samba-tool gpo cse unregister {3F60F344-92BF-11ED-A1EB-0242AC120002}
4446 synopsis
= "%prog <guid> [options]"
4448 takes_optiongroups
= {
4449 "sambaopts": options
.SambaOptions
,
4450 "versionopts": options
.VersionOptions
,
4453 takes_args
= ["guid"]
4455 def run(self
, guid
, sambaopts
=None, versionopts
=None):
4456 self
.lp
= sambaopts
.get_loadparm()
4458 ret
= unregister_gp_extension(guid
, self
.lp
.configfile
)
4460 raise CommandError('Failed to unregister CSE "%s"' % guid
)
4462 class cmd_cse(SuperCommand
):
4463 """Manage Client Side Extensions"""
4465 subcommands
["register"] = cmd_cse_register()
4466 subcommands
["list"] = cmd_cse_list()
4467 subcommands
["unregister"] = cmd_cse_unregister()
4469 class cmd_access(SuperCommand
):
4470 """Manage Host Access Group Policy Objects"""
4472 subcommands
["list"] = cmd_list_access()
4473 subcommands
["add"] = cmd_add_access()
4474 subcommands
["remove"] = cmd_remove_access()
4476 class cmd_manage(SuperCommand
):
4477 """Manage Group Policy Objects"""
4479 subcommands
["sudoers"] = cmd_sudoers()
4480 subcommands
["security"] = cmd_security()
4481 subcommands
["smb_conf"] = cmd_smb_conf()
4482 subcommands
["symlink"] = cmd_symlink()
4483 subcommands
["files"] = cmd_files()
4484 subcommands
["openssh"] = cmd_openssh()
4485 subcommands
["scripts"] = cmd_scripts()
4486 subcommands
["motd"] = cmd_motd()
4487 subcommands
["issue"] = cmd_issue()
4488 subcommands
["access"] = cmd_access()
4490 class cmd_gpo(SuperCommand
):
4491 """Group Policy Object (GPO) management."""
4494 subcommands
["listall"] = cmd_listall()
4495 subcommands
["list"] = cmd_list()
4496 subcommands
["show"] = cmd_show()
4497 subcommands
["load"] = cmd_load()
4498 subcommands
["remove"] = cmd_remove()
4499 subcommands
["getlink"] = cmd_getlink()
4500 subcommands
["setlink"] = cmd_setlink()
4501 subcommands
["dellink"] = cmd_dellink()
4502 subcommands
["listcontainers"] = cmd_listcontainers()
4503 subcommands
["getinheritance"] = cmd_getinheritance()
4504 subcommands
["setinheritance"] = cmd_setinheritance()
4505 subcommands
["fetch"] = cmd_fetch()
4506 subcommands
["create"] = cmd_create()
4507 subcommands
["del"] = cmd_del()
4508 subcommands
["aclcheck"] = cmd_aclcheck()
4509 subcommands
["backup"] = cmd_backup()
4510 subcommands
["restore"] = cmd_restore()
4511 subcommands
["admxload"] = cmd_admxload()
4512 subcommands
["manage"] = cmd_manage()
4513 subcommands
["cse"] = cmd_cse()