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