s3:tests: let modprinter.pl use $TMPDIR
[Samba.git] / python / samba / netcmd / ntacl.py
blob34675c71375d4ddfb5d04672e08f6c897e1d3acf
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 import optparse
20 import os
22 import samba.getopt as options
23 from samba import provision
24 from samba.auth import system_session
25 from samba.auth_util import system_session_unix
26 from samba.credentials import DONT_USE_KERBEROS
27 from samba.dcerpc import security, idmap
28 from samba.ndr import ndr_print
29 from samba.ntacls import setntacl, getntacl, getdosinfo
30 from samba.samba3 import param as s3param, passdb
31 from samba.samdb import SamDB
33 from . import Command, CommandError, SuperCommand, Option
36 def get_local_domain_sid(lp):
37 is_ad_dc = False
38 server_role = lp.server_role()
39 if server_role == "ROLE_ACTIVE_DIRECTORY_DC":
40 is_ad_dc = True
42 s3conf = s3param.get_context()
43 s3conf.load(lp.configfile)
45 if is_ad_dc:
46 try:
47 samdb = SamDB(session_info=system_session(),
48 lp=lp)
49 except Exception as e:
50 raise CommandError("Unable to open samdb:", e)
51 # ensure we are using the right samba_dsdb passdb backend, no
52 # matter what
53 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
55 try:
56 if is_ad_dc:
57 domain_sid = security.dom_sid(samdb.domain_sid)
58 else:
59 domain_sid = passdb.get_domain_sid()
60 except:
61 raise CommandError("Unable to read domain SID from configuration "
62 "files")
63 return domain_sid
66 class cmd_ntacl_set(Command):
67 """Set ACLs on a file."""
69 synopsis = "%prog <acl> <path> [options]"
71 takes_optiongroups = {
72 "sambaopts": options.SambaOptions,
73 "credopts": options.CredentialsOptions,
74 "versionopts": options.VersionOptions,
77 takes_options = [
78 # --quiet is not used at all...
79 Option("-q", "--quiet", help=optparse.SUPPRESS_HELP, action="store_true"),
80 Option("-v", "--verbose", help="Be verbose", action="store_true"),
81 Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
82 choices=["native", "tdb"]),
83 Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
84 Option("--use-ntvfs", help="Set the ACLs directly to the TDB or xattr for use with the ntvfs file server", action="store_true"),
85 Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server via the VFS layer", action="store_true"),
86 Option("--recursive", help="Set the ACLs for directories and their contents recursively", action="store_true"),
87 Option("--follow-symlinks", help="Follow symlinks", action="store_true"),
88 Option("--service", help="Name of the smb.conf service to use when applying the ACLs", type="string")
91 takes_args = ["acl", "path"]
93 def run(self, acl, path, use_ntvfs=False, use_s3fs=False,
94 quiet=False, verbose=False, xattr_backend=None, eadb_file=None,
95 credopts=None, sambaopts=None, versionopts=None,
96 recursive=False, follow_symlinks=False, service=None):
97 logger = self.get_logger()
98 lp = sambaopts.get_loadparm()
99 domain_sid = get_local_domain_sid(lp)
101 if not use_ntvfs and not use_s3fs:
102 use_ntvfs = "smb" in lp.get("server services")
103 elif use_s3fs:
104 use_ntvfs = False
106 def _setntacl_path(_path):
107 if not follow_symlinks and os.path.islink(_path):
108 if recursive:
109 self.outf.write("ignored symlink: %s\n" % _path)
110 return
111 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path))
113 if verbose:
114 if os.path.islink(_path):
115 self.outf.write("symlink: %s\n" % _path)
116 elif os.path.isdir(_path):
117 self.outf.write("dir: %s\n" % _path)
118 else:
119 self.outf.write("file: %s\n" % _path)
120 try:
121 setntacl(lp,
122 _path,
123 acl,
124 str(domain_sid),
125 system_session_unix(),
126 xattr_backend,
127 eadb_file,
128 use_ntvfs=use_ntvfs,
129 service=service)
130 except Exception as e:
131 raise CommandError("Could not set acl for %s: %s" % (_path, e))
133 _setntacl_path(path)
135 if recursive and os.path.isdir(path):
136 for root, dirs, files in os.walk(path, followlinks=follow_symlinks):
137 for name in files:
138 _setntacl_path(os.path.join(root, name))
139 for name in dirs:
140 _setntacl_path(os.path.join(root, name))
142 if use_ntvfs:
143 logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
146 class cmd_dosinfo_get(Command):
147 """Get DOS info of a file from xattr."""
148 synopsis = "%prog <file> [options]"
150 takes_optiongroups = {
151 "sambaopts": options.SambaOptions,
152 "credopts": options.CredentialsOptions,
153 "versionopts": options.VersionOptions,
156 takes_args = ["file"]
158 def run(self, file, credopts=None, sambaopts=None, versionopts=None):
159 lp = sambaopts.get_loadparm()
160 s3conf = s3param.get_context()
161 s3conf.load(lp.configfile)
163 dosinfo = getdosinfo(lp, file)
164 if dosinfo:
165 self.outf.write(ndr_print(dosinfo))
168 class cmd_ntacl_get(Command):
169 """Get ACLs of a file."""
170 synopsis = "%prog <file> [options]"
172 takes_optiongroups = {
173 "sambaopts": options.SambaOptions,
174 "credopts": options.CredentialsOptions,
175 "versionopts": options.VersionOptions,
178 takes_options = [
179 Option("--as-sddl", help="Output ACL in the SDDL format", action="store_true"),
180 Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
181 choices=["native", "tdb"]),
182 Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
183 Option("--use-ntvfs", help="Get the ACLs directly from the TDB or xattr used with the ntvfs file server", action="store_true"),
184 Option("--use-s3fs", help="Get the ACLs for use via the VFS layer used by the default s3fs file server", action="store_true"),
185 Option("--service", help="Name of the smb.conf service to use when getting the ACLs", type="string")
188 takes_args = ["file"]
190 def run(self, file, use_ntvfs=False, use_s3fs=False,
191 as_sddl=False, xattr_backend=None, eadb_file=None,
192 credopts=None, sambaopts=None, versionopts=None,
193 service=None):
194 lp = sambaopts.get_loadparm()
195 domain_sid = get_local_domain_sid(lp)
197 if not use_ntvfs and not use_s3fs:
198 use_ntvfs = "smb" in lp.get("server services")
199 elif use_s3fs:
200 use_ntvfs = False
202 acl = getntacl(lp,
203 file,
204 system_session_unix(),
205 xattr_backend,
206 eadb_file,
207 direct_db_access=use_ntvfs,
208 service=service)
209 if as_sddl:
210 self.outf.write(acl.as_sddl(domain_sid) + "\n")
211 else:
212 self.outf.write(ndr_print(acl))
215 class cmd_ntacl_changedomsid(Command):
216 """Change the domain SID for ACLs"""
217 synopsis = "%prog <Orig-Domain-SID> <New-Domain-SID> <file> [options]"
219 takes_optiongroups = {
220 "sambaopts": options.SambaOptions,
223 takes_options = [
224 Option(
225 "--service",
226 help="Name of the smb.conf service to use",
227 type="string"),
228 Option(
229 "--use-ntvfs",
230 help=("Set the ACLs directly to the TDB or xattr for use with the "
231 "ntvfs file server"),
232 action="store_true"),
233 Option(
234 "--use-s3fs",
235 help=("Set the ACLs for use with the default s3fs file server via "
236 "the VFS layer"),
237 action="store_true"),
238 Option(
239 "--eadb-file",
240 help="Name of the tdb file where attributes are stored",
241 type="string"),
242 Option(
243 "--xattr-backend",
244 type="choice",
245 help="xattr backend type (native fs or tdb)",
246 choices=["native", "tdb"]),
247 Option(
248 "-r",
249 "--recursive",
250 help="Set the ACLs for directories and their contents recursively",
251 action="store_true"),
252 Option(
253 "--follow-symlinks",
254 help="Follow symlinks",
255 action="store_true"),
256 Option(
257 "-v",
258 "--verbose",
259 help="Be verbose",
260 action="store_true"),
263 takes_args = ["old_domain_sid", "new_domain_sid", "path"]
265 def run(self,
266 old_domain_sid_str,
267 new_domain_sid_str,
268 path,
269 use_ntvfs=False,
270 use_s3fs=False,
271 service=None,
272 xattr_backend=None,
273 eadb_file=None,
274 sambaopts=None,
275 recursive=False,
276 follow_symlinks=False,
277 verbose=False):
278 logger = self.get_logger()
279 lp = sambaopts.get_loadparm()
280 domain_sid = get_local_domain_sid(lp)
282 if not use_ntvfs and not use_s3fs:
283 use_ntvfs = "smb" in lp.get("server services")
284 elif use_s3fs:
285 use_ntvfs = False
287 if not use_ntvfs and not service:
288 raise CommandError(
289 "Must provide a share name with --service=<share>")
291 try:
292 old_domain_sid = security.dom_sid(old_domain_sid_str)
293 except Exception as e:
294 raise CommandError("Could not parse old sid %s: %s" %
295 (old_domain_sid_str, e))
297 try:
298 new_domain_sid = security.dom_sid(new_domain_sid_str)
299 except Exception as e:
300 raise CommandError("Could not parse old sid %s: %s" %
301 (new_domain_sid_str, e))
303 def changedom_sids(_path):
304 if not follow_symlinks and os.path.islink(_path):
305 if recursive:
306 self.outf.write("ignored symlink: %s\n" % _path)
307 return
308 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path))
310 if verbose:
311 if os.path.islink(_path):
312 self.outf.write("symlink: %s\n" % _path)
313 elif os.path.isdir(_path):
314 self.outf.write("dir: %s\n" % _path)
315 else:
316 self.outf.write("file: %s\n" % _path)
318 try:
319 acl = getntacl(lp,
320 _path,
321 system_session_unix(),
322 xattr_backend,
323 eadb_file,
324 direct_db_access=use_ntvfs,
325 service=service)
326 except Exception as e:
327 raise CommandError("Could not get acl for %s: %s" % (_path, e))
329 orig_sddl = acl.as_sddl(domain_sid)
330 if verbose:
331 self.outf.write("before:\n%s\n" % orig_sddl)
333 def replace_domain_sid(sid):
334 (dom, rid) = sid.split()
335 if dom == old_domain_sid:
336 return security.dom_sid("%s-%i" % (new_domain_sid, rid))
337 return sid
339 acl.owner_sid = replace_domain_sid(acl.owner_sid)
340 acl.group_sid = replace_domain_sid(acl.group_sid)
342 if acl.sacl:
343 for ace in acl.sacl.aces:
344 ace.trustee = replace_domain_sid(ace.trustee)
345 if acl.dacl:
346 for ace in acl.dacl.aces:
347 ace.trustee = replace_domain_sid(ace.trustee)
349 new_sddl = acl.as_sddl(domain_sid)
350 if verbose:
351 self.outf.write("after:\n%s\n" % new_sddl)
353 if orig_sddl == new_sddl:
354 if verbose:
355 self.outf.write("nothing to do\n")
356 return True
358 try:
359 setntacl(lp,
360 _path,
361 acl,
362 new_domain_sid,
363 system_session_unix(),
364 xattr_backend,
365 eadb_file,
366 use_ntvfs=use_ntvfs,
367 service=service)
368 except Exception as e:
369 raise CommandError("Could not set acl for %s: %s" % (_path, e))
371 def recursive_changedom_sids(_path):
372 for root, dirs, files in os.walk(_path, followlinks=follow_symlinks):
373 for f in files:
374 changedom_sids(os.path.join(root, f))
376 for d in dirs:
377 changedom_sids(os.path.join(root, d))
379 changedom_sids(path)
380 if recursive and os.path.isdir(path):
381 recursive_changedom_sids(path)
383 if use_ntvfs:
384 logger.warning("Please note that POSIX permissions have NOT been "
385 "changed, only the stored NT ACL.")
388 class cmd_ntacl_sysvolreset(Command):
389 """Reset sysvol ACLs to defaults (including correct ACLs on GPOs)."""
390 synopsis = "%prog <file> [options]"
392 takes_optiongroups = {
393 "sambaopts": options.SambaOptions,
394 "credopts": options.CredentialsOptions,
395 "versionopts": options.VersionOptions,
398 takes_options = [
399 Option("--use-ntvfs", help="Set the ACLs for use with the ntvfs file server", action="store_true"),
400 Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server", action="store_true")
403 def run(self, use_ntvfs=False, use_s3fs=False,
404 credopts=None, sambaopts=None, versionopts=None):
405 lp = sambaopts.get_loadparm()
406 creds = credopts.get_credentials(lp)
407 creds.set_kerberos_state(DONT_USE_KERBEROS)
408 logger = self.get_logger()
410 sysvol = lp.get("path", "sysvol")
411 try:
412 samdb = SamDB(session_info=system_session(),
413 lp=lp)
414 except Exception as e:
415 raise CommandError("Unable to open samdb:", e)
417 if not use_ntvfs and not use_s3fs:
418 use_ntvfs = "smb" in lp.get("server services")
419 elif use_s3fs:
420 use_ntvfs = False
422 domain_sid = security.dom_sid(samdb.domain_sid)
424 s3conf = s3param.get_context()
425 s3conf.load(lp.configfile)
426 # ensure we are using the right samba_dsdb passdb backend, no matter what
427 s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
429 LA_sid = security.dom_sid(str(domain_sid)
430 + "-" + str(security.DOMAIN_RID_ADMINISTRATOR))
431 BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
433 s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
435 # These assertions correct for current ad_dc selftest
436 # configuration. When other environments have a broad range of
437 # groups mapped via passdb, we can relax some of these checks
438 (LA_uid, LA_type) = s4_passdb.sid_to_id(LA_sid)
439 if (LA_type != idmap.ID_TYPE_UID and LA_type != idmap.ID_TYPE_BOTH):
440 raise CommandError("SID %s is not mapped to a UID" % LA_sid)
441 (BA_gid, BA_type) = s4_passdb.sid_to_id(BA_sid)
442 if (BA_type != idmap.ID_TYPE_GID and BA_type != idmap.ID_TYPE_BOTH):
443 raise CommandError("SID %s is not mapped to a GID" % BA_sid)
445 if use_ntvfs:
446 logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
448 try:
449 provision.setsysvolacl(samdb, sysvol,
450 LA_uid, BA_gid, domain_sid,
451 lp.get("realm").lower(), samdb.domain_dn(),
452 lp, use_ntvfs=use_ntvfs)
453 except OSError as e:
454 if not e.filename:
455 raise
456 raise CommandError(f"Could not access {e.filename}: {e.strerror}", e)
459 class cmd_ntacl_sysvolcheck(Command):
460 """Check sysvol ACLs match defaults (including correct ACLs on GPOs)."""
461 synopsis = "%prog <file> [options]"
463 takes_optiongroups = {
464 "sambaopts": options.SambaOptions,
465 "credopts": options.CredentialsOptions,
466 "versionopts": options.VersionOptions,
469 def run(self, credopts=None, sambaopts=None, versionopts=None):
470 lp = sambaopts.get_loadparm()
471 creds = credopts.get_credentials(lp)
472 creds.set_kerberos_state(DONT_USE_KERBEROS)
474 netlogon = lp.get("path", "netlogon")
475 sysvol = lp.get("path", "sysvol")
476 try:
477 samdb = SamDB(session_info=system_session(), lp=lp)
478 except Exception as e:
479 raise CommandError("Unable to open samdb:", e)
481 domain_sid = security.dom_sid(samdb.domain_sid)
483 try:
484 provision.checksysvolacl(samdb, netlogon, sysvol,
485 domain_sid,
486 lp.get("realm").lower(), samdb.domain_dn(),
488 except OSError as e:
489 if not e.filename:
490 raise
491 raise CommandError(f"Could not access {e.filename}: {e.strerror}", e)
494 class cmd_ntacl(SuperCommand):
495 """NT ACLs manipulation."""
497 subcommands = {}
498 subcommands["set"] = cmd_ntacl_set()
499 subcommands["get"] = cmd_ntacl_get()
500 subcommands["changedomsid"] = cmd_ntacl_changedomsid()
501 subcommands["sysvolreset"] = cmd_ntacl_sysvolreset()
502 subcommands["sysvolcheck"] = cmd_ntacl_sysvolcheck()
503 subcommands["getdosinfo"] = cmd_dosinfo_get()