r4088: Get medieval on our ass about malloc.... :-). Take control of all our allocation
[tprouty/samba.git] / source / passdb / privileges.c
1 /*
2  * Unix SMB/CIFS implementation. 
3  *
4  * default privileges backend for passdb
5  *
6  * Copyright (C) Andrew Tridgell 2003
7  * 
8  * This program is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  * 
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  * 
18  * You should have received a copy of the GNU General Public License along with
19  * this program; if not, write to the Free Software Foundation, Inc., 675
20  * Mass Ave, Cambridge, MA 02139, USA.
21  */
22
23 #include "includes.h"
24
25 /*
26   this is a local implementation of a privileges backend, with
27   privileges stored in a tdb. Most passdb implementations will
28   probably use this backend, although some (such as pdb_ldap) will
29   store the privileges in another manner.
30
31   The basic principle is that the backend should store a list of SIDs
32   associated with each right, where a right is a string name such as
33   'SeTakeOwnershipPrivilege'. The SIDs can be of any type, and do not
34   need to belong to the local domain.
35
36   The way this is used is that certain places in the code which
37   require access control will ask the privileges backend 'does this
38   user have the following privilege'. The 'user' will be a NT_TOKEN,
39   which is essentially just a list of SIDs. If any of those SIDs are
40   listed in the list of SIDs for that privilege then the answer will
41   be 'yes'. That will usually mean that the user gets unconditional
42   access to that functionality, regradless of any ACLs. In this way
43   privileges act in a similar fashion to unix setuid bits.
44 */
45
46 /*
47   The terms 'right' and 'privilege' are used interchangably in this
48   file. This follows MSDN convention where the LSA calls are calls on
49   'rights', which really means privileges. My apologies for the
50   confusion.
51 */
52
53
54 /* 15 seconds seems like an ample time for timeouts on the privileges db */
55 #define LOCK_TIMEOUT 15
56
57
58 /* the tdb handle for the privileges database */
59 static TDB_CONTEXT *tdb;
60
61
62 /* initialise the privilege database */
63 BOOL privilege_init(void)
64 {
65         tdb = tdb_open_log(lock_path("privilege.tdb"), 0, TDB_DEFAULT, 
66                            O_RDWR|O_CREAT, 0600);
67         if (!tdb) {
68                 DEBUG(0,("Failed to open privilege database\n"));
69                 return False;
70         }
71
72         return True;
73 }
74
75 /* 
76    lock the record for a particular privilege (write lock)
77 */
78 static NTSTATUS privilege_lock_right(const char *right) 
79 {
80         if (tdb_lock_bystring(tdb, right, LOCK_TIMEOUT) != 0) {
81                 return NT_STATUS_INTERNAL_ERROR;
82         }
83         return NT_STATUS_OK;
84 }
85
86 /* 
87    unlock the record for a particular privilege (write lock)
88 */
89 static void privilege_unlock_right(const char *right) 
90 {
91         tdb_unlock_bystring(tdb, right);
92 }
93
94
95 /* 
96    return a list of SIDs that have a particular right
97 */
98 NTSTATUS privilege_enum_account_with_right(const char *right, 
99                                            uint32 *count, 
100                                            DOM_SID **sids)
101 {
102         TDB_DATA data;
103         char *p;
104         int i;
105
106         if (!tdb) {
107                 return NT_STATUS_INTERNAL_ERROR;
108         }
109
110         data = tdb_fetch_bystring(tdb, right);
111         if (!data.dptr) {
112                 *count = 0;
113                 *sids = NULL;
114                 return NT_STATUS_OK;
115         }
116
117         /* count them */
118         for (i=0, p=data.dptr; p<data.dptr+data.dsize; i++) {
119                 p += strlen(p) + 1;
120         }
121         *count = i;
122
123         /* allocate and parse */
124         *sids = SMB_MALLOC_ARRAY(DOM_SID, *count);
125         if (! *sids) {
126                 return NT_STATUS_NO_MEMORY;
127         }
128         for (i=0, p=data.dptr; p<data.dptr+data.dsize; i++) {
129                 if (!string_to_sid(&(*sids)[i], p)) {
130                         free(data.dptr);
131                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
132                 }
133                 p += strlen(p) + 1;
134         }
135         
136         free(data.dptr);
137
138         return NT_STATUS_OK;
139 }
140
141 /* 
142    set what accounts have a given right - this is an internal interface
143 */
144 static NTSTATUS privilege_set_accounts_with_right(const char *right, 
145                                                   uint32 count, 
146                                                   DOM_SID *sids)
147 {
148         TDB_DATA data;
149         char *p;
150         int i;
151
152         if (!tdb) {
153                 return NT_STATUS_INTERNAL_ERROR;
154         }
155
156         /* allocate the maximum size that we might use */
157         data.dptr = SMB_MALLOC(count * ((MAXSUBAUTHS*11) + 30));
158         if (!data.dptr) {
159                 return NT_STATUS_NO_MEMORY;
160         }
161
162         p = data.dptr;
163
164         for (i=0;i<count;i++) {
165                 sid_to_string(p, &sids[i]);
166                 p += strlen(p) + 1;
167         }
168
169         data.dsize = PTR_DIFF(p, data.dptr);
170
171         if (tdb_store_bystring(tdb, right, data, TDB_REPLACE) != 0) {
172                 free(data.dptr);
173                 return NT_STATUS_INTERNAL_ERROR;
174         }
175
176         free(data.dptr);
177         return NT_STATUS_OK;
178 }
179
180
181 /* 
182    add a SID to the list of SIDs for a right
183 */
184 NTSTATUS privilege_add_account_right(const char *right, 
185                                      DOM_SID *sid)
186 {
187         NTSTATUS status;
188         DOM_SID *current_sids;
189         uint32 current_count;
190         int i;
191
192         status = privilege_lock_right(right);
193         if (!NT_STATUS_IS_OK(status)) {
194                 return status;
195         }
196
197         status = privilege_enum_account_with_right(right, &current_count, &current_sids);
198         if (!NT_STATUS_IS_OK(status)) {
199                 privilege_unlock_right(right);
200                 return status;
201         }       
202
203         /* maybe that SID is already listed? this is not an error */
204         for (i=0;i<current_count;i++) {
205                 if (sid_equal(&current_sids[i], sid)) {
206                         privilege_unlock_right(right);
207                         free(current_sids);
208                         return NT_STATUS_OK;
209                 }
210         }
211
212         /* add it in */
213         current_sids = SMB_REALLOC_ARRAY(current_sids, DOM_SID, current_count+1);
214         if (!current_sids) {
215                 privilege_unlock_right(right);
216                 return NT_STATUS_NO_MEMORY;
217         }
218
219         sid_copy(&current_sids[current_count], sid);
220         current_count++;
221         
222         status = privilege_set_accounts_with_right(right, current_count, current_sids);
223
224         free(current_sids);
225         privilege_unlock_right(right);
226
227         return status;
228 }
229
230
231 /* 
232    remove a SID from the list of SIDs for a right
233 */
234 NTSTATUS privilege_remove_account_right(const char *right, 
235                                         DOM_SID *sid)
236 {
237         NTSTATUS status;
238         DOM_SID *current_sids;
239         uint32 current_count;
240         int i;
241
242         status = privilege_lock_right(right);
243         if (!NT_STATUS_IS_OK(status)) {
244                 return status;
245         }
246
247         status = privilege_enum_account_with_right(right, &current_count, &current_sids);
248         if (!NT_STATUS_IS_OK(status)) {
249                 privilege_unlock_right(right);
250                 return status;
251         }       
252
253         for (i=0;i<current_count;i++) {
254                 if (sid_equal(&current_sids[i], sid)) {
255                         /* found it - so remove it */
256                         if (current_count-i > 1) {
257                                 memmove(&current_sids[i], &current_sids[i+1],
258                                         sizeof(current_sids[0]) * ((current_count-i)-1));
259                         }
260                         current_count--;
261                         status = privilege_set_accounts_with_right(right, 
262                                                                    current_count, 
263                                                                    current_sids);
264                         free(current_sids);
265                         privilege_unlock_right(right);
266                         return status;
267                 }
268         }
269
270         /* removing a right that you don't have is not an error */
271         
272         safe_free(current_sids);
273         privilege_unlock_right(right);
274         return NT_STATUS_OK;
275 }
276
277
278 /*
279   an internal function for checking if a SID has a right
280 */
281 static BOOL privilege_sid_has_right(DOM_SID *sid, const char *right)
282 {
283         NTSTATUS status;
284         uint32 count;
285         DOM_SID *sids;
286         int i;
287
288         status = privilege_enum_account_with_right(right, &count, &sids);
289         if (!NT_STATUS_IS_OK(status)) {
290                 return False;
291         }
292         for (i=0;i<count;i++) {
293                 if (sid_equal(sid, &sids[i])) {
294                         free(sids);
295                         return True;
296                 }
297         }       
298
299         safe_free(sids);
300         return False;
301 }
302
303 /* 
304    list the rights for an account. This involves traversing the database
305 */
306 NTSTATUS privilege_enum_account_rights(DOM_SID *sid,
307                                        uint32 *count,
308                                        char ***rights)
309 {
310         TDB_DATA key, nextkey;
311         char *right;
312
313         if (!tdb) {
314                 return NT_STATUS_INTERNAL_ERROR;
315         }
316
317         *rights = NULL;
318         *count = 0;
319
320         for (key = tdb_firstkey(tdb); key.dptr; key = nextkey) {
321                 nextkey = tdb_nextkey(tdb, key);
322
323                 right = key.dptr;
324                 
325                 if (privilege_sid_has_right(sid, right)) {
326                         (*rights) = SMB_REALLOC_ARRAY(*rights,char *, (*count)+1);
327                         if (! *rights) {
328                                 safe_free(nextkey.dptr);
329                                 free(key.dptr);
330                                 return NT_STATUS_NO_MEMORY;
331                         }
332
333                         (*rights)[*count] = SMB_STRDUP(right);
334                         (*count)++;
335                 }
336
337                 free(key.dptr);
338         }
339
340         return NT_STATUS_OK;
341 }