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
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
31 from samba
.auth
import system_session
33 from samba
.netcmd
import (
40 def get_local_domain_sid(lp
):
42 server_role
= lp
.server_role()
43 if server_role
== "ROLE_ACTIVE_DIRECTORY_DC":
46 s3conf
= s3param
.get_context()
47 s3conf
.load(lp
.configfile
)
51 samdb
= SamDB(session_info
=system_session(),
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
57 s3conf
.set("passdb backend", "samba_dsdb:%s" % samdb
.url
)
61 domain_sid
= security
.dom_sid(samdb
.domain_sid
)
63 domain_sid
= passdb
.get_domain_sid()
65 raise CommandError("Unable to read domain SID from configuration "
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
,
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")
110 def _setntacl_path(_path
):
111 if not follow_symlinks
and os
.path
.islink(_path
):
113 self
.outf
.write("ignored symlink: %s\n" % _path
)
115 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path
))
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
)
123 self
.outf
.write("file: %s\n" % _path
)
129 system_session_unix(),
134 except Exception as e
:
135 raise CommandError("Could not set acl for %s: %s" % (_path
, e
))
139 if recursive
and os
.path
.isdir(path
):
140 for root
, dirs
, files
in os
.walk(path
, followlinks
=follow_symlinks
):
142 _setntacl_path(os
.path
.join(root
, name
))
144 _setntacl_path(os
.path
.join(root
, name
))
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)
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
,
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,
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")
208 system_session_unix(),
211 direct_db_access
=use_ntvfs
,
214 self
.outf
.write(acl
.as_sddl(domain_sid
) + "\n")
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
,
230 help="Name of the smb.conf service to use",
234 help=("Set the ACLs directly to the TDB or xattr for use with the "
235 "ntvfs file server"),
236 action
="store_true"),
239 help=("Set the ACLs for use with the default s3fs file server via "
241 action
="store_true"),
244 help="Name of the tdb file where attributes are stored",
249 help="xattr backend type (native fs or tdb)",
250 choices
=["native", "tdb"]),
254 help="Set the ACLs for directories and their contents recursively",
255 action
="store_true"),
258 help="Follow symlinks",
259 action
="store_true"),
264 action
="store_true"),
267 takes_args
= ["old_domain_sid", "new_domain_sid", "path"]
280 follow_symlinks
=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")
291 if not use_ntvfs
and not service
:
293 "Must provide a share name with --service=<share>")
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
))
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
):
310 self
.outf
.write("ignored symlink: %s\n" % _path
)
312 raise CommandError("symlink: %s: requires --follow-symlinks" % (_path
))
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
)
320 self
.outf
.write("file: %s\n" % _path
)
325 system_session_unix(),
328 direct_db_access
=use_ntvfs
,
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
)
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
))
343 acl
.owner_sid
= replace_domain_sid(acl
.owner_sid
)
344 acl
.group_sid
= replace_domain_sid(acl
.group_sid
)
347 for ace
in acl
.sacl
.aces
:
348 ace
.trustee
= replace_domain_sid(ace
.trustee
)
350 for ace
in acl
.dacl
.aces
:
351 ace
.trustee
= replace_domain_sid(ace
.trustee
)
353 new_sddl
= acl
.as_sddl(domain_sid
)
355 self
.outf
.write("after:\n%s\n" % new_sddl
)
357 if orig_sddl
== new_sddl
:
359 self
.outf
.write("nothing to do\n")
367 system_session_unix(),
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
):
378 changedom_sids(os
.path
.join(root
, f
))
381 changedom_sids(os
.path
.join(root
, d
))
384 if recursive
and os
.path
.isdir(path
):
385 recursive_changedom_sids(path
)
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
,
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")
417 samdb
= SamDB(session_info
=system_session(),
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")
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
)
451 logger
.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
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
)
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")
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
)
490 provision
.checksysvolacl(samdb
, netlogon
, sysvol
,
492 lp
.get("realm").lower(), samdb
.domain_dn(),
497 raise CommandError(f
"Could not access {e.filename}: {e.strerror}", e
)
500 class cmd_ntacl(SuperCommand
):
501 """NT ACLs manipulation."""
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()