1 /* $NetBSD: mypwd.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* mypwd 3
6 /* SUMMARY
7 /* caching getpwnam_r()/getpwuid_r()
8 /* SYNOPSIS
9 /* #include <mypwd.h>
10 /*
11 /* int mypwuid_err(uid, pwd)
12 /* uid_t uid;
13 /* struct mypasswd **pwd;
14 /*
15 /* int mypwnam_err(name, pwd)
16 /* const char *name;
17 /* struct mypasswd **pwd;
18 /*
19 /* void mypwfree(pwd)
20 /* struct mypasswd *pwd;
21 /* BACKWARDS COMPATIBILITY
22 /* struct mypasswd *mypwuid(uid)
23 /* uid_t uid;
24 /*
25 /* struct mypasswd *mypwnam(name)
26 /* const char *name;
27 /* DESCRIPTION
28 /* This module maintains a reference-counted cache of password
29 /* database lookup results. The idea is to avoid making repeated
30 /* getpw*() calls for the same information.
31 /*
32 /* mypwnam_err() and mypwuid_err() are wrappers that cache a
33 /* private copy of results from the getpwnam_r() and getpwuid_r()
34 /* library routines (on legacy systems: from getpwnam() and
35 /* getpwuid(). Note: cache updates are not protected by mutex.
36 /*
37 /* Results are shared between calls with the same \fIname\fR
38 /* or \fIuid\fR argument, so changing results is verboten.
39 /*
40 /* mypwnam() and mypwuid() are binary-compatibility wrappers
41 /* for legacy applications.
42 /*
43 /* mypwfree() cleans up the result of mypwnam*() and mypwuid*().
44 /* BUGS
45 /* This module is security sensitive and complex at the same
46 /* time, which is bad.
47 /* DIAGNOSTICS
48 /* mypwnam_err() and mypwuid_err() return a non-zero system
49 /* error code when the lookup could not be performed. They
50 /* return zero, plus a null struct mypasswd pointer, when the
51 /* requested information was not found.
52 /*
53 /* Fatal error: out of memory.
54 /* LICENSE
55 /* .ad
56 /* .fi
57 /* The Secure Mailer license must be distributed with this software.
58 /* AUTHOR(S)
59 /* Wietse Venema
60 /* IBM T.J. Watson Research
61 /* P.O. Box 704
62 /* Yorktown Heights, NY 10598, USA
63 /*--*/
64
65 /* System library. */
66
67 #include <sys_defs.h>
68 #include <unistd.h>
69 #include <string.h>
70 #ifdef USE_PATHS_H
71 #include <paths.h>
72 #endif
73 #include <errno.h>
74
75 /* Utility library. */
76
77 #include <mymalloc.h>
78 #include <htable.h>
79 #include <binhash.h>
80 #include <msg.h>
81
82 /* Global library. */
83
84 #include "mypwd.h"
85
86 /*
87 * Workaround: Solaris >= 2.5.1 provides two getpwnam_r() and getpwuid_r()
88 * implementations. The default variant is compatible with historical
89 * Solaris implementations. The non-default variant is POSIX-compliant.
90 *
91 * To get the POSIX-compliant variant, we include the file <pwd.h> after
92 * defining _POSIX_PTHREAD_SEMANTICS. We do this after all other includes,
93 * so that we won't unexpectedly affect any other APIs.
94 *
95 * This happens to work because nothing above this includes <pwd.h>, and
96 * because of the specific way that Solaris redefines getpwnam_r() and
97 * getpwuid_r() for POSIX compliance. We know the latter from peeking under
98 * the hood. What we do is only marginally better than directly invoking
99 * __posix_getpwnam_r() and __posix_getpwuid_r().
100 */
101 #ifdef GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS
102 #define _POSIX_PTHREAD_SEMANTICS
103 #endif
104 #include <pwd.h>
105
106 /*
107 * The private cache. One for lookups by name, one for lookups by uid, and
108 * one for the last looked up result. There is a minor problem: multiple
109 * cache entries may have the same uid value, but the cache that is indexed
110 * by uid can store only one entry per uid value.
111 */
112 static HTABLE *mypwcache_name = 0;
113 static BINHASH *mypwcache_uid = 0;
114 static struct mypasswd *last_pwd;
115
116 /*
117 * XXX Solaris promises that we can determine the getpw*_r() buffer size by
118 * calling sysconf(_SC_GETPW_R_SIZE_MAX). Many systems promise that they
119 * will return an ERANGE error when the buffer is too small. However, not
120 * all systems make such promises. Therefore, we settle for the dumbest
121 * option: a large buffer. This is acceptable because the buffer is used
122 * only for short-term storage.
123 */
124 #ifdef HAVE_POSIX_GETPW_R
125 #define GETPW_R_BUFSIZ 1024
126 #endif
127 #define MYPWD_ERROR_DELAY (30)
128
129 /* mypwenter - enter password info into cache */
130
mypwenter(const struct passwd * pwd)131 static struct mypasswd *mypwenter(const struct passwd * pwd)
132 {
133 struct mypasswd *mypwd;
134
135 /*
136 * Initialize on the fly.
137 */
138 if (mypwcache_name == 0) {
139 mypwcache_name = htable_create(0);
140 mypwcache_uid = binhash_create(0);
141 }
142 mypwd = (struct mypasswd *) mymalloc(sizeof(*mypwd));
143 mypwd->refcount = 0;
144 mypwd->pw_name = mystrdup(pwd->pw_name);
145 mypwd->pw_passwd = mystrdup(pwd->pw_passwd);
146 mypwd->pw_uid = pwd->pw_uid;
147 mypwd->pw_gid = pwd->pw_gid;
148 mypwd->pw_gecos = mystrdup(pwd->pw_gecos);
149 mypwd->pw_dir = mystrdup(pwd->pw_dir);
150 mypwd->pw_shell = mystrdup(*pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL);
151
152 /*
153 * Avoid mypwcache_uid memory leak when multiple names have the same UID.
154 * This makes the lookup result dependent on program history. But, it was
155 * already history-dependent before we added this extra check.
156 */
157 htable_enter(mypwcache_name, mypwd->pw_name, (void *) mypwd);
158 if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid,
159 sizeof(mypwd->pw_uid)) == 0)
160 binhash_enter(mypwcache_uid, (void *) &mypwd->pw_uid,
161 sizeof(mypwd->pw_uid), (void *) mypwd);
162 return (mypwd);
163 }
164
165 /* mypwuid - caching getpwuid() */
166
mypwuid(uid_t uid)167 struct mypasswd *mypwuid(uid_t uid)
168 {
169 struct mypasswd *mypwd;
170
171 while ((errno = mypwuid_err(uid, &mypwd)) != 0) {
172 msg_warn("getpwuid_r: %m");
173 sleep(MYPWD_ERROR_DELAY);
174 }
175 return (mypwd);
176 }
177
178 /* mypwuid_err - caching getpwuid_r(), minus thread safety */
179
mypwuid_err(uid_t uid,struct mypasswd ** result)180 int mypwuid_err(uid_t uid, struct mypasswd ** result)
181 {
182 struct passwd *pwd;
183 struct mypasswd *mypwd;
184
185 /*
186 * See if this is the same user as last time.
187 */
188 if (last_pwd != 0) {
189 if (last_pwd->pw_uid != uid) {
190 mypwfree(last_pwd);
191 last_pwd = 0;
192 } else {
193 *result = mypwd = last_pwd;
194 mypwd->refcount++;
195 return (0);
196 }
197 }
198
199 /*
200 * Find the info in the cache or in the password database.
201 */
202 if ((mypwd = (struct mypasswd *)
203 binhash_find(mypwcache_uid, (void *) &uid, sizeof(uid))) == 0) {
204 #ifdef HAVE_POSIX_GETPW_R
205 char pwstore[GETPW_R_BUFSIZ];
206 struct passwd pwbuf;
207 int err;
208
209 err = getpwuid_r(uid, &pwbuf, pwstore, sizeof(pwstore), &pwd);
210 if (err != 0)
211 return (err);
212 if (pwd == 0) {
213 *result = 0;
214 return (0);
215 }
216 #else
217 if ((pwd = getpwuid(uid)) == 0) {
218 *result = 0;
219 return (0);
220 }
221 #endif
222 mypwd = mypwenter(pwd);
223 }
224 *result = last_pwd = mypwd;
225 mypwd->refcount += 2;
226 return (0);
227 }
228
229 /* mypwnam - caching getpwnam() */
230
mypwnam(const char * name)231 struct mypasswd *mypwnam(const char *name)
232 {
233 struct mypasswd *mypwd;
234
235 while ((errno = mypwnam_err(name, &mypwd)) != 0) {
236 msg_warn("getpwnam_r: %m");
237 sleep(MYPWD_ERROR_DELAY);
238 }
239 return (mypwd);
240 }
241
242 /* mypwnam_err - caching getpwnam_r(), minus thread safety */
243
mypwnam_err(const char * name,struct mypasswd ** result)244 int mypwnam_err(const char *name, struct mypasswd ** result)
245 {
246 struct passwd *pwd;
247 struct mypasswd *mypwd;
248
249 /*
250 * See if this is the same user as last time.
251 */
252 if (last_pwd != 0) {
253 if (strcmp(last_pwd->pw_name, name) != 0) {
254 mypwfree(last_pwd);
255 last_pwd = 0;
256 } else {
257 *result = mypwd = last_pwd;
258 mypwd->refcount++;
259 return (0);
260 }
261 }
262
263 /*
264 * Find the info in the cache or in the password database.
265 */
266 if ((mypwd = (struct mypasswd *) htable_find(mypwcache_name, name)) == 0) {
267 #ifdef HAVE_POSIX_GETPW_R
268 char pwstore[GETPW_R_BUFSIZ];
269 struct passwd pwbuf;
270 int err;
271
272 err = getpwnam_r(name, &pwbuf, pwstore, sizeof(pwstore), &pwd);
273 if (err != 0)
274 return (err);
275 if (pwd == 0) {
276 *result = 0;
277 return (0);
278 }
279 #else
280 if ((pwd = getpwnam(name)) == 0) {
281 *result = 0;
282 return (0);
283 }
284 #endif
285 mypwd = mypwenter(pwd);
286 }
287 *result = last_pwd = mypwd;
288 mypwd->refcount += 2;
289 return (0);
290 }
291
292 /* mypwfree - destroy password info */
293
mypwfree(struct mypasswd * mypwd)294 void mypwfree(struct mypasswd * mypwd)
295 {
296 if (mypwd->refcount < 1)
297 msg_panic("mypwfree: refcount %d", mypwd->refcount);
298
299 /*
300 * See mypwenter() for the reason behind the binhash_locate() test.
301 */
302 if (--mypwd->refcount == 0) {
303 htable_delete(mypwcache_name, mypwd->pw_name, (void (*) (void *)) 0);
304 if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid,
305 sizeof(mypwd->pw_uid)))
306 binhash_delete(mypwcache_uid, (void *) &mypwd->pw_uid,
307 sizeof(mypwd->pw_uid), (void (*) (void *)) 0);
308 myfree(mypwd->pw_name);
309 myfree(mypwd->pw_passwd);
310 myfree(mypwd->pw_gecos);
311 myfree(mypwd->pw_dir);
312 myfree(mypwd->pw_shell);
313 myfree((void *) mypwd);
314 }
315 }
316
317 #ifdef TEST
318
319 /*
320 * Test program. Look up a couple users and/or uid values and see if the
321 * results will be properly free()d.
322 */
323 #include <stdlib.h>
324 #include <ctype.h>
325 #include <vstream.h>
326 #include <msg_vstream.h>
327
main(int argc,char ** argv)328 int main(int argc, char **argv)
329 {
330 struct mypasswd **mypwd;
331 int i;
332
333 msg_vstream_init(argv[0], VSTREAM_ERR);
334 if (argc == 1)
335 msg_fatal("usage: %s name or uid ...", argv[0]);
336
337 mypwd = (struct mypasswd **) mymalloc((argc + 2) * sizeof(*mypwd));
338
339 /*
340 * Do a sequence of lookups.
341 */
342 for (i = 1; i < argc; i++) {
343 if (ISDIGIT(argv[i][0]))
344 mypwd[i] = mypwuid(atoi(argv[i]));
345 else
346 mypwd[i] = mypwnam(argv[i]);
347 if (mypwd[i] == 0)
348 msg_fatal("%s: not found", argv[i]);
349 msg_info("lookup %s %s/%d refcount=%d name_cache=%d uid_cache=%d",
350 argv[i], mypwd[i]->pw_name, mypwd[i]->pw_uid,
351 mypwd[i]->refcount, mypwcache_name->used, mypwcache_uid->used);
352 }
353 mypwd[argc] = last_pwd;
354
355 /*
356 * The following should free all entries.
357 */
358 for (i = 1; i < argc + 1; i++) {
359 msg_info("free %s/%d refcount=%d name_cache=%d uid_cache=%d",
360 mypwd[i]->pw_name, mypwd[i]->pw_uid, mypwd[i]->refcount,
361 mypwcache_name->used, mypwcache_uid->used);
362 mypwfree(mypwd[i]);
363 }
364 msg_info("name_cache=%d uid_cache=%d",
365 mypwcache_name->used, mypwcache_uid->used);
366
367 myfree((void *) mypwd);
368 return (0);
369 }
370
371 #endif
372