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