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/>.
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
):
38 server_role
= lp
.server_role()
39 if server_role
== "ROLE_ACTIVE_DIRECTORY_DC":
42 s3conf
= s3param
.get_context()
43 s3conf
.load(lp
.configfile
)
47 samdb
= SamDB(session_info
=system_session(),
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
53 s3conf
.set("passdb backend", "samba_dsdb:%s" % samdb
.url
)
57 domain_sid
= security
.dom_sid(samdb
.domain_sid
)
59 domain_sid
= passdb
.get_domain_sid()
61 raise CommandError("Unable to read domain SID from configuration "
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
,
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")
106 def _setntacl_path(_path
):
107 if not follow_symlinks
and os
.path
.islink(_path
):
109 self
.outf
.write("ignored symlink: %s\n" % _path
)
111 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path
))
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
)
119 self
.outf
.write("file: %s\n" % _path
)
125 system_session_unix(),
130 except Exception as e
:
131 raise CommandError("Could not set acl for %s: %s" % (_path
, e
))
135 if recursive
and os
.path
.isdir(path
):
136 for root
, dirs
, files
in os
.walk(path
, followlinks
=follow_symlinks
):
138 _setntacl_path(os
.path
.join(root
, name
))
140 _setntacl_path(os
.path
.join(root
, name
))
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)
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
,
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,
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")
204 system_session_unix(),
207 direct_db_access
=use_ntvfs
,
210 self
.outf
.write(acl
.as_sddl(domain_sid
) + "\n")
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
,
226 help="Name of the smb.conf service to use",
230 help=("Set the ACLs directly to the TDB or xattr for use with the "
231 "ntvfs file server"),
232 action
="store_true"),
235 help=("Set the ACLs for use with the default s3fs file server via "
237 action
="store_true"),
240 help="Name of the tdb file where attributes are stored",
245 help="xattr backend type (native fs or tdb)",
246 choices
=["native", "tdb"]),
250 help="Set the ACLs for directories and their contents recursively",
251 action
="store_true"),
254 help="Follow symlinks",
255 action
="store_true"),
260 action
="store_true"),
263 takes_args
= ["old_domain_sid", "new_domain_sid", "path"]
276 follow_symlinks
=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")
287 if not use_ntvfs
and not service
:
289 "Must provide a share name with --service=<share>")
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
))
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
):
306 self
.outf
.write("ignored symlink: %s\n" % _path
)
308 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path
))
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
)
316 self
.outf
.write("file: %s\n" % _path
)
321 system_session_unix(),
324 direct_db_access
=use_ntvfs
,
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
)
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
))
339 acl
.owner_sid
= replace_domain_sid(acl
.owner_sid
)
340 acl
.group_sid
= replace_domain_sid(acl
.group_sid
)
343 for ace
in acl
.sacl
.aces
:
344 ace
.trustee
= replace_domain_sid(ace
.trustee
)
346 for ace
in acl
.dacl
.aces
:
347 ace
.trustee
= replace_domain_sid(ace
.trustee
)
349 new_sddl
= acl
.as_sddl(domain_sid
)
351 self
.outf
.write("after:\n%s\n" % new_sddl
)
353 if orig_sddl
== new_sddl
:
355 self
.outf
.write("nothing to do\n")
363 system_session_unix(),
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
):
374 changedom_sids(os
.path
.join(root
, f
))
377 changedom_sids(os
.path
.join(root
, d
))
380 if recursive
and os
.path
.isdir(path
):
381 recursive_changedom_sids(path
)
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
,
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")
412 samdb
= SamDB(session_info
=system_session(),
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")
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
)
446 logger
.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
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
)
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")
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
)
484 provision
.checksysvolacl(samdb
, netlogon
, sysvol
,
486 lp
.get("realm").lower(), samdb
.domain_dn(),
491 raise CommandError(f
"Could not access {e.filename}: {e.strerror}", e
)
494 class cmd_ntacl(SuperCommand
):
495 """NT ACLs manipulation."""
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()