xref: /openbsd-src/gnu/usr.bin/perl/t/op/pwent.t (revision 3d61058aa5c692477b6d18acfbbdb653a9930ff9)
1#!./perl
2
3BEGIN {
4    chdir 't' if -d 't';
5    require './test.pl';
6    set_up_inc('../lib');
7}
8
9use strict;
10use warnings;
11
12eval {my @n = getpwuid 0; setpwent()};
13skip_all($1) if $@ && $@ =~ /(The \w+ function is unimplemented)/;
14
15eval { require Config; };
16
17sub try_prog {
18    my ($where, $args, @pathnames) = @_;
19    foreach my $prog (@pathnames) {
20	next unless -x $prog;
21	next unless open PW, '-|', "$prog $args 2>/dev/null";
22	next unless defined <PW>;
23	return $where;
24    }
25    return;
26}
27
28# Try NIS.
29my $where = try_prog('NIS passwd', 'passwd',
30		     qw(/usr/bin/ypcat /bin/ypcat /etc/ypcat));
31
32# Try NetInfo.
33$where //= try_prog('NetInfo passwd', 'passwd .', '/usr/bin/nidump');
34
35# Try NIS+.
36$where //= try_prog('NIS+', 'passwd.org_dir', '/bin/niscat');
37
38# Try dscl
39DSCL: {
40my @dscl = qw(/usr/bin/dscl);
41if (!defined $where && grep { -x } @dscl) {
42    # Map dscl items to passwd fields, and provide support for
43    # mucking with the dscl output if we need to (and we do).
44    my %want = do {
45	my $inx = 0;
46	map {$_ => {inx => $inx++, mung => sub {$_[0]}}}
47	    qw{RecordName Password UniqueID PrimaryGroupID
48	       RealName NFSHomeDirectory UserShell};
49    };
50
51    # The RecordName for a /User record is the username. In some
52    # cases there are synonyms (e.g. _www and www), in which case we
53    # get a blank-delimited list. We prefer the first entry in the
54    # list because getpwnam() does.
55    $want{RecordName}{mung} = sub {(split '\s+', $_[0], 2)[0]};
56
57    # The UniqueID and PrimaryGroupID for a /User record are the
58    # user ID and the primary group ID respectively. In cases where
59    # the high bit is set, 'dscl' returns a negative number, whereas
60    # getpwnam() returns its twos complement. This mungs the dscl
61    # output to agree with what getpwnam() produces. Interestingly
62    # enough, getpwuid(-2) returns the right record ('nobody'), even
63    # though it returns the uid as 4294967294. If you track uid_t
64    # on an i386, you find it is an unsigned int, which makes the
65    # unsigned version the right one; but both /etc/passwd and
66    # /etc/master.passwd contain negative numbers.
67    $want{UniqueID}{mung} = $want{PrimaryGroupID}{mung} = sub {
68	unpack 'L', pack 'l', $_[0]};
69
70    foreach my $dscl (@dscl) {
71	next unless -x $dscl;
72	next unless open my $fh, '-|', "$dscl . -readall /Users @{[keys %want]} 2>/dev/null";
73	my @lines;
74	my @rec;
75	while (<$fh>) {
76	    chomp;
77	    if ($_ eq '-') {
78		if (@rec) {
79		    # Some records do not have all items. In particular,
80		    # the macports user has no real name. Here it's an undef,
81		    # in the password file it becomes an empty string.
82		    no warnings 'uninitialized';
83		    push @lines, join (':', @rec) . "\n";
84		    @rec = ();
85		}
86		next;
87	    }
88	    my ($name, $value) = split ':\s+', $_, 2;
89	    unless (defined $value) {
90		s/:$//;
91		$name = $_;
92		$value = <$fh>;
93		chomp $value;
94		$value =~ s/^\s+//;
95	    }
96	    if (defined (my $info = $want{$name})) {
97		$rec[$info->{inx}] = $info->{mung}->($value);
98	    }
99	}
100	if (@rec) {
101        # see above
102        no warnings 'uninitialized';
103	    push @lines, join (':', @rec) . "\n";
104	}
105	my $data = join '', @lines;
106	if (open PW, '<', \$data) {
107	    $where = "dscl . -readall /Users";
108	    last;
109	}
110    }
111}
112} # DSCL:
113
114if (not defined $where) {
115    # Try local.
116    my $no_i_pwd = !$Config::Config{i_pwd} && '$Config{i_pwd} undefined';
117
118    my $PW = "/etc/passwd";
119    if (!-f $PW) {
120	skip_all($no_i_pwd) if $no_i_pwd;
121	skip_all("no $PW file");
122    } elsif (open PW, '<', $PW) {
123	if(defined <PW>) {
124	    $where = $PW;
125	} else {
126	    skip_all($no_i_pwd) if $no_i_pwd;
127	    die "\$Config{i_pwd} is defined, $PW exists but has no entries, all other approaches failed, giving up";
128	}
129    } else {
130	die "Can't open $PW: $!";
131    }
132}
133
134# By now the PW filehandle should be open and full of juicy password entries.
135
136plan(tests => 2);
137
138# Go through at most this many users.
139# (note that the first entry has been read away by now)
140my $max = 25;
141
142my $n = 0;
143my %perfect;
144my %seen;
145
146print "# where $where\n";
147
148setpwent();
149
150while (<PW>) {
151    chomp;
152    # LIMIT -1 so that users with empty shells don't fall off
153    my @s = split /:/, $_, -1;
154    my ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s);
155    (my $v) = $Config::Config{osvers} =~ /^(\d+)/;
156    if ($^O eq 'darwin' && $v < 9) {
157       ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s) = @s[0,1,2,3,7,8,9];
158    } else {
159       ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s) = @s;
160    }
161    next if /^\+/; # ignore NIS includes
162    if (@s) {
163	push @{ $seen{$name_s} }, $.;
164    } else {
165	warn "# Your $where line $. is empty.\n";
166	next;
167    }
168    if ($n == $max) {
169	local $/;
170	my $junk = <PW>;
171	last;
172    }
173    # In principle we could whine if @s != 7 but do we know enough
174    # of passwd file formats everywhere?
175    if (@s == 7 || ($^O eq 'darwin' && @s == 10)) {
176	my @n = getpwuid($uid_s);
177	# 'nobody' et al.
178	next unless @n;
179	my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$home,$shell) = @n;
180	# Protect against one-to-many and many-to-one mappings.
181	if ($name_s ne $name) {
182	    @n = getpwnam($name_s);
183	    ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$home,$shell) = @n;
184	    next if $name_s ne $name;
185	}
186	$perfect{$name_s}++
187	    if $name    eq $name_s    and
188               $uid     eq $uid_s     and
189# Do not compare passwords: think shadow passwords.
190               $gid     eq $gid_s     and
191               $gcos    eq $gcos_s    and
192               $home    eq $home_s    and
193               $shell   eq $shell_s;
194    }
195    $n++;
196}
197
198endpwent();
199
200print "# max = $max, n = $n, perfect = ", scalar keys %perfect, "\n";
201
202SKIP: {
203    skip("Found no password entries", 1) unless $n;
204
205    if (keys %perfect == 0) {
206	$max++;
207	print <<EOEX;
208#
209# The failure of op/pwent test is not necessarily serious.
210# It may fail due to local password administration conventions.
211# If you are for example using both NIS and local passwords,
212# test failure is possible.  Any distributed password scheme
213# can cause such failures.
214#
215# What the pwent test is doing is that it compares the $max first
216# entries of $where
217# with the results of getpwuid() and getpwnam() call.  If it finds no
218# matches at all, it suspects something is wrong.
219# 
220EOEX
221    }
222
223    cmp_ok(keys %perfect, '>', 0, "pwent test satisfactory")
224	or note("(not necessarily serious: run t/op/pwent.t by itself)");
225}
226
227# Test both the scalar and list contexts.
228
229my @pw1;
230
231setpwent();
232for (1..$max) {
233    my $pw = scalar getpwent();
234    last unless defined $pw;
235    push @pw1, $pw;
236}
237endpwent();
238
239my @pw2;
240
241setpwent();
242for (1..$max) {
243    my ($pw) = (getpwent());
244    last unless defined $pw;
245    push @pw2, $pw;
246}
247endpwent();
248
249is("@pw1", "@pw2",
250    "getpwent() produced identical results in list and scalar contexts");
251
252close(PW);
253