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