1# Manipulate ACLs on directory objects 2# 3# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010 4# 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. 9# 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. 14# 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/>. 17# 18 19import samba.getopt as options 20from samba.dcerpc import security 21from samba.samdb import SamDB 22from samba.ndr import ndr_unpack, ndr_pack 23from 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) 31 32 33import ldb 34from ldb import SCOPE_BASE 35import re 36 37from samba.auth import system_session 38from samba.netcmd import ( 39 Command, 40 CommandError, 41 SuperCommand, 42 Option, 43) 44 45 46class cmd_dsacl_set(Command): 47 """Modify access list on a directory object.""" 48 49 synopsis = "%prog [options]" 50 car_help = """ The access control right to allow or deny """ 51 52 takes_optiongroups = { 53 "sambaopts": options.SambaOptions, 54 "credopts": options.CredentialsOptions, 55 "versionopts": options.VersionOptions, 56 } 57 58 takes_options = [ 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", 62 "change-pdc", 63 "change-infrastructure", 64 "change-schema", 65 "change-naming", 66 "allocate_rids", 67 "get-changes", 68 "get-changes-all", 69 "get-changes-filtered", 70 "topology-manage", 71 "topology-monitor", 72 "repl-sync", 73 "ro-repl-secret-sync"], 74 help=car_help), 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", 78 type="string"), 79 Option("--trusteedn", help="DN of the entity that gets access", 80 type="string"), 81 Option("--sddl", help="An ACE or group of ACEs to be added on the object", 82 type="string"), 83 ] 84 85 def find_trustee_sid(self, samdb, trusteedn): 86 res = samdb.search(base=trusteedn, expression="(objectClass=*)", 87 scope=SCOPE_BASE) 88 assert(len(res) == 1) 89 return ndr_unpack(security.dom_sid, res[0]["objectSid"][0]) 90 91 def modify_descriptor(self, samdb, object_dn, desc, controls=None): 92 assert(isinstance(desc, security.descriptor)) 93 m = ldb.Message() 94 m.dn = ldb.Dn(samdb, object_dn) 95 m["nTSecurityDescriptor"] = ldb.MessageElement( 96 (ndr_pack(desc)), ldb.FLAG_MOD_REPLACE, 97 "nTSecurityDescriptor") 98 samdb.modify(m) 99 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) 107 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]) 112 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: 123 if ("ID" in ace): 124 desc_sddl = desc_sddl.replace(ace, "") 125 if new_ace in desc_sddl: 126 continue 127 if desc_sddl.find("(") >= 0: 128 desc_sddl = desc_sddl[:desc_sddl.index("(")] + new_ace + desc_sddl[desc_sddl.index("("):] 129 else: 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) 133 134 def print_acl(self, samdb, object_dn, new=False): 135 desc = self.read_descriptor(samdb, object_dn) 136 desc_sddl = desc.as_sddl(self.get_domain_sid(samdb)) 137 if new: 138 self.outf.write("new descriptor for %s:\n" % object_dn) 139 else: 140 self.outf.write("old descriptor for %s:\n" % object_dn) 141 self.outf.write(desc_sddl + "\n") 142 143 def run(self, car, action, objectdn, trusteedn, sddl, 144 H=None, credopts=None, sambaopts=None, versionopts=None): 145 lp = sambaopts.get_loadparm() 146 creds = credopts.get_credentials(lp) 147 148 if sddl is None and (car is None or action is None 149 or objectdn is None or trusteedn is None): 150 return self.usage() 151 152 samdb = SamDB(url=H, session_info=system_session(), 153 credentials=creds, lp=lp) 154 cars = {'change-rid': GUID_DRS_CHANGE_RID_MASTER, 155 'change-pdc': GUID_DRS_CHANGE_PDC, 156 'change-infrastructure': GUID_DRS_CHANGE_INFR_MASTER, 157 'change-schema': GUID_DRS_CHANGE_SCHEMA_MASTER, 158 'change-naming': GUID_DRS_CHANGE_DOMAIN_MASTER, 159 'allocate_rids': GUID_DRS_ALLOCATE_RIDS, 160 'get-changes': GUID_DRS_GET_CHANGES, 161 'get-changes-all': GUID_DRS_GET_ALL_CHANGES, 162 'get-changes-filtered': GUID_DRS_GET_FILTERED_ATTRIBUTES, 163 'topology-manage': GUID_DRS_MANAGE_TOPOLOGY, 164 'topology-monitor': GUID_DRS_MONITOR_TOPOLOGY, 165 'repl-sync': GUID_DRS_REPL_SYNCRONIZE, 166 'ro-repl-secret-sync': GUID_DRS_RO_REPL_SECRET_SYNC, 167 } 168 sid = self.find_trustee_sid(samdb, trusteedn) 169 if sddl: 170 new_ace = sddl 171 elif action == "allow": 172 new_ace = "(OA;;CR;%s;;%s)" % (cars[car], str(sid)) 173 elif action == "deny": 174 new_ace = "(OD;;CR;%s;;%s)" % (cars[car], str(sid)) 175 else: 176 raise CommandError("Wrong argument '%s'!" % action) 177 178 self.print_acl(samdb, objectdn) 179 self.add_ace(samdb, objectdn, new_ace) 180 self.print_acl(samdb, objectdn, new=True) 181 182 183class cmd_dsacl_get(Command): 184 """Print access list on a directory object.""" 185 186 synopsis = "%prog [options]" 187 188 takes_optiongroups = { 189 "sambaopts": options.SambaOptions, 190 "credopts": options.CredentialsOptions, 191 "versionopts": options.VersionOptions, 192 } 193 194 takes_options = [ 195 Option("-H", "--URL", help="LDB URL for database or target server", 196 type=str, metavar="URL", dest="H"), 197 Option("--objectdn", help="DN of the object whose SD to modify", 198 type="string"), 199 ] 200 201 def read_descriptor(self, samdb, object_dn): 202 res = samdb.search(base=object_dn, scope=SCOPE_BASE, 203 attrs=["nTSecurityDescriptor"]) 204 # we should theoretically always have an SD 205 assert(len(res) == 1) 206 desc = res[0]["nTSecurityDescriptor"][0] 207 return ndr_unpack(security.descriptor, desc) 208 209 def get_domain_sid(self, samdb): 210 res = samdb.search(base=samdb.domain_dn(), 211 expression="(objectClass=*)", scope=SCOPE_BASE) 212 return ndr_unpack( security.dom_sid,res[0]["objectSid"][0]) 213 214 def print_acl(self, samdb, object_dn): 215 desc = self.read_descriptor(samdb, object_dn) 216 desc_sddl = desc.as_sddl(self.get_domain_sid(samdb)) 217 self.outf.write("descriptor for %s:\n" % object_dn) 218 self.outf.write(desc_sddl + "\n") 219 220 def run(self, objectdn, 221 H=None, credopts=None, sambaopts=None, versionopts=None): 222 lp = sambaopts.get_loadparm() 223 creds = credopts.get_credentials(lp) 224 225 samdb = SamDB(url=H, session_info=system_session(), 226 credentials=creds, lp=lp) 227 self.print_acl(samdb, objectdn) 228 229 230class cmd_dsacl(SuperCommand): 231 """DS ACLs manipulation.""" 232 233 subcommands = {} 234 subcommands["set"] = cmd_dsacl_set() 235 subcommands["get"] = cmd_dsacl_get() 236