xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/mypwd.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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