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 */
59static TDB_CONTEXT *tdb;
60
61
62/* initialise the privilege database */
63BOOL 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*/
78static 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*/
89static 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*/
98NTSTATUS 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 = malloc(sizeof(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*/
144static 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 = 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*/
184NTSTATUS 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 = Realloc(current_sids, sizeof(current_sids[0]) * (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*/
234NTSTATUS 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*/
281static 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*/
306NTSTATUS 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) = (char **)Realloc(*rights,sizeof(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] = strdup(right);
334			(*count)++;
335		}
336
337		free(key.dptr);
338	}
339
340	return NT_STATUS_OK;
341}
342