1156701Smux/*-
2156701Smux * Copyright (c) 2006, Maxime Henrion <mux@FreeBSD.org>
3156701Smux * All rights reserved.
4156701Smux *
5156701Smux * Redistribution and use in source and binary forms, with or without
6156701Smux * modification, are permitted provided that the following conditions
7156701Smux * are met:
8156701Smux * 1. Redistributions of source code must retain the above copyright
9156701Smux *    notice, this list of conditions and the following disclaimer.
10156701Smux * 2. Redistributions in binary form must reproduce the above copyright
11156701Smux *    notice, this list of conditions and the following disclaimer in the
12156701Smux *    documentation and/or other materials provided with the distribution.
13156701Smux *
14156701Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15156701Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16156701Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17156701Smux * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18156701Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19156701Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20156701Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21156701Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22156701Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23156701Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24156701Smux * SUCH DAMAGE.
25156701Smux *
26156701Smux * $FreeBSD$
27156701Smux */
28156701Smux#include <sys/types.h>
29156701Smux
30156701Smux#include <assert.h>
31156701Smux#include <grp.h>
32156701Smux#include <pthread.h>
33156701Smux#include <pwd.h>
34156701Smux#include <stdlib.h>
35156701Smux#include <string.h>
36156701Smux
37156701Smux#include "idcache.h"
38156701Smux#include "misc.h"
39156701Smux
40156701Smux/*
41156701Smux * Constants and data structures used to implement the thread-safe
42156701Smux * group and password file caches.  Cache sizes must be prime.
43156701Smux */
44156701Smux#define	UIDTONAME_SZ		317	/* Size of uid -> user name cache */
45156701Smux#define	NAMETOUID_SZ		317	/* Size of user name -> uid cache */
46156701Smux#define	GIDTONAME_SZ		317	/* Size of gid -> group name cache */
47156701Smux#define	NAMETOGID_SZ		317	/* Size of group name -> gid cache */
48156701Smux
49156701Smux/* Node structures used to cache lookups. */
50156701Smuxstruct uidc {
51156701Smux	char *name;		/* user name */
52156701Smux	uid_t uid;		/* cached uid */
53156701Smux	int valid;		/* is this a valid or a miss entry */
54156701Smux	struct uidc *next;	/* for collisions */
55156701Smux};
56156701Smux
57156701Smuxstruct gidc {
58156701Smux	char *name;		/* group name */
59156701Smux	gid_t gid;		/* cached gid */
60156701Smux	int valid;		/* is this a valid or a miss entry */
61156701Smux	struct gidc *next;	/* for collisions */
62156701Smux};
63156701Smux
64156701Smuxstatic struct uidc **uidtoname;	/* uid to user name cache */
65156701Smuxstatic struct gidc **gidtoname;	/* gid to group name cache */
66156701Smuxstatic struct uidc **nametouid;	/* user name to uid cache */
67156701Smuxstatic struct gidc **nametogid;	/* group name to gid cache */
68156701Smux
69156701Smuxstatic pthread_mutex_t uid_mtx;
70156701Smuxstatic pthread_mutex_t gid_mtx;
71156701Smux
72156701Smuxstatic void		uid_lock(void);
73156701Smuxstatic void		uid_unlock(void);
74156701Smuxstatic void		gid_lock(void);
75156701Smuxstatic void		gid_unlock(void);
76156701Smux
77156701Smuxstatic uint32_t		hash(const char *);
78156701Smux
79156701Smux/* A 32-bit version of Peter Weinberger's (PJW) hash algorithm,
80156701Smux    as used by ELF for hashing function names. */
81156701Smuxstatic uint32_t
82156701Smuxhash(const char *name)
83156701Smux{
84156701Smux	uint32_t g, h;
85156701Smux
86156701Smux	h = 0;
87156701Smux	while(*name != '\0') {
88156701Smux		h = (h << 4) + *name++;
89156701Smux		if ((g = h & 0xF0000000)) {
90156701Smux			h ^= g >> 24;
91156701Smux			h &= 0x0FFFFFFF;
92156701Smux		}
93156701Smux	}
94156701Smux	return (h);
95156701Smux}
96156701Smux
97156701Smuxstatic void
98156701Smuxuid_lock(void)
99156701Smux{
100156701Smux	int error;
101156701Smux
102156701Smux	error = pthread_mutex_lock(&uid_mtx);
103156701Smux	assert(!error);
104156701Smux}
105156701Smux
106156701Smuxstatic void
107156701Smuxuid_unlock(void)
108156701Smux{
109156701Smux	int error;
110156701Smux
111156701Smux	error = pthread_mutex_unlock(&uid_mtx);
112156701Smux	assert(!error);
113156701Smux}
114156701Smux
115156701Smuxstatic void
116156701Smuxgid_lock(void)
117156701Smux{
118156701Smux	int error;
119156701Smux
120156701Smux	error = pthread_mutex_lock(&gid_mtx);
121156701Smux	assert(!error);
122156701Smux}
123156701Smux
124156701Smuxstatic void
125156701Smuxgid_unlock(void)
126156701Smux{
127156701Smux	int error;
128156701Smux
129156701Smux	error = pthread_mutex_unlock(&gid_mtx);
130156701Smux	assert(!error);
131156701Smux}
132156701Smux
133156701Smuxstatic void
134156701Smuxuidc_insert(struct uidc **tbl, struct uidc *uidc, uint32_t key)
135156701Smux{
136156701Smux
137156701Smux	uidc->next = tbl[key];
138156701Smux	tbl[key] = uidc;
139156701Smux}
140156701Smux
141156701Smuxstatic void
142156701Smuxgidc_insert(struct gidc **tbl, struct gidc *gidc, uint32_t key)
143156701Smux{
144156701Smux
145156701Smux	gidc->next = tbl[key];
146156701Smux	tbl[key] = gidc;
147156701Smux}
148156701Smux
149156701Smux/* Return the user name for this uid, or NULL if it's not found. */
150156701Smuxchar *
151156701Smuxgetuserbyid(uid_t uid)
152156701Smux{
153156701Smux	struct passwd *pw;
154156701Smux	struct uidc *uidc, *uidc2;
155156701Smux	uint32_t key, key2;
156156701Smux
157156701Smux	key = uid % UIDTONAME_SZ;
158156701Smux	uid_lock();
159156701Smux	uidc = uidtoname[key];
160156701Smux	while (uidc != NULL) {
161156701Smux		if (uidc->uid == uid)
162156701Smux			break;
163156701Smux		uidc = uidc->next;
164156701Smux	}
165156701Smux
166156701Smux	if (uidc == NULL) {
167156701Smux		/* We didn't find this uid, look it up and add it. */
168156701Smux		uidc = xmalloc(sizeof(struct uidc));
169156701Smux		uidc->uid = uid;
170156701Smux		pw = getpwuid(uid);
171156701Smux		if (pw != NULL) {
172156701Smux			/* This uid is in the password file. */
173156701Smux			uidc->name = xstrdup(pw->pw_name);
174156701Smux			uidc->valid = 1;
175156701Smux			/* Also add it to the name -> gid table. */
176156701Smux			uidc2 = xmalloc(sizeof(struct uidc));
177156701Smux			uidc2->uid = uid;
178156701Smux			uidc2->name = uidc->name; /* We reuse the pointer. */
179156701Smux			uidc2->valid = 1;
180156701Smux			key2 = hash(uidc->name) % NAMETOUID_SZ;
181156701Smux			uidc_insert(nametouid, uidc2, key2);
182156701Smux		} else {
183156701Smux			/* Add a miss entry for this uid. */
184156701Smux			uidc->name = NULL;
185156701Smux			uidc->valid = 0;
186156701Smux		}
187156701Smux		uidc_insert(uidtoname, uidc, key);
188156701Smux	}
189156701Smux	/* It is safe to unlock here since the cache structure
190156701Smux	   is not going to get freed or changed. */
191156701Smux	uid_unlock();
192156701Smux	return (uidc->name);
193156701Smux}
194156701Smux
195156701Smux/* Return the group name for this gid, or NULL if it's not found. */
196156701Smuxchar *
197156701Smuxgetgroupbyid(gid_t gid)
198156701Smux{
199156701Smux	struct group *gr;
200156701Smux	struct gidc *gidc, *gidc2;
201156701Smux	uint32_t key, key2;
202156701Smux
203156701Smux	key = gid % GIDTONAME_SZ;
204156701Smux	gid_lock();
205156701Smux	gidc = gidtoname[key];
206156701Smux	while (gidc != NULL) {
207156701Smux		if (gidc->gid == gid)
208156701Smux			break;
209156701Smux		gidc = gidc->next;
210156701Smux	}
211156701Smux
212156701Smux	if (gidc == NULL) {
213156701Smux		/* We didn't find this gid, look it up and add it. */
214156701Smux		gidc = xmalloc(sizeof(struct gidc));
215156701Smux		gidc->gid = gid;
216156701Smux		gr = getgrgid(gid);
217156701Smux		if (gr != NULL) {
218156701Smux			/* This gid is in the group file. */
219156701Smux			gidc->name = xstrdup(gr->gr_name);
220156701Smux			gidc->valid = 1;
221156701Smux			/* Also add it to the name -> gid table. */
222156701Smux			gidc2 = xmalloc(sizeof(struct gidc));
223156701Smux			gidc2->gid = gid;
224156701Smux			gidc2->name = gidc->name; /* We reuse the pointer. */
225156701Smux			gidc2->valid = 1;
226156701Smux			key2 = hash(gidc->name) % NAMETOGID_SZ;
227156701Smux			gidc_insert(nametogid, gidc2, key2);
228156701Smux		} else {
229156701Smux			/* Add a miss entry for this gid. */
230156701Smux			gidc->name = NULL;
231156701Smux			gidc->valid = 0;
232156701Smux		}
233156701Smux		gidc_insert(gidtoname, gidc, key);
234156701Smux	}
235156701Smux	/* It is safe to unlock here since the cache structure
236156701Smux	   is not going to get freed or changed. */
237156701Smux	gid_unlock();
238156701Smux	return (gidc->name);
239156701Smux}
240156701Smux
241156701Smux/* Finds the uid for this user name.  If it's found, the gid is stored
242156701Smux   in *uid and 0 is returned.  Otherwise, -1 is returned. */
243156701Smuxint
244156701Smuxgetuidbyname(const char *name, uid_t *uid)
245156701Smux{
246156701Smux	struct passwd *pw;
247156701Smux	struct uidc *uidc, *uidc2;
248156701Smux	uint32_t key, key2;
249156701Smux
250156701Smux	uid_lock();
251156701Smux	key = hash(name) % NAMETOUID_SZ;
252156701Smux	uidc = nametouid[key];
253156701Smux	while (uidc != NULL) {
254156701Smux		if (strcmp(uidc->name, name) == 0)
255156701Smux			break;
256156701Smux		uidc = uidc->next;
257156701Smux	}
258156701Smux
259156701Smux	if (uidc == NULL) {
260156701Smux		uidc = xmalloc(sizeof(struct uidc));
261156701Smux		uidc->name = xstrdup(name);
262156701Smux		pw = getpwnam(name);
263156701Smux		if (pw != NULL) {
264156701Smux			/* This user name is in the password file. */
265156701Smux			uidc->valid = 1;
266156701Smux			uidc->uid = pw->pw_uid;
267156701Smux			/* Also add it to the uid -> name table. */
268156701Smux			uidc2 = xmalloc(sizeof(struct uidc));
269156701Smux			uidc2->name = uidc->name; /* We reuse the pointer. */
270156701Smux			uidc2->uid = uidc->uid;
271156701Smux			uidc2->valid = 1;
272156701Smux			key2 = uidc2->uid % UIDTONAME_SZ;
273156701Smux			uidc_insert(uidtoname, uidc2, key2);
274156701Smux		} else {
275156701Smux			/* Add a miss entry for this user name. */
276156701Smux			uidc->valid = 0;
277156701Smux			uidc->uid = (uid_t)-1; /* Should not be accessed. */
278156701Smux		}
279156701Smux		uidc_insert(nametouid, uidc, key);
280156701Smux	}
281156701Smux	/* It is safe to unlock here since the cache structure
282156701Smux	   is not going to get freed or changed. */
283156701Smux	uid_unlock();
284156701Smux	if (!uidc->valid)
285156701Smux		return (-1);
286156701Smux	*uid = uidc->uid;
287156701Smux	return (0);
288156701Smux}
289156701Smux
290156701Smux/* Finds the gid for this group name.  If it's found, the gid is stored
291156701Smux   in *gid and 0 is returned.  Otherwise, -1 is returned. */
292156701Smuxint
293156701Smuxgetgidbyname(const char *name, gid_t *gid)
294156701Smux{
295156701Smux	struct group *gr;
296156701Smux	struct gidc *gidc, *gidc2;
297156701Smux	uint32_t key, key2;
298156701Smux
299156701Smux	gid_lock();
300156701Smux	key = hash(name) % NAMETOGID_SZ;
301156701Smux	gidc = nametogid[key];
302156701Smux	while (gidc != NULL) {
303156701Smux		if (strcmp(gidc->name, name) == 0)
304156701Smux			break;
305156701Smux		gidc = gidc->next;
306156701Smux	}
307156701Smux
308156701Smux	if (gidc == NULL) {
309156701Smux		gidc = xmalloc(sizeof(struct gidc));
310156701Smux		gidc->name = xstrdup(name);
311156701Smux		gr = getgrnam(name);
312156701Smux		if (gr != NULL) {
313156701Smux			/* This group name is in the group file. */
314156701Smux			gidc->gid = gr->gr_gid;
315156701Smux			gidc->valid = 1;
316156701Smux			/* Also add it to the gid -> name table. */
317156701Smux			gidc2 = xmalloc(sizeof(struct gidc));
318156701Smux			gidc2->name = gidc->name; /* We reuse the pointer. */
319156701Smux			gidc2->gid = gidc->gid;
320156701Smux			gidc2->valid = 1;
321156701Smux			key2 = gidc2->gid % GIDTONAME_SZ;
322156701Smux			gidc_insert(gidtoname, gidc2, key2);
323156701Smux		} else {
324156701Smux			/* Add a miss entry for this group name. */
325156701Smux			gidc->gid = (gid_t)-1; /* Should not be accessed. */
326156701Smux			gidc->valid = 0;
327156701Smux		}
328156701Smux		gidc_insert(nametogid, gidc, key);
329156701Smux	}
330156701Smux	/* It is safe to unlock here since the cache structure
331156701Smux	   is not going to get freed or changed. */
332156701Smux	gid_unlock();
333156701Smux	if (!gidc->valid)
334156701Smux		return (-1);
335156701Smux	*gid = gidc->gid;
336156701Smux	return (0);
337156701Smux}
338156701Smux
339156701Smux/* Initialize the cache structures. */
340156701Smuxvoid
341156701Smuxidcache_init(void)
342156701Smux{
343156701Smux
344156701Smux	pthread_mutex_init(&uid_mtx, NULL);
345156701Smux	pthread_mutex_init(&gid_mtx, NULL);
346156701Smux	uidtoname = xmalloc(UIDTONAME_SZ * sizeof(struct uidc *));
347156701Smux	gidtoname = xmalloc(GIDTONAME_SZ * sizeof(struct gidc *));
348156701Smux	nametouid = xmalloc(NAMETOUID_SZ * sizeof(struct uidc *));
349156701Smux	nametogid = xmalloc(NAMETOGID_SZ * sizeof(struct gidc *));
350156701Smux	memset(uidtoname, 0, UIDTONAME_SZ * sizeof(struct uidc *));
351156701Smux	memset(gidtoname, 0, GIDTONAME_SZ * sizeof(struct gidc *));
352156701Smux	memset(nametouid, 0, NAMETOUID_SZ * sizeof(struct uidc *));
353156701Smux	memset(nametogid, 0, NAMETOGID_SZ * sizeof(struct gidc *));
354156701Smux}
355156701Smux
356156701Smux/* Cleanup the cache structures. */
357156701Smuxvoid
358156701Smuxidcache_fini(void)
359156701Smux{
360156701Smux	struct uidc *uidc, *uidc2;
361156701Smux	struct gidc *gidc, *gidc2;
362156701Smux	size_t i;
363156701Smux
364156701Smux	for (i = 0; i < UIDTONAME_SZ; i++) {
365156701Smux		uidc = uidtoname[i];
366156701Smux		while (uidc != NULL) {
367156701Smux			if (uidc->name != NULL) {
368156701Smux				assert(uidc->valid);
369156701Smux				free(uidc->name);
370156701Smux			}
371156701Smux			uidc2 = uidc->next;
372156701Smux			free(uidc);
373156701Smux			uidc = uidc2;
374156701Smux		}
375156701Smux	}
376156701Smux	free(uidtoname);
377156701Smux	for (i = 0; i < NAMETOUID_SZ; i++) {
378156701Smux		uidc = nametouid[i];
379156701Smux		while (uidc != NULL) {
380156701Smux			assert(uidc->name != NULL);
381156701Smux			/* If it's a valid entry, it has been added to both the
382156701Smux			   uidtoname and nametouid tables, and the name pointer
383156701Smux			   has been reused for both entries.  Thus, the name
384156701Smux			   pointer has already been freed in the loop above. */
385156701Smux			if (!uidc->valid)
386156701Smux				free(uidc->name);
387156701Smux			uidc2 = uidc->next;
388156701Smux			free(uidc);
389156701Smux			uidc = uidc2;
390156701Smux		}
391156701Smux	}
392156701Smux	free(nametouid);
393156701Smux	for (i = 0; i < GIDTONAME_SZ; i++) {
394156701Smux		gidc = gidtoname[i];
395156701Smux		while (gidc != NULL) {
396156701Smux			if (gidc->name != NULL) {
397156701Smux				assert(gidc->valid);
398156701Smux				free(gidc->name);
399156701Smux			}
400156701Smux			gidc2 = gidc->next;
401156701Smux			free(gidc);
402156701Smux			gidc = gidc2;
403156701Smux		}
404156701Smux	}
405156701Smux	free(gidtoname);
406156701Smux	for (i = 0; i < NAMETOGID_SZ; i++) {
407156701Smux		gidc = nametogid[i];
408156701Smux		while (gidc != NULL) {
409156701Smux			assert(gidc->name != NULL);
410156701Smux			/* See above comment. */
411156701Smux			if (!gidc->valid)
412156701Smux				free(gidc->name);
413156701Smux			gidc2 = gidc->next;
414156701Smux			free(gidc);
415156701Smux			gidc = gidc2;
416156701Smux		}
417156701Smux	}
418156701Smux	free(nametogid);
419156701Smux	pthread_mutex_destroy(&uid_mtx);
420156701Smux	pthread_mutex_destroy(&gid_mtx);
421156701Smux}
422