1/*	$NetBSD: pwcache.c,v 1.35 2024/05/12 10:58:58 rillig 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/*-
37 * Copyright (c) 2002 The NetBSD Foundation, Inc.
38 * All rights reserved.
39 *
40 * Redistribution and use in source and binary forms, with or without
41 * modification, are permitted provided that the following conditions
42 * are met:
43 * 1. Redistributions of source code must retain the above copyright
44 *    notice, this list of conditions and the following disclaimer.
45 * 2. Redistributions in binary form must reproduce the above copyright
46 *    notice, this list of conditions and the following disclaimer in the
47 *    documentation and/or other materials provided with the distribution.
48 *
49 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
50 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
51 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
52 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
53 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
54 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
55 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
56 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
57 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
58 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
59 * POSSIBILITY OF SUCH DAMAGE.
60 */
61
62#if HAVE_NBTOOL_CONFIG_H
63#include "nbtool_config.h"
64/*
65 * XXX Undefine the renames of these functions so that we don't
66 * XXX rename the versions found in the host's <pwd.h> by mistake!
67 */
68#undef group_from_gid
69#undef user_from_uid
70#endif
71
72#include <sys/cdefs.h>
73#if defined(LIBC_SCCS) && !defined(lint)
74#if 0
75static char sccsid[] = "@(#)cache.c	8.1 (Berkeley) 5/31/93";
76#else
77__RCSID("$NetBSD: pwcache.c,v 1.35 2024/05/12 10:58:58 rillig Exp $");
78#endif
79#endif /* LIBC_SCCS and not lint */
80
81#include "namespace.h"
82
83#include <sys/types.h>
84#include <sys/param.h>
85
86#include <assert.h>
87#include <grp.h>
88#include <pwd.h>
89#include <stdio.h>
90#include <stdlib.h>
91#include <string.h>
92#include <unistd.h>
93
94#if HAVE_NBTOOL_CONFIG_H
95/* XXX Now, re-apply the renaming that we undid above. */
96#define	group_from_gid	__nbcompat_group_from_gid
97#define	user_from_uid	__nbcompat_user_from_uid
98#endif
99
100#ifdef __weak_alias
101__weak_alias(user_from_uid,_user_from_uid)
102__weak_alias(group_from_gid,_group_from_gid)
103__weak_alias(pwcache_groupdb,_pwcache_groupdb)
104#endif
105
106#if !HAVE_PWCACHE_USERDB || HAVE_NBTOOL_CONFIG_H
107#include "pwcache.h"
108
109/*
110 * routines that control user, group, uid and gid caches (for the archive
111 * member print routine).
112 * IMPORTANT:
113 * these routines cache BOTH hits and misses, a major performance improvement
114 */
115
116/*
117 * function pointers to various name lookup routines.
118 * these may be changed as necessary.
119 */
120static	int		(*_pwcache_setgroupent)(int)		= setgroupent;
121static	void		(*_pwcache_endgrent)(void)		= endgrent;
122static	struct group *	(*_pwcache_getgrnam)(const char *)	= getgrnam;
123static	struct group *	(*_pwcache_getgrgid)(gid_t)		= getgrgid;
124static	int		(*_pwcache_setpassent)(int)		= setpassent;
125static	void		(*_pwcache_endpwent)(void)		= endpwent;
126static	struct passwd *	(*_pwcache_getpwnam)(const char *)	= getpwnam;
127static	struct passwd *	(*_pwcache_getpwuid)(uid_t)		= getpwuid;
128
129/*
130 * internal state
131 */
132static	int	pwopn;		/* is password file open */
133static	int	gropn;		/* is group file open */
134static	UIDC	**uidtb;	/* uid to name cache */
135static	GIDC	**gidtb;	/* gid to name cache */
136static	UIDC	**usrtb;	/* user name to uid cache */
137static	GIDC	**grptb;	/* group name to gid cache */
138
139static	int	uidtb_fail;	/* uidtb_start() failed ? */
140static	int	gidtb_fail;	/* gidtb_start() failed ? */
141static	int	usrtb_fail;	/* usrtb_start() failed ? */
142static	int	grptb_fail;	/* grptb_start() failed ? */
143
144
145static	u_int	st_hash(const char *, size_t, int);
146static	int	uidtb_start(void);
147static	int	gidtb_start(void);
148static	int	usrtb_start(void);
149static	int	grptb_start(void);
150
151
152static u_int
153st_hash(const char *name, size_t len, int tabsz)
154{
155	u_int key = 0;
156
157	_DIAGASSERT(name != NULL);
158
159	while (len--) {
160		key += *name++;
161		key = (key << 8) | (key >> 24);
162	}
163
164	return (key % tabsz);
165}
166
167/*
168 * uidtb_start
169 *	creates an empty uidtb
170 * Return:
171 *	0 if ok, -1 otherwise
172 */
173static int
174uidtb_start(void)
175{
176
177	if (uidtb != NULL)
178		return (0);
179	if (uidtb_fail)
180		return (-1);
181	if ((uidtb = (UIDC **)calloc(UID_SZ, sizeof(UIDC *))) == NULL) {
182		++uidtb_fail;
183		return (-1);
184	}
185	return (0);
186}
187
188/*
189 * gidtb_start
190 *	creates an empty gidtb
191 * Return:
192 *	0 if ok, -1 otherwise
193 */
194static int
195gidtb_start(void)
196{
197
198	if (gidtb != NULL)
199		return (0);
200	if (gidtb_fail)
201		return (-1);
202	if ((gidtb = (GIDC **)calloc(GID_SZ, sizeof(GIDC *))) == NULL) {
203		++gidtb_fail;
204		return (-1);
205	}
206	return (0);
207}
208
209/*
210 * usrtb_start
211 *	creates an empty usrtb
212 * Return:
213 *	0 if ok, -1 otherwise
214 */
215static int
216usrtb_start(void)
217{
218
219	if (usrtb != NULL)
220		return (0);
221	if (usrtb_fail)
222		return (-1);
223	if ((usrtb = (UIDC **)calloc(UNM_SZ, sizeof(UIDC *))) == NULL) {
224		++usrtb_fail;
225		return (-1);
226	}
227	return (0);
228}
229
230/*
231 * grptb_start
232 *	creates an empty grptb
233 * Return:
234 *	0 if ok, -1 otherwise
235 */
236static int
237grptb_start(void)
238{
239
240	if (grptb != NULL)
241		return (0);
242	if (grptb_fail)
243		return (-1);
244	if ((grptb = (GIDC **)calloc(GNM_SZ, sizeof(GIDC *))) == NULL) {
245		++grptb_fail;
246		return (-1);
247	}
248	return (0);
249}
250
251/*
252 * user_from_uid()
253 *	caches the name (if any) for the uid. If noname clear, we always
254 *	return the stored name (if valid or invalid match).
255 *	We use a simple hash table.
256 * Return
257 *	Pointer to stored name (or a empty string)
258 */
259const char *
260user_from_uid(uid_t uid, int noname)
261{
262	struct passwd *pw;
263	UIDC *ptr, **pptr;
264
265	if ((uidtb == NULL) && (uidtb_start() < 0))
266		return (NULL);
267
268	/*
269	 * see if we have this uid cached
270	 */
271	pptr = uidtb + (uid % UID_SZ);
272	ptr = *pptr;
273
274	if ((ptr != NULL) && (ptr->valid > 0) && (ptr->uid == uid)) {
275		/*
276		 * have an entry for this uid
277		 */
278		if (!noname || (ptr->valid == VALID))
279			return (ptr->name);
280		return (NULL);
281	}
282
283	/*
284	 * No entry for this uid, we will add it
285	 */
286	if (!pwopn) {
287		if (_pwcache_setpassent != NULL)
288			(*_pwcache_setpassent)(1);
289		++pwopn;
290	}
291
292	if (ptr == NULL)
293		*pptr = ptr = (UIDC *)malloc(sizeof(UIDC));
294
295	if ((pw = (*_pwcache_getpwuid)(uid)) == NULL) {
296		/*
297		 * no match for this uid in the local password file
298		 * a string that is the uid in numeric format
299		 */
300		if (ptr == NULL)
301			return (NULL);
302		ptr->uid = uid;
303		(void)snprintf(ptr->name, UNMLEN, "%lu", (long) uid);
304		ptr->valid = INVALID;
305		if (noname)
306			return (NULL);
307	} else {
308		/*
309		 * there is an entry for this uid in the password file
310		 */
311		if (ptr == NULL)
312			return (pw->pw_name);
313		ptr->uid = uid;
314		(void)strlcpy(ptr->name, pw->pw_name, UNMLEN);
315		ptr->valid = VALID;
316	}
317	return (ptr->name);
318}
319
320/*
321 * group_from_gid()
322 *	caches the name (if any) for the gid. If noname clear, we always
323 *	return the stored name (if valid or invalid match).
324 *	We use a simple hash table.
325 * Return
326 *	Pointer to stored name (or a empty string)
327 */
328const char *
329group_from_gid(gid_t gid, int noname)
330{
331	struct group *gr;
332	GIDC *ptr, **pptr;
333
334	if ((gidtb == NULL) && (gidtb_start() < 0))
335		return (NULL);
336
337	/*
338	 * see if we have this gid cached
339	 */
340	pptr = gidtb + (gid % GID_SZ);
341	ptr = *pptr;
342
343	if ((ptr != NULL) && (ptr->valid > 0) && (ptr->gid == gid)) {
344		/*
345		 * have an entry for this gid
346		 */
347		if (!noname || (ptr->valid == VALID))
348			return (ptr->name);
349		return (NULL);
350	}
351
352	/*
353	 * No entry for this gid, we will add it
354	 */
355	if (!gropn) {
356		if (_pwcache_setgroupent != NULL)
357			(*_pwcache_setgroupent)(1);
358		++gropn;
359	}
360
361	if (ptr == NULL)
362		*pptr = ptr = (GIDC *)malloc(sizeof(GIDC));
363
364	if ((gr = (*_pwcache_getgrgid)(gid)) == NULL) {
365		/*
366		 * no match for this gid in the local group file, put in
367		 * a string that is the gid in numeric format
368		 */
369		if (ptr == NULL)
370			return (NULL);
371		ptr->gid = gid;
372		(void)snprintf(ptr->name, GNMLEN, "%lu", (long) gid);
373		ptr->valid = INVALID;
374		if (noname)
375			return (NULL);
376	} else {
377		/*
378		 * there is an entry for this group in the group file
379		 */
380		if (ptr == NULL)
381			return (gr->gr_name);
382		ptr->gid = gid;
383		(void)strlcpy(ptr->name, gr->gr_name, GNMLEN);
384		ptr->valid = VALID;
385	}
386	return (ptr->name);
387}
388
389/*
390 * uid_from_user()
391 *	caches the uid for a given user name. We use a simple hash table.
392 * Return
393 *	the uid (if any) for a user name, or a -1 if no match can be found
394 */
395int
396uid_from_user(const char *name, uid_t *uid)
397{
398	struct passwd *pw;
399	UIDC *ptr, **pptr;
400	size_t namelen;
401
402	/*
403	 * return -1 for mangled names
404	 */
405	if (name == NULL || ((namelen = strlen(name)) == 0))
406		return (-1);
407	if ((usrtb == NULL) && (usrtb_start() < 0))
408		return (-1);
409
410	/*
411	 * look up in hash table, if found and valid return the uid,
412	 * if found and invalid, return a -1
413	 */
414	pptr = usrtb + st_hash(name, namelen, UNM_SZ);
415	ptr = *pptr;
416
417	if ((ptr != NULL) && (ptr->valid > 0) && !strcmp(name, ptr->name)) {
418		if (ptr->valid == INVALID)
419			return (-1);
420		*uid = ptr->uid;
421		return (0);
422	}
423
424	if (!pwopn) {
425		if (_pwcache_setpassent != NULL)
426			(*_pwcache_setpassent)(1);
427		++pwopn;
428	}
429
430	if (ptr == NULL)
431		*pptr = ptr = (UIDC *)malloc(sizeof(UIDC));
432
433	/*
434	 * no match, look it up, if no match store it as an invalid entry,
435	 * or store the matching uid
436	 */
437	if (ptr == NULL) {
438		if ((pw = (*_pwcache_getpwnam)(name)) == NULL)
439			return (-1);
440		*uid = pw->pw_uid;
441		return (0);
442	}
443	(void)strlcpy(ptr->name, name, UNMLEN);
444	if ((pw = (*_pwcache_getpwnam)(name)) == NULL) {
445		ptr->valid = INVALID;
446		return (-1);
447	}
448	ptr->valid = VALID;
449	*uid = ptr->uid = pw->pw_uid;
450	return (0);
451}
452
453/*
454 * gid_from_group()
455 *	caches the gid for a given group name. We use a simple hash table.
456 * Return
457 *	the gid (if any) for a group name, or a -1 if no match can be found
458 */
459int
460gid_from_group(const char *name, gid_t *gid)
461{
462	struct group *gr;
463	GIDC *ptr, **pptr;
464	size_t namelen;
465
466	/*
467	 * return -1 for mangled names
468	 */
469	if (name == NULL || ((namelen = strlen(name)) == 0))
470		return (-1);
471	if ((grptb == NULL) && (grptb_start() < 0))
472		return (-1);
473
474	/*
475	 * look up in hash table, if found and valid return the uid,
476	 * if found and invalid, return a -1
477	 */
478	pptr = grptb + st_hash(name, namelen, GID_SZ);
479	ptr = *pptr;
480
481	if ((ptr != NULL) && (ptr->valid > 0) && !strcmp(name, ptr->name)) {
482		if (ptr->valid == INVALID)
483			return (-1);
484		*gid = ptr->gid;
485		return (0);
486	}
487
488	if (!gropn) {
489		if (_pwcache_setgroupent != NULL)
490			(*_pwcache_setgroupent)(1);
491		++gropn;
492	}
493
494	if (ptr == NULL)
495		*pptr = ptr = (GIDC *)malloc(sizeof(GIDC));
496
497	/*
498	 * no match, look it up, if no match store it as an invalid entry,
499	 * or store the matching gid
500	 */
501	if (ptr == NULL) {
502		if ((gr = (*_pwcache_getgrnam)(name)) == NULL)
503			return (-1);
504		*gid = gr->gr_gid;
505		return (0);
506	}
507
508	(void)strlcpy(ptr->name, name, GNMLEN);
509	if ((gr = (*_pwcache_getgrnam)(name)) == NULL) {
510		ptr->valid = INVALID;
511		return (-1);
512	}
513	ptr->valid = VALID;
514	*gid = ptr->gid = gr->gr_gid;
515	return (0);
516}
517
518#define FLUSHTB(arr, len, fail)				\
519	do {						\
520		if (arr != NULL) {			\
521			for (i = 0; i < len; i++)	\
522				if (arr[i] != NULL)	\
523					free(arr[i]);	\
524			arr = NULL;			\
525		}					\
526		fail = 0;				\
527	} while (0)
528
529int
530pwcache_userdb(
531	int		(*a_setpassent)(int),
532	void		(*a_endpwent)(void),
533	struct passwd *	(*a_getpwnam)(const char *),
534	struct passwd *	(*a_getpwuid)(uid_t))
535{
536	int i;
537
538		/* a_setpassent and a_endpwent may be NULL */
539	if (a_getpwnam == NULL || a_getpwuid == NULL)
540		return (-1);
541
542	if (_pwcache_endpwent != NULL)
543		(*_pwcache_endpwent)();
544	FLUSHTB(uidtb, UID_SZ, uidtb_fail);
545	FLUSHTB(usrtb, UNM_SZ, usrtb_fail);
546	pwopn = 0;
547	_pwcache_setpassent = a_setpassent;
548	_pwcache_endpwent = a_endpwent;
549	_pwcache_getpwnam = a_getpwnam;
550	_pwcache_getpwuid = a_getpwuid;
551
552	return (0);
553}
554
555int
556pwcache_groupdb(
557	int		(*a_setgroupent)(int),
558	void		(*a_endgrent)(void),
559	struct group *	(*a_getgrnam)(const char *),
560	struct group *	(*a_getgrgid)(gid_t))
561{
562	int i;
563
564		/* a_setgroupent and a_endgrent may be NULL */
565	if (a_getgrnam == NULL || a_getgrgid == NULL)
566		return (-1);
567
568	if (_pwcache_endgrent != NULL)
569		(*_pwcache_endgrent)();
570	FLUSHTB(gidtb, GID_SZ, gidtb_fail);
571	FLUSHTB(grptb, GNM_SZ, grptb_fail);
572	gropn = 0;
573	_pwcache_setgroupent = a_setgroupent;
574	_pwcache_endgrent = a_endgrent;
575	_pwcache_getgrnam = a_getgrnam;
576	_pwcache_getgrgid = a_getgrgid;
577
578	return (0);
579}
580
581
582#ifdef TEST_PWCACHE
583
584struct passwd *
585test_getpwnam(const char *name)
586{
587	static struct passwd foo;
588
589	memset(&foo, 0, sizeof(foo));
590	if (strcmp(name, "toor") == 0) {
591		foo.pw_uid = 666;
592		return &foo;
593	}
594	return (getpwnam(name));
595}
596
597int
598main(int argc, char *argv[])
599{
600	uid_t	u;
601	int	r, i;
602
603	printf("pass 1 (default userdb)\n");
604	for (i = 1; i < argc; i++) {
605		printf("i: %d, pwopn %d usrtb_fail %d usrtb %p\n",
606		    i, pwopn, usrtb_fail, usrtb);
607		r = uid_from_user(argv[i], &u);
608		if (r == -1)
609			printf("  uid_from_user %s: failed\n", argv[i]);
610		else
611			printf("  uid_from_user %s: %d\n", argv[i], u);
612	}
613	printf("pass 1 finish: pwopn %d usrtb_fail %d usrtb %p\n",
614		    pwopn, usrtb_fail, usrtb);
615
616	puts("");
617	printf("pass 2 (replacement userdb)\n");
618	printf("pwcache_userdb returned %d\n",
619	    pwcache_userdb(setpassent, test_getpwnam, getpwuid));
620	printf("pwopn %d usrtb_fail %d usrtb %p\n", pwopn, usrtb_fail, usrtb);
621
622	for (i = 1; i < argc; i++) {
623		printf("i: %d, pwopn %d usrtb_fail %d usrtb %p\n",
624		    i, pwopn, usrtb_fail, usrtb);
625		u = -1;
626		r = uid_from_user(argv[i], &u);
627		if (r == -1)
628			printf("  uid_from_user %s: failed\n", argv[i]);
629		else
630			printf("  uid_from_user %s: %d\n", argv[i], u);
631	}
632	printf("pass 2 finish: pwopn %d usrtb_fail %d usrtb %p\n",
633		    pwopn, usrtb_fail, usrtb);
634
635	puts("");
636	printf("pass 3 (null pointers)\n");
637	printf("pwcache_userdb returned %d\n",
638	    pwcache_userdb(NULL, NULL, NULL));
639
640	return (0);
641}
642#endif	/* TEST_PWCACHE */
643#endif	/* !HAVE_PWCACHE_USERDB */
644