1/*	$OpenBSD: pwcache.c,v 1.16 2022/12/27 17:10:06 jmc Exp $	*/
2
3/*-
4 * Copyright (c) 1992 Keith Muller.
5 * Copyright (c) 1992, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Keith Muller of the University of California, San Diego.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <sys/types.h>
37
38#include <assert.h>
39#include <grp.h>
40#include <pwd.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45
46/*
47 * Constants and data structures used to implement group and password file
48 * caches.  Name lengths have been chosen to be as large as those supported
49 * by the passwd and group files as well as the standard archive formats.
50 * CACHE SIZES MUST BE PRIME
51 */
52#define UNMLEN		32	/* >= user name found in any protocol */
53#define GNMLEN		32	/* >= group name found in any protocol */
54#define UID_SZ		317	/* size of uid to user_name cache */
55#define UNM_SZ		317	/* size of user_name to uid cache */
56#define GID_SZ		251	/* size of gid to group_name cache */
57#define GNM_SZ		251	/* size of group_name to gid cache */
58#define VALID		1	/* entry and name are valid */
59#define INVALID		2	/* entry valid, name NOT valid */
60
61/*
62 * Node structures used in the user, group, uid, and gid caches.
63 */
64
65typedef struct uidc {
66	int valid;		/* is this a valid or a miss entry */
67	char name[UNMLEN];	/* uid name */
68	uid_t uid;		/* cached uid */
69} UIDC;
70
71typedef struct gidc {
72	int valid;		/* is this a valid or a miss entry */
73	char name[GNMLEN];	/* gid name */
74	gid_t gid;		/* cached gid */
75} GIDC;
76
77/*
78 * Routines that control user, group, uid and gid caches.
79 * Traditional passwd/group cache routines perform quite poorly with
80 * archives. The chances of hitting a valid lookup with an archive is quite a
81 * bit worse than with files already resident on the file system. These misses
82 * create a MAJOR performance cost. To address this problem, these routines
83 * cache both hits and misses.
84 */
85
86static UIDC **uidtb;	/* uid to name cache */
87static GIDC **gidtb;	/* gid to name cache */
88static UIDC **usrtb;	/* user name to uid cache */
89static GIDC **grptb;	/* group name to gid cache */
90
91static u_int
92st_hash(const char *name, size_t len, int tabsz)
93{
94	u_int key = 0;
95
96	assert(name != NULL);
97
98	while (len--) {
99		key += *name++;
100		key = (key << 8) | (key >> 24);
101	}
102
103	return key % tabsz;
104}
105
106/*
107 * uidtb_start
108 *	creates an an empty uidtb
109 * Return:
110 *	0 if ok, -1 otherwise
111 */
112static int
113uidtb_start(void)
114{
115	static int fail = 0;
116
117	if (uidtb != NULL)
118		return 0;
119	if (fail)
120		return -1;
121	if ((uidtb = calloc(UID_SZ, sizeof(UIDC *))) == NULL) {
122		++fail;
123		return -1;
124	}
125	return 0;
126}
127
128/*
129 * gidtb_start
130 *	creates an an empty gidtb
131 * Return:
132 *	0 if ok, -1 otherwise
133 */
134static int
135gidtb_start(void)
136{
137	static int fail = 0;
138
139	if (gidtb != NULL)
140		return 0;
141	if (fail)
142		return -1;
143	if ((gidtb = calloc(GID_SZ, sizeof(GIDC *))) == NULL) {
144		++fail;
145		return -1;
146	}
147	return 0;
148}
149
150/*
151 * usrtb_start
152 *	creates an an empty usrtb
153 * Return:
154 *	0 if ok, -1 otherwise
155 */
156static int
157usrtb_start(void)
158{
159	static int fail = 0;
160
161	if (usrtb != NULL)
162		return 0;
163	if (fail)
164		return -1;
165	if ((usrtb = calloc(UNM_SZ, sizeof(UIDC *))) == NULL) {
166		++fail;
167		return -1;
168	}
169	return 0;
170}
171
172/*
173 * grptb_start
174 *	creates an an empty grptb
175 * Return:
176 *	0 if ok, -1 otherwise
177 */
178static int
179grptb_start(void)
180{
181	static int fail = 0;
182
183	if (grptb != NULL)
184		return 0;
185	if (fail)
186		return -1;
187	if ((grptb = calloc(GNM_SZ, sizeof(GIDC *))) == NULL) {
188		++fail;
189		return -1;
190	}
191	return 0;
192}
193
194/*
195 * user_from_uid()
196 *	caches the name (if any) for the uid. If noname clear, we always
197 *	return the stored name (if valid or invalid match).
198 *	We use a simple hash table.
199 * Return:
200 *	Pointer to stored name (or a empty string)
201 */
202const char *
203user_from_uid(uid_t uid, int noname)
204{
205	struct passwd pwstore, *pw = NULL;
206	char pwbuf[_PW_BUF_LEN];
207	UIDC **pptr, *ptr = NULL;
208
209	if ((uidtb != NULL) || (uidtb_start() == 0)) {
210		/*
211		 * see if we have this uid cached
212		 */
213		pptr = uidtb + (uid % UID_SZ);
214		ptr = *pptr;
215
216		if ((ptr != NULL) && (ptr->valid > 0) && (ptr->uid == uid)) {
217			/*
218			 * have an entry for this uid
219			 */
220			if (!noname || (ptr->valid == VALID))
221				return ptr->name;
222			return NULL;
223		}
224
225		if (ptr == NULL)
226			*pptr = ptr = malloc(sizeof(UIDC));
227	}
228
229	getpwuid_r(uid, &pwstore, pwbuf, sizeof(pwbuf), &pw);
230	if (pw == NULL) {
231		/*
232		 * no match for this uid in the local password file
233		 * a string that is the uid in numeric format
234		 */
235		if (ptr == NULL)
236			return NULL;
237		ptr->uid = uid;
238		(void)snprintf(ptr->name, UNMLEN, "%u", uid);
239		ptr->valid = INVALID;
240		if (noname)
241			return NULL;
242	} else {
243		/*
244		 * there is an entry for this uid in the password file
245		 */
246		if (ptr == NULL)
247			return pw->pw_name;
248		ptr->uid = uid;
249		(void)strlcpy(ptr->name, pw->pw_name, sizeof(ptr->name));
250		ptr->valid = VALID;
251	}
252	return ptr->name;
253}
254
255/*
256 * group_from_gid()
257 *	caches the name (if any) for the gid. If noname clear, we always
258 *	return the stored name (if valid or invalid match).
259 *	We use a simple hash table.
260 * Return:
261 *	Pointer to stored name (or a empty string)
262 */
263const char *
264group_from_gid(gid_t gid, int noname)
265{
266	struct group grstore, *gr = NULL;
267	char grbuf[_GR_BUF_LEN];
268	GIDC **pptr, *ptr = NULL;
269
270	if ((gidtb != NULL) || (gidtb_start() == 0)) {
271		/*
272		 * see if we have this gid cached
273		 */
274		pptr = gidtb + (gid % GID_SZ);
275		ptr = *pptr;
276
277		if ((ptr != NULL) && (ptr->valid > 0) && (ptr->gid == gid)) {
278			/*
279			 * have an entry for this gid
280			 */
281			if (!noname || (ptr->valid == VALID))
282				return ptr->name;
283			return NULL;
284		}
285
286		if (ptr == NULL)
287			*pptr = ptr = malloc(sizeof(GIDC));
288	}
289
290	getgrgid_r(gid, &grstore, grbuf, sizeof(grbuf), &gr);
291	if (gr == NULL) {
292		/*
293		 * no match for this gid in the local group file, put in
294		 * a string that is the gid in numeric format
295		 */
296		if (ptr == NULL)
297			return NULL;
298		ptr->gid = gid;
299		(void)snprintf(ptr->name, GNMLEN, "%u", gid);
300		ptr->valid = INVALID;
301		if (noname)
302			return NULL;
303	} else {
304		/*
305		 * there is an entry for this group in the group file
306		 */
307		if (ptr == NULL)
308			return gr->gr_name;
309		ptr->gid = gid;
310		(void)strlcpy(ptr->name, gr->gr_name, sizeof(ptr->name));
311		ptr->valid = VALID;
312	}
313	return ptr->name;
314}
315
316/*
317 * uid_from_user()
318 *	caches the uid for a given user name. We use a simple hash table.
319 * Return:
320 *	0 if the user name is found (filling in uid), -1 otherwise
321 */
322int
323uid_from_user(const char *name, uid_t *uid)
324{
325	struct passwd pwstore, *pw = NULL;
326	char pwbuf[_PW_BUF_LEN];
327	UIDC **pptr, *ptr = NULL;
328	size_t namelen;
329
330	/*
331	 * return -1 for mangled names
332	 */
333	if (name == NULL || ((namelen = strlen(name)) == 0))
334		return -1;
335
336	if ((usrtb != NULL) || (usrtb_start() == 0)) {
337		/*
338		 * look up in hash table, if found and valid return the uid,
339		 * if found and invalid, return a -1
340		 */
341		pptr = usrtb + st_hash(name, namelen, UNM_SZ);
342		ptr = *pptr;
343
344		if ((ptr != NULL) && (ptr->valid > 0) &&
345		    strcmp(name, ptr->name) == 0) {
346			if (ptr->valid == INVALID)
347				return -1;
348			*uid = ptr->uid;
349			return 0;
350		}
351
352		if (ptr == NULL)
353			*pptr = ptr = malloc(sizeof(UIDC));
354	}
355
356	/*
357	 * no match, look it up, if no match store it as an invalid entry,
358	 * or store the matching uid
359	 */
360	getpwnam_r(name, &pwstore, pwbuf, sizeof(pwbuf), &pw);
361	if (ptr == NULL) {
362		if (pw == NULL)
363			return -1;
364		*uid = pw->pw_uid;
365		return 0;
366	}
367	(void)strlcpy(ptr->name, name, sizeof(ptr->name));
368	if (pw == NULL) {
369		ptr->valid = INVALID;
370		return -1;
371	}
372	ptr->valid = VALID;
373	*uid = ptr->uid = pw->pw_uid;
374	return 0;
375}
376
377/*
378 * gid_from_group()
379 *	caches the gid for a given group name. We use a simple hash table.
380 * Return:
381 *	0 if the group name is found (filling in gid), -1 otherwise
382 */
383int
384gid_from_group(const char *name, gid_t *gid)
385{
386	struct group grstore, *gr = NULL;
387	char grbuf[_GR_BUF_LEN];
388	GIDC **pptr, *ptr = NULL;
389	size_t namelen;
390
391	/*
392	 * return -1 for mangled names
393	 */
394	if (name == NULL || ((namelen = strlen(name)) == 0))
395		return -1;
396
397	if ((grptb != NULL) || (grptb_start() == 0)) {
398		/*
399		 * look up in hash table, if found and valid return the uid,
400		 * if found and invalid, return a -1
401		 */
402		pptr = grptb + st_hash(name, namelen, GID_SZ);
403		ptr = *pptr;
404
405		if ((ptr != NULL) && (ptr->valid > 0) &&
406		    strcmp(name, ptr->name) == 0) {
407			if (ptr->valid == INVALID)
408				return -1;
409			*gid = ptr->gid;
410			return 0;
411		}
412
413		if (ptr == NULL)
414			*pptr = ptr = malloc(sizeof(GIDC));
415	}
416
417	/*
418	 * no match, look it up, if no match store it as an invalid entry,
419	 * or store the matching gid
420	 */
421	getgrnam_r(name, &grstore, grbuf, sizeof(grbuf), &gr);
422	if (ptr == NULL) {
423		if (gr == NULL)
424			return -1;
425		*gid = gr->gr_gid;
426		return 0;
427	}
428
429	(void)strlcpy(ptr->name, name, sizeof(ptr->name));
430	if (gr == NULL) {
431		ptr->valid = INVALID;
432		return -1;
433	}
434	ptr->valid = VALID;
435	*gid = ptr->gid = gr->gr_gid;
436	return 0;
437}
438