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
65 typedef 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
71 typedef 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
86 static UIDC **uidtb; /* uid to name cache */
87 static GIDC **gidtb; /* gid to name cache */
88 static UIDC **usrtb; /* user name to uid cache */
89 static GIDC **grptb; /* group name to gid cache */
90
91 static u_int
st_hash(const char * name,size_t len,int tabsz)92 st_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 */
112 static int
uidtb_start(void)113 uidtb_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 */
134 static int
gidtb_start(void)135 gidtb_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 */
156 static int
usrtb_start(void)157 usrtb_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 */
178 static int
grptb_start(void)179 grptb_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 */
202 const char *
user_from_uid(uid_t uid,int noname)203 user_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 */
263 const char *
group_from_gid(gid_t gid,int noname)264 group_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 */
322 int
uid_from_user(const char * name,uid_t * uid)323 uid_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 */
383 int
gid_from_group(const char * name,gid_t * gid)384 gid_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