1# 2# Routines for reading and caching user and group information. These 3# are used in multiple programs... it caches the info once, then hopefully 4# won't be used again. 5# 6# Steve Romig, May 1991. 7# 8# Provides a bunch of routines and a bunch of arrays. Routines 9# (and their usage): 10# 11# load_passwd_info($use_getent, $file_name) 12# 13# loads user information into the %uname* and %uid* arrays 14# (see below). 15# 16# If $use_getent is non-zero: 17# get the info via repeated 'getpwent' calls. This can be 18# *slow* on some hosts, especially if they are running as a 19# YP (NIS) client. 20# If $use_getent is 0: 21# if $file_name is "", then get the info from reading the 22# results of "ypcat passwd" and from /etc/passwd. Otherwise, 23# read the named file. The file should be in passwd(5) 24# format. 25# 26# load_group_info($use_gentent, $file_name) 27# 28# is similar to load_passwd_info. 29# 30# Information is stored in several convenient associative arrays: 31# 32# %uname2shell Assoc array, indexed by user name, value is 33# shell for that user name. 34# 35# %uname2dir Assoc array, indexed by user name, value is 36# home directory for that user name. 37# 38# %uname2uid Assoc array, indexed by name, value is uid for 39# that uid. 40# 41# %uname2passwd Assoc array, indexed by name, value is password 42# for that user name. 43# 44# %uid2names Assoc array, indexed by uid, value is list of 45# user names with that uid, in form "name name 46# name...". 47# 48# %gid2members Assoc array, indexed by gid, value is list of 49# group members in form "name name name..." 50# 51# %gname2gid Assoc array, indexed by group name, value is 52# matching gid. 53# 54# %gid2names Assoc array, indexed by gid, value is the 55# list of group names with that gid in form 56# "name name name...". 57# 58# You can also use routines named the same as the arrays - pass the index 59# as the arg, get back the value. If you use this, get{gr|pw}{uid|gid|nam} 60# will be used to lookup entries that aren't found in the cache. 61# 62# To be done: 63# probably ought to add routines to deal with full names. 64# maybe there ought to be some anal-retentive checking of password 65# and group entries. 66# probably ought to cache get{pw|gr}{nam|uid|gid} lookups also. 67# probably ought to avoid overwriting existing entries (eg, duplicate 68# names in password file would collide in the tables that are 69# indexed by name). 70# 71# Disclaimer: 72# If you use YP and you use netgroup entries such as 73# +@servers:::::: 74# +:*:::::/usr/local/utils/messages 75# then loading the password file in with &load_passwd_info(0) will get 76# you mostly correct YP stuff *except* that it won't do the password and 77# shell substitutions as you'd expect. You might want to use 78# &load_passwd_info(1) instead to use getpwent calls to do the lookups, 79# which would be more correct. 80# 81 82package main; 83 84$PASSWD = '/etc/passwd' unless defined $PASSWD; 85 86require 'pathconf.pl'; 87 88%uname2shell = (); 89%uname2dir = (); 90%uname2uid = (); 91%uname2passwd = (); 92%uid2names = (); 93%gid2members = (); 94%gname2gid = (); 95%gid2names = (); 96 97$DOMAINNAME = "/bin/domainname" unless defined $DOMAINNAME; 98$YPCAT = "/bin/ypcat" unless defined $YPCAT; 99 100$yptmp = "./yptmp.$$"; 101 102$passwd_loaded = 0; # flags to use to avoid reloading everything 103$group_loaded = 0; # unnecessarily... 104 105# 106# We provide routines for getting values from the data structures as well. 107# These are named after the data structures they cache their data in. Note 108# that they will all generate password and group file lookups via getpw* 109# and getgr* if they can't find info in the cache, so they will work 110# "right" even if load_passwd_info and load_group_info aren't called to 111# preload the caches. 112# 113# I should point out, however, that if you don't call load_*_info to preload 114# the cache, uid2names, gid2names and gid2members *will not* be complete, since 115# you must read the entire password and group files to get a complete picture. 116# This might be acceptable in some cases, so you can skip the load_*_info 117# calls if you know what you are doing... 118# 119sub uname2shell { 120 local($key) = @_; 121 122 if (! defined($uname2shell{$key})) { 123 &add_pw_info(getpwnam($key)); 124 } 125 126 return($uname2shell{$key}); 127} 128 129sub uname2dir { 130 local($key) = @_; 131 local(@pw_info); 132 133 if (! defined($uname2dir{$key})) { 134 &add_pw_info(getpwnam($key)); 135 } 136 137 return($uname2dir{$key}); 138} 139 140sub uname2uid { 141 local($key) = @_; 142 local(@pw_info); 143 144 if (! defined($uname2uid{$key})) { 145 &add_pw_info(getpwnam($key)); 146 } 147 148 return($uname2uid{$key}); 149} 150 151sub uname2passwd { 152 local($key) = @_; 153 local(@pw_info); 154 155 if (! defined($uname2passwd{$key})) { 156 &add_pw_info(getpwnam($key)); 157 } 158 159 return($uname2passwd{$key}); 160} 161 162sub uid2names { 163 local($key) = @_; 164 local(@pw_info); 165 166 if (! defined($uid2names{$key})) { 167 &add_pw_info(getpwuid($key)); 168 } 169 170 return($uid2names{$key}); 171} 172 173sub gid2members { 174 local($key) = @_; 175 local(@gr_info); 176 177 if (! defined($gid2members{$key})) { 178 &add_gr_info(getgrgid($key)); 179 } 180 181 return($gid2members{$key}); 182} 183 184sub gname2gid { 185 local($key) = @_; 186 local(@gr_info); 187 188 if (! defined($gname2gid{$key})) { 189 &add_gr_info(getgrnam($key)); 190 } 191 192 return($gname2gid{$key}); 193} 194 195sub gid2names { 196 local($key) = @_; 197 local(@gr_info); 198 199 if (! defined($gid2names{$key})) { 200 &add_gr_info(getgrgid($key)); 201 } 202 203 return($gid2names{$key}); 204} 205 206# 207# Update user information for the user named $name. We cache the password, 208# uid, login group, home directory and shell. 209# 210 211sub add_pw_info { 212 local($name, $passwd, $uid, $gid) = @_; 213 local($dir, $shell); 214 215# 216# Ugh! argh...yech...sigh. If we use getpwent, we get back 9 elts, 217# if we parse /etc/passwd directly we get 7. Pick off the last 2 and 218# assume that they are the $directory and $shell. 219# 220 $num = ( $#_ >= 7 ? 8 : 6 ); 221 $dir = $_[$num - 1]; 222 $shell = $_[$num] || '/bin/sh'; 223 224 225 if ($name ne "") { 226 $uname2shell{$name} = $shell; 227 $uname2dir{$name} = $dir; 228 $uname2uid{$name} = $uid; 229 $uname2passwd{$name} = $passwd; 230 231 if ($gid ne "") { 232 # fixme: should probably check for duplicates...sigh 233 234 if (defined($gid2members{$gid})) { 235 $gid2members{$gid} .= " $name"; 236 } else { 237 $gid2members{$gid} = $name; 238 } 239 } 240 241 if ($uid ne "") { 242 if (defined($uid2names{$uid})) { 243 $uid2names{$uid} .= " $name"; 244 } else { 245 $uid2names{$uid} = $name; 246 } 247 } 248 } 249} 250 251# 252# Update group information for the group named $name. We cache the gid 253# and the list of group members. 254# 255 256sub add_gr_info { 257 local($name, $passwd, $gid, $members) = @_; 258 259 if ($name ne "") { 260 $gname2gid{$name} = $gid; 261 262 if ($gid ne "") { 263 if (defined($gid2names{$gid})) { 264 $gid2names{$gid} .= " $name"; 265 } else { 266 $gid2names{$gid} = $name; 267 } 268 269 # fixme: should probably check for duplicates 270 271 $members = join(' ', split(/[, \t]+/, $members)); 272 273 if (defined($gid2members{$gid})) { 274 $gid2members{$gid} .= " " . $members; 275 } else { 276 $gid2members{$gid} = $members; 277 } 278 } 279 } 280} 281 282# 283# We need to suck in the entire group and password files so that we can 284# make the %uid2names, %gid2members and %gid2names lists complete. Otherwise, 285# we would just read the entries as needed with getpw* and cache the results. 286# Sigh. 287# 288# There are several ways that we might find the info. If $use_getent is 1, 289# then we just use getpwent and getgrent calls to read the info in. 290# 291# That isn't real efficient if you are using YP (especially on a YP client), so 292# if $use_getent is 0, we can use ypcat to get a copy of the passwd and 293# group maps in a fairly efficient manner. If we do this we have to also read 294# the local /etc/{passwd,group} files to complete our information. If we aren't 295# using YP, we just read the local pasword and group files. 296# 297sub load_passwd_info { 298 local($use_getent, $file_name) = @_; 299 local(@pw_info); 300 301 if ($passwd_loaded) { 302 return; 303 } 304 305 $passwd_loaded = 1; 306 307 if ($'GET_PASSWD) { 308 open(GFILE, "$'GET_PASSWD|") || die "can't $'GET_PASSWD"; 309 while (<GFILE>) { 310 chop; 311 &add_pw_info(split(/:/)); 312 } 313 close(GFILE); 314 } 315 else { 316 317 if ($use_getent) { 318 # 319 # Use getpwent to get the info from the system, and add_pw_info to 320 # cache it. 321 # 322 while (@pw_info = getpwent) { 323 &add_pw_info(@pw_info); 324 } 325 326 endpwent; 327 328 return; 329 } elsif ($file_name eq "") { 330 chop($has_yp = `$DOMAINNAME`); 331 if ($has_yp) { 332 # 333 # If we have YP (NIS), then use ypcat to get the stuff from the 334 # map.@ 335 # 336 system("$YPCAT passwd > $yptmp 2> /dev/null"); 337 if (-s $yptmp) { 338 open(FILE, "$YPCAT passwd|") || 339 die "can't 'ypcat passwd'"; 340 while (<FILE>) { 341 chop; 342 &add_pw_info(split(/:/)); 343 } 344 } 345 close(FILE); 346 } 347 348 # 349 # We have to read /etc/passwd no matter what... 350 # 351 $file_name = "/etc/passwd"; 352 } 353 354 open(FILE, $file_name) || 355 die "can't open $file_name"; 356 357 while (<FILE>) { 358 chop; 359 360 if ($_ !~ /^\+/) { 361 &add_pw_info(split(/:/)); 362 } 363 364 # fixme: if the name matches +@name, then this is a wierd 365 # netgroup thing, and we aren't dealing with it right. might want 366 # to warn the poor user...suggest that he use the use_getent 367 # method instead. 368 } 369 } 370 371 close(FILE); 372} 373 374sub load_group_info { 375 local($use_getent, $file_name) = @_; 376 local(@gr_info); 377 378 if ($group_loaded) { 379 return; 380 } 381 382 $group_loaded = 1; 383 384 if ($use_getent) { 385 # 386 # Use getgrent to get the info from the system, and add_gr_info to 387 # cache it. 388 # 389 while ((@gr_info = getgrent()) != 0) { 390 &add_gr_info(@gr_info); 391 } 392 393 endgrent(); 394 395 return(); 396 } elsif ($file_name eq "") { 397 chop($has_yp = `$DOMAINNAME`); 398 if ($has_yp) { 399 # 400 # If we have YP (NIS), then use ypcat to get the stuff from the 401 # map. 402 # 403 system("$YPCAT passwd > $yptmp 2> /dev/null"); 404 if (-s $yptmp) { 405 open(FILE, "$YPCAT group|") || 406 die "can't 'ypcat group'"; 407 while (<FILE>) { 408 chop; 409 &add_gr_info(split(/:/)); 410 } 411 close(FILE); 412 } 413 } 414 415 # 416 # We have to read /etc/group no matter what... 417 # 418 $file_name = "/etc/group"; 419 } 420 421 open(FILE, $file_name) || 422 die "can't open $file_name"; 423 424 while (<FILE>) { 425 chop; 426 if ($_ !~ /^\+/) { 427 &add_gr_info(split(/:/)); 428 } 429 430 # fixme: if the name matches +@name, then this is a wierd 431 # netgroup thing, and we aren't dealing with it right. might want 432 # to warn the poor user...suggest that he use the use_getent 433 # method instead. 434 } 435 436 close(FILE); 437} 438 439# Load the password stuff -- Do NOT take this out! 440&'load_passwd_info(0,$PASSWD); 441 442unlink $yptmp; 443 4441; 445