1 # Manipulate ACLs on directory objects
3 # Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
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 samba.getopt as options
20 from samba.dcerpc import security
21 from samba.samdb import SamDB
22 from samba.ndr import ndr_unpack, ndr_pack
23 from samba.dcerpc.security import (
24 GUID_DRS_ALLOCATE_RIDS, GUID_DRS_CHANGE_DOMAIN_MASTER,
25 GUID_DRS_CHANGE_INFR_MASTER, GUID_DRS_CHANGE_PDC,
26 GUID_DRS_CHANGE_RID_MASTER, GUID_DRS_CHANGE_SCHEMA_MASTER,
27 GUID_DRS_GET_CHANGES, GUID_DRS_GET_ALL_CHANGES,
28 GUID_DRS_GET_FILTERED_ATTRIBUTES, GUID_DRS_MANAGE_TOPOLOGY,
29 GUID_DRS_MONITOR_TOPOLOGY, GUID_DRS_REPL_SYNCRONIZE,
30 GUID_DRS_RO_REPL_SECRET_SYNC)
34 from ldb import SCOPE_BASE
37 from samba.auth import system_session
38 from samba.netcmd import (
46 class cmd_dsacl_set(Command):
47 """Modify access list on a directory object."""
49 synopsis = "%prog [options]"
50 car_help = """ The access control right to allow or deny """
52 takes_optiongroups = {
53 "sambaopts": options.SambaOptions,
54 "credopts": options.CredentialsOptions,
55 "versionopts": options.VersionOptions,
59 Option("-H", "--URL", help="LDB URL for database or target server",
60 type=str, metavar="URL", dest="H"),
61 Option("--car", type="choice", choices=["change-rid",
63 "change-infrastructure",
69 "get-changes-filtered",
73 "ro-repl-secret-sync"],
75 Option("--action", type="choice", choices=["allow", "deny"],
76 help="""Deny or allow access"""),
77 Option("--objectdn", help="DN of the object whose SD to modify",
79 Option("--trusteedn", help="DN of the entity that gets access",
81 Option("--sddl", help="An ACE or group of ACEs to be added on the object",
85 def find_trustee_sid(self, samdb, trusteedn):
86 res = samdb.search(base=trusteedn, expression="(objectClass=*)",
89 return ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
91 def modify_descriptor(self, samdb, object_dn, desc, controls=None):
92 assert(isinstance(desc, security.descriptor))
94 m.dn = ldb.Dn(samdb, object_dn)
95 m["nTSecurityDescriptor"] = ldb.MessageElement(
96 (ndr_pack(desc)), ldb.FLAG_MOD_REPLACE,
97 "nTSecurityDescriptor")
100 def read_descriptor(self, samdb, object_dn):
101 res = samdb.search(base=object_dn, scope=SCOPE_BASE,
102 attrs=["nTSecurityDescriptor"])
103 # we should theoretically always have an SD
104 assert(len(res) == 1)
105 desc = res[0]["nTSecurityDescriptor"][0]
106 return ndr_unpack(security.descriptor, desc)
108 def get_domain_sid(self, samdb):
109 res = samdb.search(base=samdb.domain_dn(),
110 expression="(objectClass=*)", scope=SCOPE_BASE)
111 return ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
113 def add_ace(self, samdb, object_dn, new_ace):
114 """Add new ace explicitly."""
115 desc = self.read_descriptor(samdb, object_dn)
116 new_ace = security.descriptor.from_sddl("D:" + new_ace,self.get_domain_sid(samdb))
117 new_ace_list = re.findall("\(.*?\)",new_ace.as_sddl())
118 for new_ace in new_ace_list:
119 desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
120 # TODO add bindings for descriptor manipulation and get rid of this
121 desc_aces = re.findall("\(.*?\)", desc_sddl)
122 for ace in desc_aces:
124 desc_sddl = desc_sddl.replace(ace, "")
125 if new_ace in desc_sddl:
127 if desc_sddl.find("(") >= 0:
128 desc_sddl = desc_sddl[:desc_sddl.index("(")] + new_ace + desc_sddl[desc_sddl.index("("):]
130 desc_sddl = desc_sddl + new_ace
131 desc = security.descriptor.from_sddl(desc_sddl, self.get_domain_sid(samdb))
132 self.modify_descriptor(samdb, object_dn, desc)
134 def print_new_acl(self, samdb, object_dn):
135 desc = self.read_descriptor(samdb, object_dn)
136 desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
137 self.outf.write("new descriptor for %s:\n" % object_dn)
138 self.outf.write(desc_sddl + "\n")
140 def run(self, car, action, objectdn, trusteedn, sddl,
141 H=None, credopts=None, sambaopts=None, versionopts=None):
142 lp = sambaopts.get_loadparm()
143 creds = credopts.get_credentials(lp)
145 if sddl is None and (car is None or action is None
146 or objectdn is None or trusteedn is None):
149 samdb = SamDB(url=H, session_info=system_session(),
150 credentials=creds, lp=lp)
151 cars = {'change-rid': GUID_DRS_CHANGE_RID_MASTER,
152 'change-pdc': GUID_DRS_CHANGE_PDC,
153 'change-infrastructure': GUID_DRS_CHANGE_INFR_MASTER,
154 'change-schema': GUID_DRS_CHANGE_SCHEMA_MASTER,
155 'change-naming': GUID_DRS_CHANGE_DOMAIN_MASTER,
156 'allocate_rids': GUID_DRS_ALLOCATE_RIDS,
157 'get-changes': GUID_DRS_GET_CHANGES,
158 'get-changes-all': GUID_DRS_GET_ALL_CHANGES,
159 'get-changes-filtered': GUID_DRS_GET_FILTERED_ATTRIBUTES,
160 'topology-manage': GUID_DRS_MANAGE_TOPOLOGY,
161 'topology-monitor': GUID_DRS_MONITOR_TOPOLOGY,
162 'repl-sync': GUID_DRS_REPL_SYNCRONIZE,
163 'ro-repl-secret-sync': GUID_DRS_RO_REPL_SECRET_SYNC,
165 sid = self.find_trustee_sid(samdb, trusteedn)
168 elif action == "allow":
169 new_ace = "(OA;;CR;%s;;%s)" % (cars[car], str(sid))
170 elif action == "deny":
171 new_ace = "(OD;;CR;%s;;%s)" % (cars[car], str(sid))
173 raise CommandError("Wrong argument '%s'!" % action)
175 self.print_new_acl(samdb, objectdn)
176 self.add_ace(samdb, objectdn, new_ace)
177 self.print_new_acl(samdb, objectdn)
180 class cmd_dsacl_get(Command):
181 """Print access list on a directory object."""
183 synopsis = "%prog [options]"
185 takes_optiongroups = {
186 "sambaopts": options.SambaOptions,
187 "credopts": options.CredentialsOptions,
188 "versionopts": options.VersionOptions,
192 Option("-H", "--URL", help="LDB URL for database or target server",
193 type=str, metavar="URL", dest="H"),
194 Option("--objectdn", help="DN of the object whose SD to modify",
198 def read_descriptor(self, samdb, object_dn):
199 res = samdb.search(base=object_dn, scope=SCOPE_BASE,
200 attrs=["nTSecurityDescriptor"])
201 # we should theoretically always have an SD
202 assert(len(res) == 1)
203 desc = res[0]["nTSecurityDescriptor"][0]
204 return ndr_unpack(security.descriptor, desc)
206 def get_domain_sid(self, samdb):
207 res = samdb.search(base=samdb.domain_dn(),
208 expression="(objectClass=*)", scope=SCOPE_BASE)
209 return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
211 def print_acl(self, samdb, object_dn):
212 desc = self.read_descriptor(samdb, object_dn)
213 desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
214 self.outf.write("descriptor for %s:\n" % object_dn)
215 self.outf.write(desc_sddl + "\n")
217 def run(self, objectdn,
218 H=None, credopts=None, sambaopts=None, versionopts=None):
219 lp = sambaopts.get_loadparm()
220 creds = credopts.get_credentials(lp)
222 samdb = SamDB(url=H, session_info=system_session(),
223 credentials=creds, lp=lp)
224 self.print_acl(samdb, objectdn)
227 class cmd_dsacl(SuperCommand):
228 """DS ACLs manipulation."""
231 subcommands["set"] = cmd_dsacl_set()
232 subcommands["get"] = cmd_dsacl_get()