summaryrefslogtreecommitdiff
path: root/t/op/pwent.t
blob: 9653c6baec5e3afea9037ab123f527d7a4bd9976 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#!./perl

BEGIN {
    chdir 't' if -d 't';
    @INC = '../lib';
    eval {my @n = getpwuid 0; setpwent()};
    if ($@ && $@ =~ /(The \w+ function is unimplemented)/) {
	print "1..0 # Skip: $1\n";
	exit 0;
    }
    eval { require Config; import Config; };
    my $reason;
    if ($Config{'i_pwd'} ne 'define') {
	$reason = '$Config{i_pwd} undefined';
    }
    elsif (not -f "/etc/passwd" ) { # Play safe.
	$reason = 'no /etc/passwd file';
    }

    if (not defined $where) {	# Try NIS.
	foreach my $ypcat (qw(/usr/bin/ypcat /bin/ypcat /etc/ypcat)) {
	    if (-x $ypcat &&
		open(PW, "$ypcat passwd 2>/dev/null |") &&
		defined(<PW>)) {
		$where = "NIS passwd";
		undef $reason;
		last;
	    }
	}
    }

    if (not defined $where) {	# Try NetInfo.
	foreach my $nidump (qw(/usr/bin/nidump)) {
	    if (-x $nidump &&
		open(PW, "$nidump passwd . 2>/dev/null |") &&
		defined(<PW>)) {
		$where = "NetInfo passwd";
		undef $reason;
		last;
	    }
	}
    }

    if (not defined $where &&		# Try dscl
	$Config{useperlio} eq 'define') {	# need perlio

	# Map dscl items to passwd fields, and provide support for
	# mucking with the dscl output if we need to (and we do).
	my %want = do {
	    my $inx = 0;
	    map {$_ => {inx => $inx++, mung => sub {$_[0]}}}
		qw{RecordName Password UniqueID PrimaryGroupID
		RealName NFSHomeDirectory UserShell};
	};

	# The RecordName for a /User record is the username. In some
	# cases there are synonyms (e.g. _www and www), in which case we
	# get a blank-delimited list. We prefer the first entry in the
	# list because getpwnam() does.
	$want{RecordName}{mung} = sub {(split '\s+', $_[0], 2)[0]};

	# The UniqueID and PrimaryGroupID for a /User record are the
	# user ID and the primary group ID respectively. In cases where
	# the high bit is set, 'dscl' returns a negative number, whereas
	# getpwnam() returns its twos complement. This mungs the dscl
	# output to agree with what getpwnam() produces. Interestingly
	# enough, getpwuid(-2) returns the right record ('nobody'), even
	# though it returns the uid as 4294967294. If you track uid_t
	# on an i386, you find it is an unsigned int, which makes the
	# unsigned version the right one; but both /etc/passwd and
	# /etc/master.passwd contain negative numbers.
	$want{UniqueID}{mung} = $want{PrimaryGroupID}{mung} = sub {
	    unpack 'L', pack 'l', $_[0]};

	foreach my $dscl (qw(/usr/bin/dscl)) {
	    -x $dscl or next;
	    open (my $fh, '-|', join (' ', $dscl, qw{. -readall /Users},
		    keys %want, '2>/dev/null')) or next;
	    my $data;
	    my @rec;
	    while (<$fh>) {
		chomp;
		if ($_ eq '-') {
		    @rec and $data .= join (':', @rec) . "\n";
		    @rec = ();
		    next;
		}
		my ($name, $value) = split ':\s+', $_, 2;
		unless (defined $value) {
		    s/:$//;
		    $name = $_;
		    $value = <$fh>;
		    chomp $value;
		    $value =~ s/^\s+//;
		}
		if (defined (my $info = $want{$name})) {
		    $rec[$info->{inx}] = $info->{mung}->($value);
		}
	    }
	    @rec and $data .= join (':', @rec) . "\n";
	    if (open (PW, '<', \$data)) {
		$where = "dscl . -readall /Users";
		undef $reason;
		last;
	    }
	}
    }

    if (not defined $where) {	# Try local.
	my $PW = "/etc/passwd";
	if (-f $PW && open(PW, $PW) && defined(<PW>)) {
	    $where = $PW;
	    undef $reason;
	}
    }

    if (not defined $where) {      # Try NIS+
     foreach my $niscat (qw(/bin/niscat)) {
         if (-x $niscat &&
           open(PW, "$niscat passwd.org_dir 2>/dev/null |") &&
           defined(<PW>)) {
           $where = "NIS+ $niscat passwd.org_dir";
           undef $reason;
           last;
         }
     }
    }

    if ($reason) {	# Give up.
	print "1..0 # Skip: $reason\n";
	exit 0;
    }
}

