pidl:Wireshark Fix the type of array of pointerse to hf_ values
[Samba.git] / python / samba / netcmd / gpo.py
blobba55b2ec7a772224c0679032894c982a2fca56a2
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/>.
21 import os
22 import sys
23 import samba.getopt as options
24 import ldb
25 import re
26 import xml.etree.ElementTree as ET
27 import shutil
28 import tempfile
30 from samba.auth import system_session
31 from samba.netcmd import (
32 Command,
33 CommandError,
34 Option,
35 SuperCommand,
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
42 import samba.security
43 import samba.auth
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
49 import uuid
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 (
56 GPIniParser,
57 GPTIniParser,
58 GPFDeploy1IniParser,
59 GPScriptsIniParser
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
70 import hashlib
71 import json
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,
82 smb_connection,
83 get_gpo_dn
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)
94 if not flags:
95 ret = 'NONE'
96 else:
97 ret = ' '.join(flags)
98 return ret
101 def gplink_options_string(value):
102 """return gplink options string"""
103 options = policy.get_gplink_options(value)
104 if not options:
105 ret = 'NONE'
106 else:
107 ret = ' '.join(options)
108 return ret
111 def parse_gplink(gplink):
112 """parse a gPLink into an array of dn and options"""
113 ret = []
115 if gplink.strip() == '':
116 return ret
118 a = gplink.split(']')
119 for g in a:
120 if not g:
121 continue
122 d = g.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])})
126 return ret
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)
132 return ret
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"""
139 if url is None:
140 if dc is None:
141 try:
142 dc = netcmd_finddc(lp, creds)
143 except Exception as e:
144 raise RuntimeError("Could not find a DC for domain", e)
145 url = 'ldap://' + dc
146 return url
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
163 if gpo is not None:
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)
169 if dn is not None:
170 base_dn = dn
171 search_scope = ldb.SCOPE_BASE
173 try:
174 msg = samdb.search(base=base_dn, scope=search_scope,
175 expression=search_expr,
176 attrs=['nTSecurityDescriptor',
177 'versionNumber',
178 'flags',
179 'name',
180 'displayName',
181 'gPCFileSysPath',
182 'gPCMachineExtensionNames',
183 'gPCUserExtensionNames'],
184 controls=['sd_flags:1:%d' % sd_flags])
185 except Exception as e:
186 if gpo is not None:
187 mesg = "Cannot get information for GPO %s" % gpo
188 else:
189 mesg = "Cannot get information for GPOs"
190 raise CommandError(mesg, e)
192 return msg
195 def get_gpo_containers(samdb, gpo):
196 """lists dn of containers for a GPO"""
198 search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
199 try:
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)
204 return msg
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
210 try:
211 msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
212 expression="(objectClass=*)",
213 attrs=['gPLink'])[0]
214 except Exception as e:
215 raise CommandError("Container '%s' does not exist" % container_dn, e)
217 found = False
218 gpo_dn = str(get_gpo_dn(samdb, gpo))
219 if 'gPLink' in msg:
220 gplist = parse_gplink(str(msg['gPLink'][0]))
221 for g in gplist:
222 if g['dn'].lower() == gpo_dn.lower():
223 gplist.remove(g)
224 found = True
225 break
226 else:
227 raise CommandError("No GPO(s) linked to this container")
229 if not found:
230 raise CommandError("GPO '%s' not linked to this container" % gpo)
232 m = ldb.Message()
233 m.dn = container_dn
234 if gplist:
235 gplink_str = encode_gplink(gplist)
236 m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
237 else:
238 m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
239 try:
240 samdb.modify(m)
241 except Exception as e:
242 raise CommandError("Error removing GPO from container", e)
245 def parse_unc(unc):
246 """Parse UNC string into a hostname, a service, and a filepath"""
247 tmp = []
248 if unc.startswith('\\\\'):
249 tmp = unc[2:].split('\\', 2)
250 elif unc.startswith('//'):
251 tmp = unc[2:].split('/', 2)
253 if len(tmp) != 3:
254 raise ValueError("Invalid UNC string: %s" % unc)
256 return tmp
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.
277 return GPParser()
278 if re.match(r'.*\.ini$', name, flags=flags):
279 return GPIniParser()
280 if re.match(r'.*\.pol$', name, flags=flags):
281 return GPPolParser()
282 if re.match(r'.*\.aas$', name, flags=flags):
283 return GPAasParser()
285 return GPParser()
288 def backup_directory_remote_to_local(conn, remotedir, localdir):
289 SUFFIX = '.SAMBABACKUP'
290 if not os.path.isdir(localdir):
291 os.mkdir(localdir)
292 r_dirs = [ remotedir ]
293 l_dirs = [ localdir ]
294 while r_dirs:
295 r_dir = r_dirs.pop()
296 l_dir = l_dirs.pop()
298 dirlist = conn.list(r_dir, attribs=attr_flags)
299 dirlist.sort(key=lambda x : x['name'])
300 for e in dirlist:
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)
307 os.mkdir(l_name)
308 else:
309 data = conn.loadfile(r_name)
310 with open(l_name + SUFFIX, 'wb') as f:
311 f.write(data)
313 parser = find_parser(e['name'])
314 parser.parse(data)
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):
326 os.mkdir(localdir)
327 r_dirs = [remotedir]
328 l_dirs = [localdir]
329 while r_dirs:
330 r_dir = r_dirs.pop()
331 l_dir = l_dirs.pop()
333 dirlist = conn.list(r_dir, attribs=attr_flags)
334 dirlist.sort(key=lambda x : x['name'])
335 for e in dirlist:
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)
342 os.mkdir(l_name)
343 else:
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)
353 l_dirs = [localdir]
354 r_dirs = [remotedir]
355 while l_dirs:
356 l_dir = l_dirs.pop()
357 r_dir = r_dirs.pop()
359 dirlist = os.listdir(l_dir)
360 dirlist.sort()
361 for e in dirlist:
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)
368 try:
369 conn.mkdir(r_name)
370 except NTSTATUSError:
371 if not ignore_existing_dir:
372 raise
373 else:
374 if keep_existing_files:
375 try:
376 conn.loadfile(r_name)
377 continue
378 except NTSTATUSError:
379 pass
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
393 will be created.
395 If --tmpdir is not used, a temporary directory is securely created.
397 if tmpdir is None:
398 tmpdir = tempfile.mkdtemp()
399 print("Using temporary directory %s (use --tmpdir to change)" % tmpdir,
400 file=self.outf)
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):
407 os.mkdir(localdir)
409 gpodir = os.path.join(localdir, gpo)
410 if os.path.isdir(gpodir):
411 raise CommandError(
412 "GPO directory '%s' already exists, refusing to overwrite" % gpodir)
414 try:
415 os.mkdir(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"""
423 try:
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):
432 """List all GPOs."""
434 synopsis = "%prog [options]"
436 takes_optiongroups = {
437 "sambaopts": options.SambaOptions,
438 "versionopts": options.VersionOptions,
439 "credopts": options.CredentialsOptions,
442 takes_options = [
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)
454 self.samdb_connect()
456 msg = get_gpo_info(self.samdb, None)
458 for m in msg:
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,
480 takes_options = [
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)
492 self.samdb_connect()
494 try:
495 msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
496 (ldb.binary_encode(accountname), ldb.binary_encode(accountname)))
497 user_dn = msg[0].dn
498 except Exception:
499 raise CommandError("Failed to find account %s" % accountname)
501 # check if its a computer account
502 try:
503 msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
504 is_computer = 'computer' in msg['objectClass']
505 except Exception:
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
520 gpos = []
522 inherit = True
523 dn = ldb.Dn(self.samdb, str(user_dn)).parent()
524 while True:
525 msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
526 if 'gPLink' in msg:
527 glist = parse_gplink(str(msg['gPLink'][0]))
528 for g in glist:
529 if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
530 continue
531 if g['options'] & dsdb.GPLINK_OPT_DISABLE:
532 continue
534 try:
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)
544 except Exception:
545 self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
546 g['dn'])
547 continue
549 try:
550 samba.security.access_check(secdesc, token,
551 security.SEC_STD_READ_CONTROL |
552 security.SEC_ADS_LIST |
553 security.SEC_ADS_READ_PROP)
554 except RuntimeError:
555 self.outf.write("Failed access check on %s\n" % msg.dn)
556 continue
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):
561 continue
562 if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
563 continue
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:
569 inherit = False
571 if dn == self.samdb.get_default_basedn():
572 break
573 dn = dn.parent()
575 if is_computer:
576 msg_str = 'computer'
577 else:
578 msg_str = 'user'
580 self.outf.write("GPOs for %s %s\n" % (msg_str, accountname))
581 for g in gpos:
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,
596 takes_args = ['gpo']
598 takes_options = [
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://'):
609 dc_hostname = H[7:]
610 self.url = H
611 else:
612 dc_hostname = netcmd_finddc(self.lp, self.creds)
613 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
615 self.samdb_connect()
617 try:
618 msg = get_gpo_info(self.samdb, gpo)[0]
619 except Exception:
620 raise CommandError("GPO '%s' does not exist" % gpo)
622 try:
623 secdesc_ndr = msg['nTSecurityDescriptor'][0]
624 secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
625 secdesc_sddl = secdesc.as_sddl()
626 except Exception:
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)
641 # SMB connect to DC
642 conn = smb_connection(dc_hostname,
643 'sysvol',
644 lp=self.lp,
645 creds=self.creds)
647 realm = self.lp.get('realm')
648 pol_file = '\\'.join([realm.lower(), 'Policies', gpo,
649 '%s\\Registry.pol'])
650 policy_defs = []
651 for policy_class in ['MACHINE', 'USER']:
652 try:
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")
663 raise
665 for entry in pol_data.entries:
666 if entry.valuename == "**delvals.":
667 continue
668 defs = {}
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')
679 else:
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.
693 Example json_input:
696 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
697 "valuename": "StartPage",
698 "class": "USER",
699 "type": "REG_SZ",
700 "data": "homepage"
703 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
704 "valuename": "URL",
705 "class": "USER",
706 "type": "REG_SZ",
707 "data": "google.com"
710 "keyname": "Software\\Microsoft\\Internet Explorer\\Toolbar",
711 "valuename": "IEToolbar",
712 "class": "USER",
713 "type": "REG_BINARY",
714 "data": [0]
717 "keyname": "Software\\Policies\\Microsoft\\InputPersonalization",
718 "valuename": "RestrictImplicitTextCollection",
719 "class": "USER",
720 "type": "REG_DWORD",
721 "data": 1
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,
746 takes_args = ['gpo']
748 takes_options = [
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,
764 machine_exts=None,
765 user_exts=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}']
771 if content is None:
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)
776 else:
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)
782 self.samdb_connect()
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')
788 try:
789 if replace:
790 reg.replace_s(policy_defs)
791 else:
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")
797 else:
798 raise
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.
807 Example json_input:
810 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
811 "valuename": "StartPage",
812 "class": "USER",
815 "keyname": "Software\\Policies\\Mozilla\\Firefox\\Homepage",
816 "valuename": "URL",
817 "class": "USER",
820 "keyname": "Software\\Microsoft\\Internet Explorer\\Toolbar",
821 "valuename": "IEToolbar",
822 "class": "USER"
825 "keyname": "Software\\Policies\\Microsoft\\InputPersonalization",
826 "valuename": "RestrictImplicitTextCollection",
827 "class": "USER"
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,
842 takes_args = ['gpo']
844 takes_options = [
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:
858 machine_exts = []
859 if user_exts is None:
860 user_exts = []
861 if content 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)
866 else:
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)
872 self.samdb_connect()
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')
878 try:
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")
884 else:
885 raise
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']
901 takes_options = [
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,
906 versionopts=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)
913 self.samdb_connect()
915 try:
916 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
917 expression="(objectClass=*)",
918 attrs=['gPLink'])[0]
919 except Exception:
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]))
925 for g in gplist:
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")
931 else:
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']
948 takes_options = [
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)
964 self.samdb_connect()
966 gplink_options = 0
967 if disabled:
968 gplink_options |= dsdb.GPLINK_OPT_DISABLE
969 if enforced:
970 gplink_options |= dsdb.GPLINK_OPT_ENFORCE
972 # Check if valid GPO DN
973 try:
974 get_gpo_info(self.samdb, gpo=gpo)[0]
975 except Exception:
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
980 try:
981 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
982 expression="(objectClass=*)",
983 attrs=['gPLink'])[0]
984 except Exception:
985 raise CommandError("Container '%s' does not exist" % container_dn)
987 # Update existing GPlinks or Add new one
988 existing_gplink = False
989 if 'gPLink' in msg:
990 gplist = parse_gplink(str(msg['gPLink'][0]))
991 existing_gplink = True
992 found = False
993 for g in gplist:
994 if g['dn'].lower() == gpo_dn.lower():
995 g['options'] = gplink_options
996 found = True
997 break
998 if found:
999 raise CommandError("GPO '%s' already linked to this container" % gpo)
1000 else:
1001 gplist.insert(0, {'dn': gpo_dn, 'options': gplink_options})
1002 else:
1003 gplist = []
1004 gplist.append({'dn': gpo_dn, 'options': gplink_options})
1006 gplink_str = encode_gplink(gplist)
1008 m = ldb.Message()
1009 m.dn = ldb.Dn(self.samdb, container_dn)
1011 if existing_gplink:
1012 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
1013 else:
1014 m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
1016 try:
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']
1038 takes_options = [
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,
1043 versionopts=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
1053 try:
1054 get_gpo_info(self.samdb, gpo=gpo)[0]
1055 except Exception:
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']
1077 takes_options = [
1078 Option("-H", help="LDB URL for database or target server", type=str)
1081 def run(self, gpo, H=None, sambaopts=None, credopts=None,
1082 versionopts=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)
1092 if len(msg):
1093 self.outf.write("Container(s) using GPO %s\n" % gpo)
1094 for m in msg:
1095 self.outf.write(" DN: %s\n" % m['dn'])
1096 else:
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']
1113 takes_options = [
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,
1118 versionopts=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()
1127 try:
1128 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
1129 expression="(objectClass=*)",
1130 attrs=['gPOptions'])[0]
1131 except Exception:
1132 raise CommandError("Container '%s' does not exist" % container_dn)
1134 inheritance = 0
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")
1140 else:
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']
1157 takes_options = [
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,
1162 versionopts=None):
1164 if inherit_state.lower() == 'block':
1165 inheritance = dsdb.GPO_BLOCK_INHERITANCE
1166 elif inherit_state.lower() == 'inherit':
1167 inheritance = dsdb.GPO_INHERIT
1168 else:
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()
1177 try:
1178 msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
1179 expression="(objectClass=*)",
1180 attrs=['gPOptions'])[0]
1181 except Exception:
1182 raise CommandError("Container '%s' does not exist" % container_dn)
1184 m = ldb.Message()
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')
1189 else:
1190 m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
1192 try:
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']
1211 takes_options = [
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://'):
1223 dc_hostname = H[7:]
1224 self.url = H
1225 else:
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()
1230 try:
1231 msg = get_gpo_info(self.samdb, gpo)[0]
1232 except Exception:
1233 raise CommandError("GPO '%s' does not exist" % gpo)
1235 # verify UNC path
1236 unc = str(msg['gPCFileSysPath'][0])
1237 try:
1238 [dom_name, service, sharepath] = parse_unc(unc)
1239 except ValueError:
1240 raise CommandError("Invalid GPO path (%s)" % unc)
1242 # SMB connect to DC
1243 conn = smb_connection(dc_hostname, service, lp=self.lp,
1244 creds=self.creds)
1246 # Copy GPT
1247 tmpdir, gpodir = self.construct_tmpdir(tmpdir, gpo)
1249 try:
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):
1258 """Backup a GPO."""
1260 synopsis = "%prog <gpo> [options]"
1262 takes_optiongroups = {
1263 "sambaopts": options.SambaOptions,
1264 "versionopts": options.VersionOptions,
1265 "credopts": options.CredentialsOptions,
1268 takes_args = ['gpo']
1270 takes_options = [
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://'):
1287 dc_hostname = H[7:]
1288 self.url = H
1289 else:
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()
1294 try:
1295 msg = get_gpo_info(self.samdb, gpo)[0]
1296 except Exception:
1297 raise CommandError("GPO '%s' does not exist" % gpo)
1299 # verify UNC path
1300 unc = str(msg['gPCFileSysPath'][0])
1301 try:
1302 [dom_name, service, sharepath] = parse_unc(unc)
1303 except ValueError:
1304 raise CommandError("Invalid GPO path (%s)" % unc)
1306 # SMB connect to DC
1307 conn = smb_connection(dc_hostname, service, lp=self.lp,
1308 creds=self.creds)
1310 # Copy GPT
1311 tmpdir, gpodir = self.construct_tmpdir(tmpdir, gpo)
1313 try:
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)
1321 if generalize:
1322 self.outf.write('\nAttempting to generalize XML entities:\n')
1323 entities = cmd_backup.generalize_xml_entities(self.outf, gpodir,
1324 gpodir)
1325 import operator
1326 ents = "".join('<!ENTITY {} "{}\n">'.format(ent[1].strip('&;'), ent[0]) \
1327 for ent in sorted(entities.items(), key=operator.itemgetter(1)))
1329 if ent_file:
1330 with open(ent_file, 'w') as f:
1331 f.write(ents)
1332 self.outf.write('Entities successfully written to %s\n' %
1333 ent_file)
1334 else:
1335 self.outf.write('\nEntities:\n')
1336 self.outf.write(ents)
1338 # Backup the enabled GPO extension names
1339 for ext in ('gPCMachineExtensionNames', 'gPCUserExtensionNames'):
1340 if ext in msg:
1341 with open(os.path.join(gpodir, ext + '.SAMBAEXT'), 'wb') as f:
1342 f.write(msg[ext][0])
1344 @staticmethod
1345 def generalize_xml_entities(outf, sourcedir, targetdir):
1346 entities = {}
1348 if not os.path.exists(targetdir):
1349 os.mkdir(targetdir)
1351 l_dirs = [ sourcedir ]
1352 r_dirs = [ targetdir ]
1353 while l_dirs:
1354 l_dir = l_dirs.pop()
1355 r_dir = r_dirs.pop()
1357 dirlist = os.listdir(l_dir)
1358 dirlist.sort()
1359 for e in dirlist:
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):
1367 os.mkdir(r_name)
1368 else:
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)
1376 try:
1377 with open(l_name, 'r') as ltemp:
1378 data = ltemp.read()
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)
1385 else:
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)
1394 return entities
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']
1410 takes_options = [
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,
1416 versionopts=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://'):
1425 dc_hostname = H[7:]
1426 self.url = H
1427 flags = (nbt.NBT_SERVER_LDAP |
1428 nbt.NBT_SERVER_DS |
1429 nbt.NBT_SERVER_WRITABLE)
1430 cldap_ret = net.finddc(address=dc_hostname, flags=flags)
1431 else:
1432 flags = (nbt.NBT_SERVER_LDAP |
1433 nbt.NBT_SERVER_DS |
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)
1442 if msg.count > 0:
1443 raise CommandError("A GPO already existing with name '%s'" % displayname)
1445 # Create new GUID
1446 guid = str(uuid.uuid4())
1447 gpo = "{%s}" % guid.upper()
1449 self.gpo_name = gpo
1451 realm = cldap_ret.dns_domain
1452 unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
1454 # Create GPT
1455 self.tmpdir, gpodir = self.construct_tmpdir(tmpdir, gpo)
1456 self.gpodir = gpodir
1458 try:
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,
1470 creds=self.creds)
1472 self.conn = conn
1474 self.samdb.transaction_start()
1475 try:
1476 # Add cn=<guid>
1477 gpo_dn = get_gpo_dn(self.samdb, gpo)
1479 m = ldb.Message()
1480 m.dn = gpo_dn
1481 m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
1482 self.samdb.add(m)
1484 # Add cn=User,cn=<guid>
1485 m = ldb.Message()
1486 m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
1487 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1488 self.samdb.add(m)
1490 # Add cn=Machine,cn=<guid>
1491 m = ldb.Message()
1492 m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
1493 m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
1494 self.samdb.add(m)
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)
1512 # Set ACL
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)
1522 m = ldb.Message()
1523 m.dn = gpo_dn
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)
1531 except Exception:
1532 self.samdb.transaction_cancel()
1533 raise
1534 else:
1535 self.samdb.transaction_commit()
1537 if tmpdir is None:
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']
1557 takes_options = [
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):
1569 os.mkdir(targetdir)
1571 l_dirs = [ sourcedir ]
1572 r_dirs = [ targetdir ]
1573 while l_dirs:
1574 l_dir = l_dirs.pop()
1575 r_dir = r_dirs.pop()
1577 dirlist = os.listdir(l_dir)
1578 dirlist.sort()
1579 for e in dirlist:
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):
1587 os.mkdir(r_name)
1588 else:
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)
1596 try:
1597 with open(l_name, 'r') as ltemp:
1598 data = ltemp.read()
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))
1608 else:
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')
1622 except:
1623 import traceback
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):
1636 dtd_header = ''
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" %
1649 entities)
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)
1665 try:
1666 if tmpdir is None:
1667 # Create GPT
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,
1673 dtd_header)
1675 keep_new_files = not restore_metadata
1677 # Copy GPO files over SMB
1678 copy_directory_local_to_remote(self.conn, self.gpodir,
1679 self.sharepath,
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:
1690 data = f.read()
1692 m = ldb.Message()
1693 m.dn = gpo_dn
1694 m[ext] = ldb.MessageElement(data, ldb.FLAG_MOD_REPLACE,
1695 ext)
1697 self.samdb.modify(m)
1699 if tmpdir is None:
1700 # Without --tmpdir, we created one in /tmp/. It must go.
1701 shutil.rmtree(self.tmpdir)
1703 except Exception as e:
1704 import traceback
1705 traceback.print_exc()
1706 self.outf.write(str(e) + '\n')
1708 self.outf.write("Failed to restore GPO -- deleting...\n")
1709 cmd = cmd_del()
1710 cmd.run(self.gpo_name, H, sambaopts, credopts, versionopts)
1712 raise CommandError("Failed to restore: %s" % e)
1715 class cmd_del(GPOCommand):
1716 """Delete a GPO."""
1718 synopsis = "%prog <gpo> [options]"
1720 takes_optiongroups = {
1721 "sambaopts": options.SambaOptions,
1722 "versionopts": options.VersionOptions,
1723 "credopts": options.CredentialsOptions,
1726 takes_args = ['gpo']
1728 takes_options = [
1729 Option("-H", help="LDB URL for database or target server", type=str),
1732 def run(self, gpo, H=None, sambaopts=None, credopts=None,
1733 versionopts=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://'):
1740 dc_hostname = H[7:]
1741 self.url = H
1742 else:
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
1749 try:
1750 msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1751 unc_path = str(msg['gPCFileSysPath'][0])
1752 except Exception:
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,
1758 creds=self.creds)
1760 self.samdb.transaction_start()
1761 try:
1762 # Check for existing links
1763 msg = get_gpo_containers(self.samdb, gpo)
1765 if len(msg):
1766 self.outf.write("GPO %s is linked to containers\n" % gpo)
1767 for m in msg:
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)
1777 # Remove GPO files
1778 conn.deltree(sharepath)
1780 except Exception:
1781 self.samdb.transaction_cancel()
1782 raise
1783 else:
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,
1800 takes_options = [
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://'):
1814 dc_hostname = H[7:]
1815 self.url = H
1816 else:
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)
1824 for m in msg:
1825 # verify UNC path
1826 unc = str(m['gPCFileSysPath'][0])
1827 try:
1828 [dom_name, service, sharepath] = parse_unc(unc)
1829 except ValueError:
1830 raise CommandError("Invalid GPO path (%s)" % unc)
1832 # SMB connect to DC
1833 conn = smb_connection(dc_hostname, service, lp=self.lp,
1834 creds=self.creds)
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,
1863 takes_options = [
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,
1871 admx_dir=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://'):
1877 dc_hostname = H[7:]
1878 self.url = H
1879 else:
1880 dc_hostname = netcmd_finddc(self.lp, self.creds)
1881 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1883 # SMB connect to DC
1884 conn = smb_connection(dc_hostname,
1885 'sysvol',
1886 lp=self.lp,
1887 creds=self.creds)
1889 smb_dir = '\\'.join([self.lp.get('realm').lower(),
1890 'Policies', 'PolicyDefinitions'])
1891 try:
1892 conn.mkdir(smb_dir)
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:
1898 raise
1900 for dirname, dirs, files in os.walk(admx_dir):
1901 for fname in files:
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])
1906 try:
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:
1913 raise
1914 with open(full_path, 'rb') as f:
1915 try:
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.
1941 Example:
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,
1956 takes_options = [
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://'):
1972 dc_hostname = H[7:]
1973 self.url = H
1974 else:
1975 dc_hostname = netcmd_finddc(self.lp, self.creds)
1976 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1978 # SMB connect to DC
1979 conn = smb_connection(dc_hostname,
1980 'sysvol',
1981 lp=self.lp,
1982 creds=self.creds)
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'])
1992 try:
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(),
2003 'policysetting')
2004 pv = ET.SubElement(policysetting, 'version')
2005 pv.text = '1'
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")
2018 else:
2019 raise
2021 sudoers_entry = ET.SubElement(data, 'sudoers_entry')
2022 if passwd:
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')
2031 principal.text = u
2032 principal.attrib['type'] = 'user'
2033 if groups is not None:
2034 for g in groups.split():
2035 principal = ET.SubElement(listelement, 'principal')
2036 principal.text = g
2037 principal.attrib['type'] = 'group'
2039 out = BytesIO()
2040 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
2041 out.seek(0)
2042 try:
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")
2050 raise
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.
2057 Example:
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,
2069 takes_options = [
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://'):
2082 dc_hostname = H[7:]
2083 self.url = H
2084 else:
2085 dc_hostname = netcmd_finddc(self.lp, self.creds)
2086 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2088 # SMB connect to DC
2089 conn = smb_connection(dc_hostname,
2090 'sysvol',
2091 lp=self.lp,
2092 creds=self.creds)
2094 realm = self.lp.get('realm')
2095 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
2096 'MACHINE\\VGP\\VTLA\\Sudo',
2097 'SudoersConfiguration\\manifest.xml'])
2098 try:
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
2105 xml_data = None
2106 elif e.args[0] == NT_STATUS_ACCESS_DENIED:
2107 raise CommandError("The authenticated user does "
2108 "not have sufficient privileges")
2109 else:
2110 raise
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')
2119 principals = []
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])
2125 else:
2126 uname = 'ALL'
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'])
2134 try:
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")
2144 raise
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.
2158 Example:
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,
2170 takes_options = [
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://'):
2183 dc_hostname = H[7:]
2184 self.url = H
2185 else:
2186 dc_hostname = netcmd_finddc(self.lp, self.creds)
2187 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2189 # SMB connect to DC
2190 conn = smb_connection(dc_hostname,
2191 'sysvol',
2192 lp=self.lp,
2193 creds=self.creds)
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'])
2203 try:
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]:
2211 data = None
2212 elif e.args[0] == NT_STATUS_ACCESS_DENIED:
2213 raise CommandError("The authenticated user does "
2214 "not have sufficient privileges")
2215 else:
2216 raise
2218 pol_file = '\\'.join([realm.lower(), 'Policies', gpo,
2219 'MACHINE\\Registry.pol'])
2220 try:
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]:
2226 pol_data = None
2227 elif e.args[0] == NT_STATUS_ACCESS_DENIED:
2228 raise CommandError("The authenticated user does "
2229 "not have sufficient privileges")
2230 else:
2231 raise
2233 entries = {}
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')
2238 principals = []
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])
2244 else:
2245 uname = 'ALL'
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)
2249 entries[p] = e
2251 if entry in entries.keys():
2252 data.remove(entries[entry])
2254 out = BytesIO()
2255 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
2256 out.seek(0)
2257 try:
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")
2265 raise
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
2271 try:
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")
2278 raise
2279 else:
2280 raise CommandError("Cannot remove '%s' because it does not exist" %
2281 entry)
2283 class cmd_sudoers(SuperCommand):
2284 """Manage Sudoers Group Policy Objects"""
2285 subcommands = {}
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.
2297 Example:
2298 samba-tool gpo manage security set {31B2F340-016D-11D2-945F-00C04FB984F9} MaxTicketAge 10
2300 Possible policies:
2301 MaxTicketAge Maximum lifetime for user ticket
2302 Defined in hours
2304 MaxServiceAge Maximum lifetime for service ticket
2305 Defined in minutes
2307 MaxRenewAge Maximum lifetime for user ticket renewal
2308 Defined in minutes
2310 MinimumPasswordAge Minimum password age
2311 Defined in days
2313 MaximumPasswordAge Maximum password age
2314 Defined in days
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,
2331 takes_options = [
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://'):
2345 dc_hostname = H[7:]
2346 self.url = H
2347 else:
2348 dc_hostname = netcmd_finddc(self.lp, self.creds)
2349 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2351 # SMB connect to DC
2352 conn = smb_connection(dc_hostname,
2353 'sysvol',
2354 lp=self.lp,
2355 creds=self.creds)
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'])
2364 try:
2365 inf_data = ConfigParser(interpolation=None)
2366 inf_data.optionxform=str
2367 raw = conn.loadfile(inf_file)
2368 try:
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]:
2379 raise
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)
2395 else:
2396 inf_data.remove_option(section, policy)
2397 if len(inf_data.options(section)) == 0:
2398 inf_data.remove_section(section)
2400 out = StringIO()
2401 inf_data.write(out)
2402 try:
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")
2410 else:
2411 raise
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.
2419 Example:
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,
2431 takes_options = [
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://'):
2444 dc_hostname = H[7:]
2445 self.url = H
2446 else:
2447 dc_hostname = netcmd_finddc(self.lp, self.creds)
2448 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2450 # SMB connect to DC
2451 conn = smb_connection(dc_hostname,
2452 'sysvol',
2453 lp=self.lp,
2454 creds=self.creds)
2456 realm = self.lp.get('realm')
2457 inf_file = '\\'.join([realm.lower(), 'Policies', gpo,
2458 'MACHINE\\Microsoft\\Windows NT\\SecEdit\\GptTmpl.inf'])
2459 try:
2460 inf_data = ConfigParser(interpolation=None)
2461 inf_data.optionxform=str
2462 raw = conn.loadfile(inf_file)
2463 try:
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")
2475 raise
2477 for section in inf_data.sections():
2478 if section not in ['Kerberos Policy', 'System Access']:
2479 continue
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"""
2485 subcommands = {}
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.
2494 Example:
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,
2506 takes_options = [
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://'):
2519 dc_hostname = H[7:]
2520 self.url = H
2521 else:
2522 dc_hostname = netcmd_finddc(self.lp, self.creds)
2523 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2525 # SMB connect to DC
2526 conn = smb_connection(dc_hostname,
2527 'sysvol',
2528 lp=self.lp,
2529 creds=self.creds)
2531 realm = self.lp.get('realm')
2532 pol_file = '\\'.join([realm.lower(), 'Policies', gpo,
2533 'MACHINE\\Registry.pol'])
2534 try:
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")
2544 raise
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.
2560 Example:
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,
2572 takes_options = [
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,
2580 versionopts=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://'):
2586 dc_hostname = H[7:]
2587 self.url = H
2588 else:
2589 dc_hostname = netcmd_finddc(self.lp, self.creds)
2590 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2592 # SMB connect to DC
2593 conn = smb_connection(dc_hostname,
2594 'sysvol',
2595 lp=self.lp,
2596 creds=self.creds)
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'])
2604 try:
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")
2614 else:
2615 raise
2617 if value is None:
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)
2625 else:
2626 if get_string(value).lower() in ['yes', 'true', '1']:
2627 etype = 4
2628 val = 1
2629 elif get_string(value).lower() in ['no', 'false', '0']:
2630 etype = 4
2631 val = 0
2632 elif get_string(value).isnumeric():
2633 etype = 4
2634 val = int(get_string(value))
2635 else:
2636 etype = 1
2637 val = get_bytes(value)
2638 e = preg.entry()
2639 e.keyname = b'Software\\Policies\\Samba\\smb_conf'
2640 e.valuename = get_bytes(setting)
2641 e.type = etype
2642 e.data = val
2643 entries = list(pol_data.entries)
2644 entries.append(e)
2645 pol_data.entries = entries
2646 pol_data.num_entries = len(entries)
2648 try:
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")
2656 raise
2658 class cmd_smb_conf(SuperCommand):
2659 """Manage smb.conf Group Policy Objects"""
2660 subcommands = {}
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.
2669 Example:
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,
2681 takes_options = [
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://'):
2694 dc_hostname = H[7:]
2695 self.url = H
2696 else:
2697 dc_hostname = netcmd_finddc(self.lp, self.creds)
2698 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2700 # SMB connect to DC
2701 conn = smb_connection(dc_hostname,
2702 'sysvol',
2703 lp=self.lp,
2704 creds=self.creds)
2706 realm = self.lp.get('realm')
2707 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
2708 'MACHINE\\VGP\\VTLA\\Unix',
2709 'Symlink\\manifest.xml'])
2710 try:
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")
2720 raise
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.
2734 Example:
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,
2746 takes_options = [
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,
2754 versionopts=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://'):
2760 dc_hostname = H[7:]
2761 self.url = H
2762 else:
2763 dc_hostname = netcmd_finddc(self.lp, self.creds)
2764 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2766 # SMB connect to DC
2767 conn = smb_connection(dc_hostname,
2768 'sysvol',
2769 lp=self.lp,
2770 creds=self.creds)
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'])
2779 try:
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(),
2790 'policysetting')
2791 pv = ET.SubElement(policysetting, 'version')
2792 pv.text = '1'
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")
2801 else:
2802 raise
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
2810 out = BytesIO()
2811 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
2812 out.seek(0)
2813 try:
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")
2821 raise
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
2827 clients.
2829 Example:
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,
2841 takes_options = [
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,
2849 versionopts=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://'):
2855 dc_hostname = H[7:]
2856 self.url = H
2857 else:
2858 dc_hostname = netcmd_finddc(self.lp, self.creds)
2859 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2861 # SMB connect to DC
2862 conn = smb_connection(dc_hostname,
2863 'sysvol',
2864 lp=self.lp,
2865 creds=self.creds)
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'])
2874 try:
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")
2887 else:
2888 raise
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)
2895 break
2896 else:
2897 raise CommandError("Cannot remove link from '%s' to '%s' "
2898 "because it does not exist" % source, target)
2901 out = BytesIO()
2902 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
2903 out.seek(0)
2904 try:
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")
2912 raise
2914 class cmd_symlink(SuperCommand):
2915 """Manage symlink Group Policy Objects"""
2916 subcommands = {}
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.
2926 Example:
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,
2938 takes_options = [
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://'):
2951 dc_hostname = H[7:]
2952 self.url = H
2953 else:
2954 dc_hostname = netcmd_finddc(self.lp, self.creds)
2955 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
2957 # SMB connect to DC
2958 conn = smb_connection(dc_hostname,
2959 'sysvol',
2960 lp=self.lp,
2961 creds=self.creds)
2963 realm = self.lp.get('realm')
2964 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
2965 'MACHINE\\VGP\\VTLA\\Unix',
2966 'Files\\manifest.xml'])
2967 try:
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")
2977 raise
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.
2996 Example:
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,
3008 takes_options = [
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://'):
3025 dc_hostname = H[7:]
3026 self.url = H
3027 else:
3028 dc_hostname = netcmd_finddc(self.lp, self.creds)
3029 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3031 # SMB connect to DC
3032 conn = smb_connection(dc_hostname,
3033 'sysvol',
3034 lp=self.lp,
3035 creds=self.creds)
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'])
3044 try:
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(),
3055 'policysetting')
3056 pv = ET.SubElement(policysetting, 'version')
3057 pv.text = '1'
3058 name = ET.SubElement(policysetting, 'name')
3059 name.text = 'Files'
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")
3066 else:
3067 raise
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')
3088 out = BytesIO()
3089 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3090 out.seek(0)
3091 source_data = open(source, 'rb').read()
3092 sysvol_source = '\\'.join([vgp_dir, os.path.basename(source)])
3093 try:
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")
3102 raise
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.
3109 Example:
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,
3121 takes_options = [
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,
3129 versionopts=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://'):
3135 dc_hostname = H[7:]
3136 self.url = H
3137 else:
3138 dc_hostname = netcmd_finddc(self.lp, self.creds)
3139 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3141 # SMB connect to DC
3142 conn = smb_connection(dc_hostname,
3143 'sysvol',
3144 lp=self.lp,
3145 creds=self.creds)
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'])
3154 try:
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")
3167 else:
3168 raise
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])
3175 conn.unlink(source)
3176 data.remove(file_properties)
3177 break
3178 else:
3179 raise CommandError("Cannot remove file '%s' "
3180 "because it does not exist" % target)
3183 out = BytesIO()
3184 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3185 out.seek(0)
3186 try:
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")
3194 raise
3196 class cmd_files(SuperCommand):
3197 """Manage Files Group Policy Objects"""
3198 subcommands = {}
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.
3208 Example:
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,
3220 takes_options = [
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://'):
3233 dc_hostname = H[7:]
3234 self.url = H
3235 else:
3236 dc_hostname = netcmd_finddc(self.lp, self.creds)
3237 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3239 # SMB connect to DC
3240 conn = smb_connection(dc_hostname,
3241 'sysvol',
3242 lp=self.lp,
3243 creds=self.creds)
3245 realm = self.lp.get('realm')
3246 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
3247 'MACHINE\\VGP\\VTLA\\SshCfg',
3248 'SshD\\manifest.xml'])
3249 try:
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")
3259 raise
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:
3266 continue
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.
3277 Example:
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,
3289 takes_options = [
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://'):
3303 dc_hostname = H[7:]
3304 self.url = H
3305 else:
3306 dc_hostname = netcmd_finddc(self.lp, self.creds)
3307 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3309 # SMB connect to DC
3310 conn = smb_connection(dc_hostname,
3311 'sysvol',
3312 lp=self.lp,
3313 creds=self.creds)
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'])
3322 try:
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(),
3334 'policysetting')
3335 pv = ET.SubElement(policysetting, 'version')
3336 pv.text = '1'
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")
3350 else:
3351 raise
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
3357 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
3362 else:
3363 keyvaluepair = ET.SubElement(configsection, 'keyvaluepair')
3364 key = ET.SubElement(keyvaluepair, 'key')
3365 key.text = setting
3366 dvalue = ET.SubElement(keyvaluepair, 'value')
3367 dvalue.text = value
3368 else:
3369 for configsection in configfile.findall('configsection'):
3370 if configsection.find('sectionname').text:
3371 continue # Ignore Quest SSH settings
3372 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])
3377 else:
3378 raise CommandError("Cannot remove '%s' because it does " \
3379 "not exist" % setting)
3381 out = BytesIO()
3382 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3383 out.seek(0)
3384 try:
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")
3392 raise
3394 class cmd_openssh(SuperCommand):
3395 """Manage OpenSSH Group Policy Objects"""
3396 subcommands = {}
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.
3405 Example:
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,
3417 takes_options = [
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://'):
3430 dc_hostname = H[7:]
3431 self.url = H
3432 else:
3433 dc_hostname = netcmd_finddc(self.lp, self.creds)
3434 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3436 # SMB connect to DC
3437 conn = smb_connection(dc_hostname,
3438 'sysvol',
3439 lp=self.lp,
3440 creds=self.creds)
3442 realm = self.lp.get('realm')
3443 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
3444 'MACHINE\\VGP\\VTLA\\Unix',
3445 'Scripts\\Startup\\manifest.xml'])
3446 try:
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")
3456 raise
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
3469 else:
3470 run_as = 'root'
3471 if parameters is not None:
3472 parameters = parameters.text
3473 else:
3474 parameters = ''
3475 self.outf.write('@reboot %s %s %s\n' % (run_as, script_path,
3476 parameters))
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.
3483 Example:
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,
3495 takes_options = [
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://'):
3514 dc_hostname = H[7:]
3515 self.url = H
3516 else:
3517 dc_hostname = netcmd_finddc(self.lp, self.creds)
3518 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3520 # SMB connect to DC
3521 conn = smb_connection(dc_hostname,
3522 'sysvol',
3523 lp=self.lp,
3524 creds=self.creds)
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'])
3533 try:
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(),
3544 'policysetting')
3545 pv = ET.SubElement(policysetting, 'version')
3546 pv.text = '1'
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")
3556 else:
3557 raise
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
3571 if run_once:
3572 ET.SubElement(listelement, 'run_once')
3574 out = BytesIO()
3575 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3576 out.seek(0)
3577 sysvol_script = '\\'.join([vgp_dir, os.path.basename(script)])
3578 try:
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")
3587 raise
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.
3594 Example:
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,
3606 takes_options = [
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,
3614 versionopts=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://'):
3620 dc_hostname = H[7:]
3621 self.url = H
3622 else:
3623 dc_hostname = netcmd_finddc(self.lp, self.creds)
3624 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3626 # SMB connect to DC
3627 conn = smb_connection(dc_hostname,
3628 'sysvol',
3629 lp=self.lp,
3630 creds=self.creds)
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'])
3639 try:
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")
3652 else:
3653 raise
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)
3659 break
3660 else:
3661 raise CommandError("Cannot remove script '%s' "
3662 "because it does not exist" % script)
3664 out = BytesIO()
3665 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3666 out.seek(0)
3667 try:
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")
3675 raise
3677 class cmd_startup(SuperCommand):
3678 """Manage Startup Scripts Group Policy Objects"""
3679 subcommands = {}
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"""
3686 subcommands = {}
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
3693 to winbind clients.
3695 Example:
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,
3707 takes_options = [
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://'):
3720 dc_hostname = H[7:]
3721 self.url = H
3722 else:
3723 dc_hostname = netcmd_finddc(self.lp, self.creds)
3724 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3726 # SMB connect to DC
3727 conn = smb_connection(dc_hostname,
3728 'sysvol',
3729 lp=self.lp,
3730 creds=self.creds)
3732 realm = self.lp.get('realm')
3733 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
3734 'MACHINE\\VGP\\VTLA\\Unix',
3735 'MOTD\\manifest.xml'])
3736 try:
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")
3746 raise
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.
3759 Example:
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,
3771 takes_options = [
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,
3779 versionopts=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://'):
3785 dc_hostname = H[7:]
3786 self.url = H
3787 else:
3788 dc_hostname = netcmd_finddc(self.lp, self.creds)
3789 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3791 # SMB connect to DC
3792 conn = smb_connection(dc_hostname,
3793 'sysvol',
3794 lp=self.lp,
3795 creds=self.creds)
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'])
3805 if value is None:
3806 conn.unlink(vgp_xml)
3807 reg.increment_gpt_ini(machine_changed=True)
3808 return
3810 try:
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(),
3819 'policysetting')
3820 pv = ET.SubElement(policysetting, 'version')
3821 pv.text = '1'
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")
3834 else:
3835 raise
3837 text = ET.SubElement(data, 'text')
3838 text.text = value
3840 out = BytesIO()
3841 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
3842 out.seek(0)
3843 try:
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")
3851 raise
3853 class cmd_motd(SuperCommand):
3854 """Manage Message of the Day Group Policy Objects"""
3855 subcommands = {}
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
3863 to winbind clients.
3865 Example:
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,
3877 takes_options = [
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://'):
3890 dc_hostname = H[7:]
3891 self.url = H
3892 else:
3893 dc_hostname = netcmd_finddc(self.lp, self.creds)
3894 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3896 # SMB connect to DC
3897 conn = smb_connection(dc_hostname,
3898 'sysvol',
3899 lp=self.lp,
3900 creds=self.creds)
3902 realm = self.lp.get('realm')
3903 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
3904 'MACHINE\\VGP\\VTLA\\Unix',
3905 'Issue\\manifest.xml'])
3906 try:
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")
3916 raise
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.
3929 Example:
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,
3941 takes_options = [
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,
3949 versionopts=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://'):
3955 dc_hostname = H[7:]
3956 self.url = H
3957 else:
3958 dc_hostname = netcmd_finddc(self.lp, self.creds)
3959 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
3961 # SMB connect to DC
3962 conn = smb_connection(dc_hostname,
3963 'sysvol',
3964 lp=self.lp,
3965 creds=self.creds)
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'])
3975 if value is None:
3976 conn.unlink(vgp_xml)
3977 reg.increment_gpt_ini(machine_changed=True)
3978 return
3980 try:
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(),
3989 'policysetting')
3990 pv = ET.SubElement(policysetting, 'version')
3991 pv.text = '1'
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")
4004 else:
4005 raise
4007 text = ET.SubElement(data, 'text')
4008 text.text = value
4010 out = BytesIO()
4011 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
4012 out.seek(0)
4013 try:
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")
4021 raise
4023 class cmd_issue(SuperCommand):
4024 """Manage Issue Group Policy Objects"""
4025 subcommands = {}
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.
4034 Example:
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,
4046 takes_options = [
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://'):
4059 dc_hostname = H[7:]
4060 self.url = H
4061 else:
4062 dc_hostname = netcmd_finddc(self.lp, self.creds)
4063 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
4065 # SMB connect to DC
4066 conn = smb_connection(dc_hostname,
4067 'sysvol',
4068 lp=self.lp,
4069 creds=self.creds)
4071 realm = self.lp.get('realm')
4072 vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo,
4073 'MACHINE\\VGP\\VTLA\\VAS',
4074 'HostAccessControl\\Allow\\manifest.xml'])
4075 try:
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")
4085 else:
4086 raise
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'])
4100 try:
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")
4110 else:
4111 raise
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.
4129 Example:
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,
4141 takes_options = [
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://'):
4155 dc_hostname = H[7:]
4156 self.url = H
4157 else:
4158 dc_hostname = netcmd_finddc(self.lp, self.creds)
4159 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
4161 # SMB connect to DC
4162 conn = smb_connection(dc_hostname,
4163 'sysvol',
4164 lp=self.lp,
4165 creds=self.creds)
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'])
4179 else:
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'])
4183 try:
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(),
4194 'policysetting')
4195 pv = ET.SubElement(policysetting, 'version')
4196 pv.text = '1'
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")
4207 else:
4208 raise
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',
4218 'samaccountname',
4219 'objectClass'])
4220 if len(res) == 0:
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
4244 out = BytesIO()
4245 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
4246 out.seek(0)
4247 try:
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")
4255 raise
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
4261 winbind clients.
4263 Example:
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,
4275 takes_options = [
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://'):
4289 dc_hostname = H[7:]
4290 self.url = H
4291 else:
4292 dc_hostname = netcmd_finddc(self.lp, self.creds)
4293 self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
4295 # SMB connect to DC
4296 conn = smb_connection(dc_hostname,
4297 'sysvol',
4298 lp=self.lp,
4299 creds=self.creds)
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'])
4313 else:
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'])
4317 try:
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")
4330 else:
4331 raise
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)
4340 break
4341 else:
4342 raise CommandError("Cannot remove %s entry because it does "
4343 "not exist" % etype)
4345 out = BytesIO()
4346 xml_data.write(out, encoding='UTF-8', xml_declaration=True)
4347 out.seek(0)
4348 try:
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")
4356 raise
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
4368 command.
4370 Example:
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,
4381 takes_options = [
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)
4402 if not ret:
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.
4410 Example:
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.
4442 Example:
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)
4459 if not ret:
4460 raise CommandError('Failed to unregister CSE "%s"' % guid)
4462 class cmd_cse(SuperCommand):
4463 """Manage Client Side Extensions"""
4464 subcommands = {}
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"""
4471 subcommands = {}
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"""
4478 subcommands = {}
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."""
4493 subcommands = {}
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()