1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * Sid manipulation (stubs).
29 */
30
31#include <sys/atomic.h>
32#include <sys/avl.h>
33#include <sys/cmn_err.h>
34#include <sys/kmem.h>
35#include <sys/mutex.h>
36#include <sys/sid.h>
37#include <sys/sysmacros.h>
38#include <sys/systm.h>
39#include <sys/kidmap.h>
40#include <sys/idmap.h>
41
42static kmutex_t sid_lock;
43static avl_tree_t sid_tree;
44static boolean_t sid_inited = B_FALSE;
45
46static ksiddomain_t
47*ksid_enterdomain(const char *dom)
48{
49	size_t len = strlen(dom) + 1;
50	ksiddomain_t *res;
51
52	ASSERT(MUTEX_HELD(&sid_lock));
53	res = kmem_alloc(sizeof (ksiddomain_t), KM_SLEEP);
54	res->kd_len = (uint_t)len;
55	res->kd_name = kmem_alloc(len, KM_SLEEP);
56	bcopy(dom, res->kd_name, len);
57
58	res->kd_ref = 1;
59
60	avl_add(&sid_tree, res);
61
62	return (res);
63}
64
65void
66ksid_hold(ksid_t *ks)
67{
68	if (ks->ks_domain != NULL)
69		ksiddomain_hold(ks->ks_domain);
70}
71
72void
73ksid_rele(ksid_t *ks)
74{
75	if (ks->ks_domain != NULL)
76		ksiddomain_rele(ks->ks_domain);
77}
78
79void
80ksiddomain_hold(ksiddomain_t *kd)
81{
82	atomic_add_32(&kd->kd_ref, 1);
83}
84
85void
86ksiddomain_rele(ksiddomain_t *kd)
87{
88	if (atomic_add_32_nv(&kd->kd_ref, -1) == 0) {
89		/*
90		 * The kd reference can only be incremented from 0 when
91		 * the sid_lock is held; so we lock and then check need to
92		 * check for 0 again.
93		 */
94		mutex_enter(&sid_lock);
95		if (kd->kd_ref == 0) {
96			avl_remove(&sid_tree, kd);
97			kmem_free(kd->kd_name, kd->kd_len);
98			kmem_free(kd, sizeof (*kd));
99		}
100		mutex_exit(&sid_lock);
101	}
102}
103
104void
105ksidlist_hold(ksidlist_t *ksl)
106{
107	atomic_add_32(&ksl->ksl_ref, 1);
108}
109
110void
111ksidlist_rele(ksidlist_t *ksl)
112{
113	if (atomic_add_32_nv(&ksl->ksl_ref, -1) == 0) {
114		int i;
115
116		for (i = 0; i < ksl->ksl_nsid; i++)
117			ksid_rele(&ksl->ksl_sids[i]);
118
119		kmem_free(ksl, KSIDLIST_MEM(ksl->ksl_nsid));
120	}
121}
122
123static int
124ksid_cmp(const void *a, const void *b)
125{
126	const ksiddomain_t *ap = a;
127	const ksiddomain_t *bp = b;
128	int res;
129
130	res = strcmp(ap->kd_name, bp->kd_name);
131	if (res > 0)
132		return (1);
133	if (res != 0)
134		return (-1);
135	return (0);
136}
137
138/*
139 * Lookup the named domain in the AVL tree.
140 * If no entry is found, add the domain to the AVL tree.
141 * The domain is returned held and needs to be released
142 * when done.
143 */
144ksiddomain_t
145*ksid_lookupdomain(const char *dom)
146{
147	ksiddomain_t *res;
148	ksiddomain_t tmpl;
149
150	mutex_enter(&sid_lock);
151
152	if (!sid_inited) {
153		avl_create(&sid_tree, ksid_cmp, sizeof (ksiddomain_t),
154		    offsetof(ksiddomain_t, kd_link));
155
156		res = ksid_enterdomain(dom);
157		sid_inited = B_TRUE;
158		mutex_exit(&sid_lock);
159		return (res);
160	}
161
162	tmpl.kd_name = (char *)dom;
163
164	res = avl_find(&sid_tree, &tmpl, NULL);
165	if (res == NULL) {
166		res = ksid_enterdomain(dom);
167	} else {
168		ksiddomain_hold(res);
169	}
170
171	mutex_exit(&sid_lock);
172	return (res);
173}
174
175const char *
176ksid_getdomain(ksid_t *ks)
177{
178	return (ks->ks_domain->kd_name);
179}
180
181uint_t
182ksid_getrid(ksid_t *ks)
183{
184	return (ks->ks_rid);
185}
186
187uid_t
188ksid_getid(ksid_t *ks)
189{
190	return (ks->ks_id);
191}
192
193int
194ksid_lookupbyuid(zone_t *zone, uid_t id, ksid_t *res)
195{
196	const char *sid_prefix;
197
198	if (kidmap_getsidbyuid(zone, id, &sid_prefix, &res->ks_rid)
199	    != IDMAP_SUCCESS)
200		return (-1);
201
202	res->ks_domain = ksid_lookupdomain(sid_prefix);
203
204	res->ks_id = id;
205
206	return (0);
207}
208
209int
210ksid_lookupbygid(zone_t *zone, gid_t id, ksid_t *res)
211{
212	const char *sid_prefix;
213
214	if (kidmap_getsidbygid(zone, id, &sid_prefix, &res->ks_rid)
215	    != IDMAP_SUCCESS)
216		return (-1);
217
218	res->ks_domain = ksid_lookupdomain(sid_prefix);
219
220	res->ks_id = id;
221
222	return (0);
223}
224
225credsid_t *
226kcrsid_alloc(void)
227{
228	credsid_t *kcr = kmem_zalloc(sizeof (*kcr), KM_SLEEP);
229	kcr->kr_ref = 1;
230	return (kcr);
231}
232
233/*
234 * Returns a credsid_t with a refcount of 1.
235 */
236static credsid_t *
237kcrsid_dup(credsid_t *org)
238{
239	credsid_t *new;
240	ksid_index_t ki;
241
242	if (org == NULL)
243		return (kcrsid_alloc());
244	if (org->kr_ref == 1)
245		return (org);
246	new = kcrsid_alloc();
247
248	/* Copy, then update reference counts */
249	*new = *org;
250	new->kr_ref = 1;
251	for (ki = 0; ki < KSID_COUNT; ki++)
252		ksid_hold(&new->kr_sidx[ki]);
253
254	if (new->kr_sidlist != NULL)
255		ksidlist_hold(new->kr_sidlist);
256
257	kcrsid_rele(org);
258	return (new);
259}
260
261void
262kcrsid_hold(credsid_t *kcr)
263{
264	atomic_add_32(&kcr->kr_ref, 1);
265}
266
267void
268kcrsid_rele(credsid_t *kcr)
269{
270	if (atomic_add_32_nv(&kcr->kr_ref, -1) == 0) {
271		ksid_index_t i;
272
273		for (i = 0; i < KSID_COUNT; i++)
274			ksid_rele(&kcr->kr_sidx[i]);
275
276		if (kcr->kr_sidlist != NULL)
277			ksidlist_rele(kcr->kr_sidlist);
278
279		kmem_free(kcr, sizeof (*kcr));
280	}
281}
282
283/*
284 * Copy the SID credential into a previously allocated piece of memory.
285 */
286void
287kcrsidcopy_to(const credsid_t *okcr, credsid_t *nkcr)
288{
289	int i;
290
291	ASSERT(nkcr->kr_ref == 1);
292
293	if (okcr == NULL)
294		return;
295	*nkcr = *okcr;
296	for (i = 0; i < KSID_COUNT; i++)
297		ksid_hold(&nkcr->kr_sidx[i]);
298	if (nkcr->kr_sidlist != NULL)
299		ksidlist_hold(nkcr->kr_sidlist);
300	nkcr->kr_ref = 1;
301}
302
303static int
304kcrsid_sidcount(const credsid_t *kcr)
305{
306	int cnt = 0;
307	int i;
308
309	if (kcr == NULL)
310		return (0);
311
312	for (i = 0; i < KSID_COUNT; i++)
313		if (kcr->kr_sidx[i].ks_domain != NULL)
314			cnt++;
315
316	if (kcr->kr_sidlist != NULL)
317		cnt += kcr->kr_sidlist->ksl_nsid;
318	return (cnt);
319}
320
321/*
322 * Argument needs to be a ksid_t with a properly held ks_domain reference.
323 */
324credsid_t *
325kcrsid_setsid(credsid_t *okcr, ksid_t *ksp, ksid_index_t i)
326{
327	int ocnt = kcrsid_sidcount(okcr);
328	credsid_t *nkcr;
329
330	/*
331	 * Unset the particular ksid; if there are no other SIDs or if this
332	 * is the last SID, remove the auxilary data structure.
333	 */
334	if (ksp == NULL) {
335		if (ocnt == 0 ||
336		    (ocnt == 1 && okcr->kr_sidx[i].ks_domain != NULL)) {
337			if (okcr != NULL)
338				kcrsid_rele(okcr);
339			return (NULL);
340		}
341	}
342	nkcr = kcrsid_dup(okcr);
343	ksid_rele(&nkcr->kr_sidx[i]);
344	if (ksp == NULL)
345		bzero(&nkcr->kr_sidx[i], sizeof (ksid_t));
346	else
347		nkcr->kr_sidx[i] = *ksp;
348
349	return (nkcr);
350}
351
352/*
353 * Argument needs to be a ksidlist_t with properly held ks_domain references
354 * and a reference count taking the new reference into account.
355 */
356credsid_t *
357kcrsid_setsidlist(credsid_t *okcr, ksidlist_t *ksl)
358{
359	int ocnt = kcrsid_sidcount(okcr);
360	credsid_t *nkcr;
361
362	/*
363	 * Unset the sidlist; if there are no further SIDs, remove the
364	 * auxilary data structure.
365	 */
366	if (ksl == NULL) {
367		if (ocnt == 0 || (okcr->kr_sidlist != NULL &&
368		    ocnt == okcr->kr_sidlist->ksl_nsid)) {
369			if (okcr != NULL)
370				kcrsid_rele(okcr);
371			return (NULL);
372		}
373	}
374	nkcr = kcrsid_dup(okcr);
375	if (nkcr->kr_sidlist != NULL)
376		ksidlist_rele(nkcr->kr_sidlist);
377
378	nkcr->kr_sidlist = ksl;
379	return (nkcr);
380}
381
382ksidlist_t *
383kcrsid_gidstosids(zone_t *zone, int ngrp, gid_t *grp)
384{
385	int i;
386	ksidlist_t *list;
387	int cnt;
388
389	if (ngrp == 0)
390		return (NULL);
391
392	cnt = 0;
393	list = kmem_zalloc(KSIDLIST_MEM(ngrp), KM_SLEEP);
394
395	list->ksl_nsid = ngrp;
396	list->ksl_ref = 1;
397
398	for (i = 0; i < ngrp; i++) {
399		if (grp[i] > MAXUID) {
400			list->ksl_neid++;
401			if (ksid_lookupbygid(zone,
402			    grp[i], &list->ksl_sids[i]) != 0) {
403				while (--i >= 0)
404					ksid_rele(&list->ksl_sids[i]);
405				cnt = 0;
406				break;
407			}
408			cnt++;
409		} else {
410			list->ksl_sids[i].ks_id = grp[i];
411		}
412	}
413	if (cnt == 0) {
414		kmem_free(list, KSIDLIST_MEM(ngrp));
415		return (NULL);
416	}
417	return (list);
418}
419