# By now the PW filehandle should be open and full of juicy password entries.

print "1..2\n";

# Go through at most this many users.
# (note that the first entry has been read away by now)
my $max = 25;

my $n = 0;
my $tst = 1;
my %perfect;
my %seen;

print "# where $where\n";

setpwent();

while (<PW>) {
    chomp;
    # LIMIT -1 so that users with empty shells don't fall off
    my @s = split /:/, $_, -1;
    my ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s);
    (my $v) = $Config{osvers} =~ /^(\d+)/;
    if ($^O eq 'darwin' && $v < 9) {
       ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s) = @s[0,1,2,3,7,8,9];
    } else {
       ($name_s, $passwd_s, $uid_s, $gid_s, $gcos_s, $home_s, $shell_s) = @s;
    }
    next if /^\+/; # ignore NIS includes
    if (@s) {
	push @{ $seen{$name_s} }, $.;
    } else {
	warn "# Your $where line $. is empty.\n";
	next;
    }
    if ($n == $max) {
	local $/;
	my $junk = <PW>;
	last;
    }
    # In principle we could whine if @s != 7 but do we know enough
    # of passwd file formats everywhere?
    if (@s == 7 || ($^O eq 'darwin' && @s == 10)) {
	@n = getpwuid($uid_s);
	# 'nobody' et al.
	next unless @n;
	my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$home,$shell) = @n;
	# Protect against one-to-many and many-to-one mappings.
	if ($name_s ne $name) {
	    @n = getpwnam($name_s);
	    ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$home,$shell) = @n;
	    next if $name_s ne $name;
	}
	$perfect{$name_s}++
	    if $name    eq $name_s    and
               $uid     eq $uid_s     and
# Do not compare passwords: think shadow passwords.
               $gid     eq $gid_s     and
               $gcos    eq $gcos_s    and
               $home    eq $home_s    and
               $shell   eq $shell_s;
    }
    $n++;
}

endpwent();

print "# max = $max, n = $n, perfect = ", scalar keys %perfect, "\n";

if (keys %perfect == 0 && $n) {
    $max++;
    print <<EOEX;
#
# The failure of op/pwent test is not necessarily serious.
# It may fail due to local password administration conventions.
# If you are for example using both NIS and local passwords,
# test failure is possible.  Any distributed password scheme
# can cause such failures.
#
# What the pwent test is doing is that it compares the $max first
# entries of $where
# with the results of getpwuid() and getpwnam() call.  If it finds no
# matches at all, it suspects something is wrong.
# 
EOEX
    print "not ";
    $not = 1;
} else {
    $not = 0;
}
print "ok ", $tst++;
print "\t# (not necessarily serious: run t/op/pwent.t by itself)" if $not;
print "\n";

# Test both the scalar and list contexts.

my @pw1;

setpwent();
for (1..$max) {
    my $pw = scalar getpwent();
    last unless defined $pw;
    push @pw1, $pw;
}
endpwent();

my @pw2;

setpwent();
for (1..$max) {
    my ($pw) = (getpwent());
    last unless defined $pw;
    push @pw2, $pw;
}
endpwent();

print "not " unless "@pw1" eq "@pw2";
print "ok ", $tst++, "\n";

close(PW);