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