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