s4:kdc: adjust formatting of samba_kdc_update_pac() documentation
[Samba.git] / python / samba / netcmd / ntacl.py
blobb6aaed7712a2251b91dacb94ae2a33292ce3ddf3
1 # Manipulate file NT ACLs
3 # Copyright Matthieu Patou 2010 <mat@matws.net>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from samba.credentials import DONT_USE_KERBEROS
20 import samba.getopt as options
21 from samba.dcerpc import security, idmap
22 from samba.ntacls import setntacl, getntacl, getdosinfo
23 from samba import Ldb
24 from samba.ndr import ndr_unpack, ndr_print
25 from samba.samdb import SamDB
26 from samba.samba3 import param as s3param, passdb
27 from samba import provision
28 from samba.auth_util import system_session_unix
29 import os
31 from samba.auth import system_session
33 from samba.netcmd import (
34 Command,
35 CommandError,
36 SuperCommand,
37 Option,
40 def get_local_domain_sid(lp):
41 is_ad_dc = False
42 server_role = lp.server_role()
43 if server_role == "ROLE_ACTIVE_DIRECTORY_DC":
44 is_ad_dc = True
46 s3conf = s3param.get_context()
47 s3conf.load(lp.configfile)
49 if is_ad_dc:
50 try:
51 samdb = SamDB(session_info=system_session(),
52 lp=lp)
53 except Exception as e:
54 raise CommandError("Unable to open samdb:", e)
55 # ensure we are using the right samba_dsdb passdb backend, no
56 # matter what
57 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
59 try:
60 if is_ad_dc:
61 domain_sid = security.dom_sid(samdb.domain_sid)
62 else:
63 domain_sid = passdb.get_domain_sid()
64 except:
65 raise CommandError("Unable to read domain SID from configuration "
66 "files")
67 return domain_sid
70 class cmd_ntacl_set(Command):
71 """Set ACLs on a file."""
73 synopsis = "%prog <acl> <path> [options]"
75 takes_optiongroups = {
76 "sambaopts": options.SambaOptions,
77 "credopts": options.CredentialsOptions,
78 "versionopts": options.VersionOptions,
81 takes_options = [
82 # --quiet is not used at all...
83 Option("-q", "--quiet", help=Option.SUPPRESS_HELP, action="store_true"),
84 Option("-v", "--verbose", help="Be verbose", action="store_true"),
85 Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
86 choices=["native", "tdb"]),
87 Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
88 Option("--use-ntvfs", help="Set the ACLs directly to the TDB or xattr for use with the ntvfs file server", action="store_true"),
89 Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server via the VFS layer", action="store_true"),
90 Option("--recursive", help="Set the ACLs for directories and their contents recursively", action="store_true"),
91 Option("--follow-symlinks", help="Follow symlinks", action="store_true"),
92 Option("--service", help="Name of the smb.conf service to use when applying the ACLs", type="string")
95 takes_args = ["acl", "path"]
97 def run(self, acl, path, use_ntvfs=False, use_s3fs=False,
98 quiet=False, verbose=False, xattr_backend=None, eadb_file=None,
99 credopts=None, sambaopts=None, versionopts=None,
100 recursive=False, follow_symlinks=False, service=None):
101 logger = self.get_logger()
102 lp = sambaopts.get_loadparm()
103 domain_sid = get_local_domain_sid(lp)
105 if not use_ntvfs and not use_s3fs:
106 use_ntvfs = "smb" in lp.get("server services")
107 elif use_s3fs:
108 use_ntvfs = False
110 def _setntacl_path(_path):
111 if not follow_symlinks and os.path.islink(_path):
112 if recursive:
113 self.outf.write("ignored symlink: %s\n" % _path)
114 return
115 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path))
117 if verbose:
118 if os.path.islink(_path):
119 self.outf.write("symlink: %s\n" % _path)
120 elif os.path.isdir(_path):
121 self.outf.write("dir: %s\n" % _path)
122 else:
123 self.outf.write("file: %s\n" % _path)
124 try:
125 return setntacl(lp,
126 _path,
127 acl,
128 str(domain_sid),
129 system_session_unix(),
130 xattr_backend,
131 eadb_file,
132 use_ntvfs=use_ntvfs,
133 service=service)
134 except Exception as e:
135 raise CommandError("Could not set acl for %s: %s" % (_path, e))
137 _setntacl_path(path)
139 if recursive and os.path.isdir(path):
140 for root, dirs, files in os.walk(path, followlinks=follow_symlinks):
141 for name in files:
142 _setntacl_path(os.path.join(root, name))
143 for name in dirs:
144 _setntacl_path(os.path.join(root, name))
146 if use_ntvfs:
147 logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
150 class cmd_dosinfo_get(Command):
151 """Get DOS info of a file from xattr."""
152 synopsis = "%prog <file> [options]"
154 takes_optiongroups = {
155 "sambaopts": options.SambaOptions,
156 "credopts": options.CredentialsOptions,
157 "versionopts": options.VersionOptions,
160 takes_args = ["file"]
162 def run(self, file, credopts=None, sambaopts=None, versionopts=None):
163 lp = sambaopts.get_loadparm()
164 s3conf = s3param.get_context()
165 s3conf.load(lp.configfile)
167 dosinfo = getdosinfo(lp, file)
168 if dosinfo:
169 self.outf.write(ndr_print(dosinfo))
172 class cmd_ntacl_get(Command):
173 """Get ACLs of a file."""
174 synopsis = "%prog <file> [options]"
176 takes_optiongroups = {
177 "sambaopts": options.SambaOptions,
178 "credopts": options.CredentialsOptions,
179 "versionopts": options.VersionOptions,
182 takes_options = [
183 Option("--as-sddl", help="Output ACL in the SDDL format", action="store_true"),
184 Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
185 choices=["native", "tdb"]),
186 Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
187 Option("--use-ntvfs", help="Get the ACLs directly from the TDB or xattr used with the ntvfs file server", action="store_true"),
188 Option("--use-s3fs", help="Get the ACLs for use via the VFS layer used by the default s3fs file server", action="store_true"),
189 Option("--service", help="Name of the smb.conf service to use when getting the ACLs", type="string")
192 takes_args = ["file"]
194 def run(self, file, use_ntvfs=False, use_s3fs=False,
195 as_sddl=False, xattr_backend=None, eadb_file=None,
196 credopts=None, sambaopts=None, versionopts=None,
197 service=None):
198 lp = sambaopts.get_loadparm()
199 domain_sid = get_local_domain_sid(lp)
201 if not use_ntvfs and not use_s3fs:
202 use_ntvfs = "smb" in lp.get("server services")
203 elif use_s3fs:
204 use_ntvfs = False
206 acl = getntacl(lp,
207 file,
208 system_session_unix(),
209 xattr_backend,
210 eadb_file,
211 direct_db_access=use_ntvfs,
212 service=service)
213 if as_sddl:
214 self.outf.write(acl.as_sddl(domain_sid) + "\n")
215 else:
216 self.outf.write(ndr_print(acl))
219 class cmd_ntacl_changedomsid(Command):
220 """Change the domain SID for ACLs"""
221 synopsis = "%prog <Orig-Domain-SID> <New-Domain-SID> <file> [options]"
223 takes_optiongroups = {
224 "sambaopts": options.SambaOptions,
227 takes_options = [
228 Option(
229 "--service",
230 help="Name of the smb.conf service to use",
231 type="string"),
232 Option(
233 "--use-ntvfs",
234 help=("Set the ACLs directly to the TDB or xattr for use with the "
235 "ntvfs file server"),
236 action="store_true"),
237 Option(
238 "--use-s3fs",
239 help=("Set the ACLs for use with the default s3fs file server via "
240 "the VFS layer"),
241 action="store_true"),
242 Option(
243 "--eadb-file",
244 help="Name of the tdb file where attributes are stored",
245 type="string"),
246 Option(
247 "--xattr-backend",
248 type="choice",
249 help="xattr backend type (native fs or tdb)",
250 choices=["native", "tdb"]),
251 Option(
252 "-r",
253 "--recursive",
254 help="Set the ACLs for directories and their contents recursively",
255 action="store_true"),
256 Option(
257 "--follow-symlinks",
258 help="Follow symlinks",
259 action="store_true"),
260 Option(
261 "-v",
262 "--verbose",
263 help="Be verbose",
264 action="store_true"),
267 takes_args = ["old_domain_sid", "new_domain_sid", "path"]
269 def run(self,
270 old_domain_sid_str,
271 new_domain_sid_str,
272 path,
273 use_ntvfs=False,
274 use_s3fs=False,
275 service=None,
276 xattr_backend=None,
277 eadb_file=None,
278 sambaopts=None,
279 recursive=False,
280 follow_symlinks=False,
281 verbose=False):
282 logger = self.get_logger()
283 lp = sambaopts.get_loadparm()
284 domain_sid = get_local_domain_sid(lp)
286 if not use_ntvfs and not use_s3fs:
287 use_ntvfs = "smb" in lp.get("server services")
288 elif use_s3fs:
289 use_ntvfs = False
291 if not use_ntvfs and not service:
292 raise CommandError(
293 "Must provide a share name with --service=<share>")
295 try:
296 old_domain_sid = security.dom_sid(old_domain_sid_str)
297 except Exception as e:
298 raise CommandError("Could not parse old sid %s: %s" %
299 (old_domain_sid_str, e))
301 try:
302 new_domain_sid = security.dom_sid(new_domain_sid_str)
303 except Exception as e:
304 raise CommandError("Could not parse old sid %s: %s" %
305 (new_domain_sid_str, e))
307 def changedom_sids(_path):
308 if not follow_symlinks and os.path.islink(_path):
309 if recursive:
310 self.outf.write("ignored symlink: %s\n" % _path)
311 return
312 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path))
314 if verbose:
315 if os.path.islink(_path):
316 self.outf.write("symlink: %s\n" % _path)
317 elif os.path.isdir(_path):
318 self.outf.write("dir: %s\n" % _path)
319 else:
320 self.outf.write("file: %s\n" % _path)
322 try:
323 acl = getntacl(lp,
324 _path,
325 system_session_unix(),
326 xattr_backend,
327 eadb_file,
328 direct_db_access=use_ntvfs,
329 service=service)
330 except Exception as e:
331 raise CommandError("Could not get acl for %s: %s" % (_path, e))
333 orig_sddl = acl.as_sddl(domain_sid)
334 if verbose:
335 self.outf.write("before:\n%s\n" % orig_sddl)
337 def replace_domain_sid(sid):
338 (dom, rid) = sid.split()
339 if dom == old_domain_sid:
340 return security.dom_sid("%s-%i" % (new_domain_sid, rid))
341 return sid
343 acl.owner_sid = replace_domain_sid(acl.owner_sid)
344 acl.group_sid = replace_domain_sid(acl.group_sid)
346 if acl.sacl:
347 for ace in acl.sacl.aces:
348 ace.trustee = replace_domain_sid(ace.trustee)
349 if acl.dacl:
350 for ace in acl.dacl.aces:
351 ace.trustee = replace_domain_sid(ace.trustee)
353 new_sddl = acl.as_sddl(domain_sid)
354 if verbose:
355 self.outf.write("after:\n%s\n" % new_sddl)
357 if orig_sddl == new_sddl:
358 if verbose:
359 self.outf.write("nothing to do\n")
360 return True
362 try:
363 setntacl(lp,
364 _path,
365 acl,
366 new_domain_sid,
367 system_session_unix(),
368 xattr_backend,
369 eadb_file,
370 use_ntvfs=use_ntvfs,
371 service=service)
372 except Exception as e:
373 raise CommandError("Could not set acl for %s: %s" % (_path, e))
375 def recursive_changedom_sids(_path):
376 for root, dirs, files in os.walk(_path, followlinks=follow_symlinks):
377 for f in files:
378 changedom_sids(os.path.join(root, f))
380 for d in dirs:
381 changedom_sids(os.path.join(root, d))
383 changedom_sids(path)
384 if recursive and os.path.isdir(path):
385 recursive_changedom_sids(path)
387 if use_ntvfs:
388 logger.warning("Please note that POSIX permissions have NOT been "
389 "changed, only the stored NT ACL.")
392 class cmd_ntacl_sysvolreset(Command):
393 """Reset sysvol ACLs to defaults (including correct ACLs on GPOs)."""
394 synopsis = "%prog <file> [options]"
396 takes_optiongroups = {
397 "sambaopts": options.SambaOptions,
398 "credopts": options.CredentialsOptions,
399 "versionopts": options.VersionOptions,
402 takes_options = [
403 Option("--use-ntvfs", help="Set the ACLs for use with the ntvfs file server", action="store_true"),
404 Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server", action="store_true")
407 def run(self, use_ntvfs=False, use_s3fs=False,
408 credopts=None, sambaopts=None, versionopts=None):
409 lp = sambaopts.get_loadparm()
410 creds = credopts.get_credentials(lp)
411 creds.set_kerberos_state(DONT_USE_KERBEROS)
412 logger = self.get_logger()
414 netlogon = lp.get("path", "netlogon")
415 sysvol = lp.get("path", "sysvol")
416 try:
417 samdb = SamDB(session_info=system_session(),
418 lp=lp)
419 except Exception as e:
420 raise CommandError("Unable to open samdb:", e)
422 if not use_ntvfs and not use_s3fs:
423 use_ntvfs = "smb" in lp.get("server services")
424 elif use_s3fs:
425 use_ntvfs = False
427 domain_sid = security.dom_sid(samdb.domain_sid)
429 s3conf = s3param.get_context()
430 s3conf.load(lp.configfile)
431 # ensure we are using the right samba_dsdb passdb backend, no matter what
432 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
434 LA_sid = security.dom_sid(str(domain_sid)
435 + "-" + str(security.DOMAIN_RID_ADMINISTRATOR))
436 BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
438 s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
440 # These assertions correct for current ad_dc selftest
441 # configuration. When other environments have a broad range of
442 # groups mapped via passdb, we can relax some of these checks
443 (LA_uid, LA_type) = s4_passdb.sid_to_id(LA_sid)
444 if (LA_type != idmap.ID_TYPE_UID and LA_type != idmap.ID_TYPE_BOTH):
445 raise CommandError("SID %s is not mapped to a UID" % LA_sid)
446 (BA_gid, BA_type) = s4_passdb.sid_to_id(BA_sid)
447 if (BA_type != idmap.ID_TYPE_GID and BA_type != idmap.ID_TYPE_BOTH):
448 raise CommandError("SID %s is not mapped to a GID" % BA_sid)
450 if use_ntvfs:
451 logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
453 try:
454 provision.setsysvolacl(samdb, netlogon, sysvol,
455 LA_uid, BA_gid, domain_sid,
456 lp.get("realm").lower(), samdb.domain_dn(),
457 lp, use_ntvfs=use_ntvfs)
458 except OSError as e:
459 if not e.filename:
460 raise
461 raise CommandError(f"Could not access {e.filename}: {e.strerror}", e)
464 class cmd_ntacl_sysvolcheck(Command):
465 """Check sysvol ACLs match defaults (including correct ACLs on GPOs)."""
466 synopsis = "%prog <file> [options]"
468 takes_optiongroups = {
469 "sambaopts": options.SambaOptions,
470 "credopts": options.CredentialsOptions,
471 "versionopts": options.VersionOptions,
474 def run(self, credopts=None, sambaopts=None, versionopts=None):
475 lp = sambaopts.get_loadparm()
476 creds = credopts.get_credentials(lp)
477 creds.set_kerberos_state(DONT_USE_KERBEROS)
478 logger = self.get_logger()
480 netlogon = lp.get("path", "netlogon")
481 sysvol = lp.get("path", "sysvol")
482 try:
483 samdb = SamDB(session_info=system_session(), lp=lp)
484 except Exception as e:
485 raise CommandError("Unable to open samdb:", e)
487 domain_sid = security.dom_sid(samdb.domain_sid)
489 try:
490 provision.checksysvolacl(samdb, netlogon, sysvol,
491 domain_sid,
492 lp.get("realm").lower(), samdb.domain_dn(),
494 except OSError as e:
495 if not e.filename:
496 raise
497 raise CommandError(f"Could not access {e.filename}: {e.strerror}", e)
500 class cmd_ntacl(SuperCommand):
501 """NT ACLs manipulation."""
503 subcommands = {}
504 subcommands["set"] = cmd_ntacl_set()
505 subcommands["get"] = cmd_ntacl_get()
506 subcommands["changedomsid"] = cmd_ntacl_changedomsid()
507 subcommands["sysvolreset"] = cmd_ntacl_sysvolreset()
508 subcommands["sysvolcheck"] = cmd_ntacl_sysvolcheck()
509 subcommands["getdosinfo"] = cmd_dosinfo_get()