Home | History | Annotate | Line # | Download | only in global
      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 
    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 
    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 
    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 
    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 
    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 
    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 
    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