1 # Utility methods for security descriptor manipulation
3 # Copyright Nadezhda Ivanova 2010 <nivanova@samba.org>
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 """Utility methods for security descriptor manipulation."""
21 from ldb
import FLAG_MOD_REPLACE
, SCOPE_BASE
, Dn
, Message
, MessageElement
24 from samba
.dcerpc
import security
25 from samba
.ndr
import ndr_deepcopy
, ndr_pack
, ndr_unpack
26 from samba
.ntstatus
import NT_STATUS_OBJECT_NAME_NOT_FOUND
29 def escaped_claim_id(claim_id
):
30 """Encode claim attribute names according to [MS-DTYP] 2.5.1 ("attr-char2")
32 Some characters must be encoded as %hhhh, while others must not be.
33 Of the optional ones, we encode some control characters.
35 The \x00 byte is also encoded, which is useful for tests, but it
36 is forbidden in either form.
38 escapes
= '\x00\t\n\x0b\x0c\r !"%&()<=>|'
45 class SDUtils(object):
46 """Some utilities for manipulation of security descriptors on objects."""
48 def __init__(self
, samdb
):
50 self
.domain_sid
= security
.dom_sid(self
.ldb
.get_domain_sid())
52 def modify_sd_on_dn(self
, object_dn
, sd
, controls
=None):
53 """Modify security descriptor using either SDDL string
54 or security.descriptor object
57 if isinstance(object_dn
, Dn
):
60 m
.dn
= Dn(self
.ldb
, object_dn
)
62 assert (isinstance(sd
, str) or isinstance(sd
, security
.descriptor
))
63 if isinstance(sd
, str):
64 tmp_desc
= security
.descriptor
.from_sddl(sd
, self
.domain_sid
)
65 elif isinstance(sd
, security
.descriptor
):
68 m
["nTSecurityDescriptor"] = MessageElement(ndr_pack(tmp_desc
),
70 "nTSecurityDescriptor")
71 self
.ldb
.modify(m
, controls
)
73 def read_sd_on_dn(self
, object_dn
, controls
=None):
74 res
= self
.ldb
.search(object_dn
, SCOPE_BASE
, None,
75 ["nTSecurityDescriptor"], controls
=controls
)
76 desc
= res
[0]["nTSecurityDescriptor"][0]
77 return ndr_unpack(security
.descriptor
, desc
)
79 def get_object_sid(self
, object_dn
):
80 res
= self
.ldb
.search(object_dn
)
81 return ndr_unpack(security
.dom_sid
, res
[0]["objectSid"][0])
83 def update_aces_in_dacl(self
, dn
, del_aces
=None, add_aces
=None,
84 sddl_attr
=None, controls
=None):
90 def ace_from_sddl(ace_sddl
):
91 ace_sd
= security
.descriptor
.from_sddl("D:" + ace_sddl
, self
.domain_sid
)
92 assert len(ace_sd
.dacl
.aces
) == 1
93 return ace_sd
.dacl
.aces
[0]
97 controls
= ["sd_flags:1:%d" % security
.SECINFO_DACL
]
98 sd
= self
.read_sd_on_dn(dn
, controls
=controls
)
99 if not sd
.type & security
.SEC_DESC_DACL_PROTECTED
:
100 # if the DACL is not protected remove all
101 # inherited aces, as they will be re-inherited
102 # on the server, we need a ndr_deepcopy in order
103 # to avoid reference problems while deleting
104 # the aces while looping over them
105 dacl_copy
= ndr_deepcopy(sd
.dacl
)
106 for ace
in dacl_copy
.aces
:
107 if ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
110 except samba
.NTSTATUSError
as err
:
111 if err
.args
[0] != NT_STATUS_OBJECT_NAME_NOT_FOUND
:
113 # dacl_del_ace may remove more than
114 # one ace, so we may not find it anymore
119 res
= self
.ldb
.search(dn
, SCOPE_BASE
, None,
120 [sddl_attr
], controls
=controls
)
121 old_sddl
= str(res
[0][sddl_attr
][0])
122 sd
= security
.descriptor
.from_sddl(old_sddl
, self
.domain_sid
)
127 inherited_ignored
= []
130 if isinstance(ace
, str):
131 ace
= ace_from_sddl(ace
)
132 assert isinstance(ace
, security
.ace
)
134 if ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
135 inherited_ignored
.append(ace
)
138 if ace
not in sd
.dacl
.aces
:
139 del_ignored
.append(ace
)
147 if isinstance(ace
, dict):
151 if isinstance(ace
, str):
152 ace
= ace_from_sddl(ace
)
153 assert isinstance(ace
, security
.ace
)
155 if ace
.flags
& security
.SEC_ACE_FLAG_INHERITED_ACE
:
156 inherited_ignored
.append(ace
)
159 if ace
in sd
.dacl
.aces
:
160 add_ignored
.append(ace
)
163 sd
.dacl_add(ace
, add_idx
)
167 return del_ignored
, add_ignored
, inherited_ignored
169 if sddl_attr
is None:
170 self
.modify_sd_on_dn(dn
, sd
, controls
=controls
)
172 new_sddl
= sd
.as_sddl(self
.domain_sid
)
175 m
[sddl_attr
] = MessageElement(new_sddl
.encode('ascii'),
178 self
.ldb
.modify(m
, controls
=controls
)
180 return del_ignored
, add_ignored
, inherited_ignored
182 def dacl_prepend_aces(self
, object_dn
, aces
, controls
=None):
183 """Prepend an ACE (or more) to an objects security descriptor
185 ace_sd
= security
.descriptor
.from_sddl("D:" + aces
, self
.domain_sid
)
188 for ace
in ace_sd
.dacl
.aces
:
189 add_aces
.append({"idx": add_idx
, "ace": ace
})
191 _
, ai
, ii
= self
.update_aces_in_dacl(object_dn
, add_aces
=add_aces
,
195 def dacl_add_ace(self
, object_dn
, ace
):
196 """Add an ACE (or more) to an objects security descriptor
198 _
, _
= self
.dacl_prepend_aces(object_dn
, ace
,
199 controls
=["show_deleted:1"])
201 def dacl_append_aces(self
, object_dn
, aces
, controls
=None):
202 """Append an ACE (or more) to an objects security descriptor
204 ace_sd
= security
.descriptor
.from_sddl("D:" + aces
, self
.domain_sid
)
206 for ace
in ace_sd
.dacl
.aces
:
208 _
, ai
, ii
= self
.update_aces_in_dacl(object_dn
, add_aces
=add_aces
,
212 def dacl_delete_aces(self
, object_dn
, aces
, controls
=None):
213 """Delete an ACE (or more) to an objects security descriptor
215 del_sd
= security
.descriptor
.from_sddl("D:" + aces
, self
.domain_sid
)
217 for ace
in del_sd
.dacl
.aces
:
219 di
, _
, ii
= self
.update_aces_in_dacl(object_dn
, del_aces
=del_aces
,
223 def get_sd_as_sddl(self
, object_dn
, controls
=None):
224 """Return object nTSecurityDescriptor in SDDL format
228 desc
= self
.read_sd_on_dn(object_dn
, controls
+ ["show_deleted:1"])
229 return desc
.as_sddl(self
.domain_sid
)