/*++ /* NAME /* mypwd 3 /* SUMMARY /* caching getpwnam_r()/getpwuid_r() /* SYNOPSIS /* #include /* /* int mypwuid_err(uid, pwd) /* uid_t uid; /* struct mypasswd **pwd; /* /* int mypwnam_err(name, pwd) /* const char *name; /* struct mypasswd **pwd; /* /* void mypwfree(pwd) /* struct mypasswd *pwd; /* BACKWARDS COMPATIBILITY /* struct mypasswd *mypwuid(uid) /* uid_t uid; /* /* struct mypasswd *mypwnam(name) /* const char *name; /* DESCRIPTION /* This module maintains a reference-counted cache of password /* database lookup results. The idea is to avoid making repeated /* getpw*() calls for the same information. /* /* mypwnam_err() and mypwuid_err() are wrappers that cache a /* private copy of results from the getpwnam_r() and getpwuid_r() /* library routines (on legacy systems: from getpwnam() and /* getpwuid(). Note: cache updates are not protected by mutex. /* /* Results are shared between calls with the same \fIname\fR /* or \fIuid\fR argument, so changing results is verboten. /* /* mypwnam() and mypwuid() are binary-compatibility wrappers /* for legacy applications. /* /* mypwfree() cleans up the result of mypwnam*() and mypwuid*(). /* BUGS /* This module is security sensitive and complex at the same /* time, which is bad. /* DIAGNOSTICS /* mypwnam_err() and mypwuid_err() return a non-zero system /* error code when the lookup could not be performed. They /* return zero, plus a null struct mypasswd pointer, when the /* requested information was not found. /* /* Fatal error: out of memory. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include #include #include #ifdef USE_PATHS_H #include #endif #include /* Utility library. */ #include #include #include #include /* Global library. */ #include "mypwd.h" /* * Workaround: Solaris >= 2.5.1 provides two getpwnam_r() and getpwuid_r() * implementations. The default variant is compatible with historical * Solaris implementations. The non-default variant is POSIX-compliant. * * To get the POSIX-compliant variant, we include the file after * defining _POSIX_PTHREAD_SEMANTICS. We do this after all other includes, * so that we won't unexpectedly affect any other APIs. * * This happens to work because nothing above this includes , and * because of the specific way that Solaris redefines getpwnam_r() and * getpwuid_r() for POSIX compliance. We know the latter from peeking under * the hood. What we do is only marginally better than directly invoking * __posix_getpwnam_r() and __posix_getpwuid_r(). */ #ifdef GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS #define _POSIX_PTHREAD_SEMANTICS #endif #include /* * The private cache. One for lookups by name, one for lookups by uid, and * one for the last looked up result. There is a minor problem: multiple * cache entries may have the same uid value, but the cache that is indexed * by uid can store only one entry per uid value. */ static HTABLE *mypwcache_name = 0; static BINHASH *mypwcache_uid = 0; static struct mypasswd *last_pwd; /* * XXX Solaris promises that we can determine the getpw*_r() buffer size by * calling sysconf(_SC_GETPW_R_SIZE_MAX). Many systems promise that they * will return an ERANGE error when the buffer is too small. However, not * all systems make such promises. Therefore, we settle for the dumbest * option: a large buffer. This is acceptable because the buffer is used * only for short-term storage. */ #ifdef HAVE_POSIX_GETPW_R #define GETPW_R_BUFSIZ 1024 #endif #define MYPWD_ERROR_DELAY (30) /* mypwenter - enter password info into cache */ static struct mypasswd *mypwenter(const struct passwd * pwd) { struct mypasswd *mypwd; /* * Initialize on the fly. */ if (mypwcache_name == 0) { mypwcache_name = htable_create(0); mypwcache_uid = binhash_create(0); } mypwd = (struct mypasswd *) mymalloc(sizeof(*mypwd)); mypwd->refcount = 0; mypwd->pw_name = mystrdup(pwd->pw_name); mypwd->pw_passwd = mystrdup(pwd->pw_passwd); mypwd->pw_uid = pwd->pw_uid; mypwd->pw_gid = pwd->pw_gid; mypwd->pw_gecos = mystrdup(pwd->pw_gecos); mypwd->pw_dir = mystrdup(pwd->pw_dir); mypwd->pw_shell = mystrdup(*pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL); /* * Avoid mypwcache_uid memory leak when multiple names have the same UID. * This makes the lookup result dependent on program history. But, it was * already history-dependent before we added this extra check. */ htable_enter(mypwcache_name, mypwd->pw_name, (void *) mypwd); if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid, sizeof(mypwd->pw_uid)) == 0) binhash_enter(mypwcache_uid, (void *) &mypwd->pw_uid, sizeof(mypwd->pw_uid), (void *) mypwd); return (mypwd); } /* mypwuid - caching getpwuid() */ struct mypasswd *mypwuid(uid_t uid) { struct mypasswd *mypwd; while ((errno = mypwuid_err(uid, &mypwd)) != 0) { msg_warn("getpwuid_r: %m"); sleep(MYPWD_ERROR_DELAY); } return (mypwd); } /* mypwuid_err - caching getpwuid_r(), minus thread safety */ int mypwuid_err(uid_t uid, struct mypasswd ** result) { struct passwd *pwd; struct mypasswd *mypwd; /* * See if this is the same user as last time. */ if (last_pwd != 0) { if (last_pwd->pw_uid != uid) { mypwfree(last_pwd); last_pwd = 0; } else { *result = mypwd = last_pwd; mypwd->refcount++; return (0); } } /* * Find the info in the cache or in the password database. */ if ((mypwd = (struct mypasswd *) binhash_find(mypwcache_uid, (void *) &uid, sizeof(uid))) == 0) { #ifdef HAVE_POSIX_GETPW_R char pwstore[GETPW_R_BUFSIZ]; struct passwd pwbuf; int err; err = getpwuid_r(uid, &pwbuf, pwstore, sizeof(pwstore), &pwd); if (err != 0) return (err); if (pwd == 0) { *result = 0; return (0); } #else if ((pwd = getpwuid(uid)) == 0) { *result = 0; return (0); } #endif mypwd = mypwenter(pwd); } *result = last_pwd = mypwd; mypwd->refcount += 2; return (0); } /* mypwnam - caching getpwnam() */ struct mypasswd *mypwnam(const char *name) { struct mypasswd *mypwd; while ((errno = mypwnam_err(name, &mypwd)) != 0) { msg_warn("getpwnam_r: %m"); sleep(MYPWD_ERROR_DELAY); } return (mypwd); } /* mypwnam_err - caching getpwnam_r(), minus thread safety */ int mypwnam_err(const char *name, struct mypasswd ** result) { struct passwd *pwd; struct mypasswd *mypwd; /* * See if this is the same user as last time. */ if (last_pwd != 0) { if (strcmp(last_pwd->pw_name, name) != 0) { mypwfree(last_pwd); last_pwd = 0; } else { *result = mypwd = last_pwd; mypwd->refcount++; return (0); } } /* * Find the info in the cache or in the password database. */ if ((mypwd = (struct mypasswd *) htable_find(mypwcache_name, name)) == 0) { #ifdef HAVE_POSIX_GETPW_R char pwstore[GETPW_R_BUFSIZ]; struct passwd pwbuf; int err; err = getpwnam_r(name, &pwbuf, pwstore, sizeof(pwstore), &pwd); if (err != 0) return (err); if (pwd == 0) { *result = 0; return (0); } #else if ((pwd = getpwnam(name)) == 0) { *result = 0; return (0); } #endif mypwd = mypwenter(pwd); } *result = last_pwd = mypwd; mypwd->refcount += 2; return (0); } /* mypwfree - destroy password info */ void mypwfree(struct mypasswd * mypwd) { if (mypwd->refcount < 1) msg_panic("mypwfree: refcount %d", mypwd->refcount); /* * See mypwenter() for the reason behind the binhash_locate() test. */ if (--mypwd->refcount == 0) { htable_delete(mypwcache_name, mypwd->pw_name, (void (*) (void *)) 0); if (binhash_locate(mypwcache_uid, (void *) &mypwd->pw_uid, sizeof(mypwd->pw_uid))) binhash_delete(mypwcache_uid, (void *) &mypwd->pw_uid, sizeof(mypwd->pw_uid), (void (*) (void *)) 0); myfree(mypwd->pw_name); myfree(mypwd->pw_passwd); myfree(mypwd->pw_gecos); myfree(mypwd->pw_dir); myfree(mypwd->pw_shell); myfree((void *) mypwd); } } #ifdef TEST /* * Test program. Look up a couple users and/or uid values and see if the * results will be properly free()d. */ #include #include #include #include int main(int argc, char **argv) { struct mypasswd **mypwd; int i; msg_vstream_init(argv[0], VSTREAM_ERR); if (argc == 1) msg_fatal("usage: %s name or uid ...", argv[0]); mypwd = (struct mypasswd **) mymalloc((argc + 2) * sizeof(*mypwd)); /* * Do a sequence of lookups. */ for (i = 1; i < argc; i++) { if (ISDIGIT(argv[i][0])) mypwd[i] = mypwuid(atoi(argv[i])); else mypwd[i] = mypwnam(argv[i]); if (mypwd[i] == 0) msg_fatal("%s: not found", argv[i]); msg_info("lookup %s %s/%d refcount=%d name_cache=%d uid_cache=%d", argv[i], mypwd[i]->pw_name, mypwd[i]->pw_uid, mypwd[i]->refcount, mypwcache_name->used, mypwcache_uid->used); } mypwd[argc] = last_pwd; /* * The following should free all entries. */ for (i = 1; i < argc + 1; i++) { msg_info("free %s/%d refcount=%d name_cache=%d uid_cache=%d", mypwd[i]->pw_name, mypwd[i]->pw_uid, mypwd[i]->refcount, mypwcache_name->used, mypwcache_uid->used); mypwfree(mypwd[i]); } msg_info("name_cache=%d uid_cache=%d", mypwcache_name->used, mypwcache_uid->used); myfree((void *) mypwd); return (0); } #endif