use strict; use warnings; use Test::More; use Unix::SavedIDs; use Unix::SetUser; use Data::Dumper; if ( $< != 0 ) { plan skip_all => "Only root can change user, so please run these tests as root."; } else { plan tests => 26; } my(%exist,%nonexist); foreach my $type ('uid','gid','user','group') { $exist{$type} = []; $nonexist{$type} = []; } TRYUID: foreach my $uid (1 .. 60000) { if ( $uid == $< ) { # hey, that's me! next; } my $gid = (getpwuid($uid))[3]; if ( defined($gid) ) { if ($gid == 0) { next; } push(@{$exist{uid}},$uid); push(@{$exist{user}},(getpwuid($uid))[0]); push(@{$exist{gid}},$gid); push(@{$exist{group}},(getgrgid($gid))[0]); } else { push(@{$nonexist{uid}},$uid); } if ( !getgrgid($uid) ) { push(@{$nonexist{gid}}, $uid); } if ( $uid >= 60000 ) { die "Failed to find 5 existant and nonexistant uids and gids " ."after trying 60000 numbers. Yikes!"; } foreach my $state (\%exist,\%nonexist) { my $statelabel = 'exists'; if ( $state eq \%nonexist ) { $statelabel = 'nonexistant'; } foreach my $type ('uid','gid') { if ( !defined($state->{$type}) ) { next TRYUID; } if ( @{$state->{$type}} < 5 ) { next TRYUID; } } } last; } my @alphabet = ('A' .. 'Z'); for my $try ( 0 .. 1000 ) { my $randstr = ''; for my $letter ( 0 .. 8 ) { $randstr .= $alphabet[int(rand(26))]; } if ( !getpwnam($randstr) ) { push(@{$nonexist{user}},$randstr); } if ( !getgrnam($randstr) ) { push(@{$nonexist{group}},$randstr); } if ( @{$nonexist{group}} >= 5 && @{$nonexist{user}} >= 5 ) { last; } if ( $try >= 1000 ) { die "Failed to find five unused usernames and groupnames in 1000 " ."random strings! Wow!"; } } print "\n -- Users and Groups Used During Tests --\n\n"; print "Exising Users/Groups:\n"; map { print ' '.$_.' => '.join(", ",@{$exist{$_}})."\n" } sort(keys(%exist)); print "\nNon-Exising Users/Groups:\n"; map { print ' '.$_.' => '.join(", ",@{$nonexist{$_}})."\n" } sort(keys(%nonexist)); print "\n"; my @tests = ( [ 0, "1 Arg", $exist{user}->[0] ], [ 0, "2 Arg", $exist{user}->[0],$exist{group}->[0] ], [ 0, "3 Arg", $exist{user}->[0],$exist{group}->[0],$exist{group}->[1] ], [ 0, "3 Arg no primary group", $exist{user}->[0],undef,$exist{group}->[0] ], [ 0, "4 Arg", $exist{user}->[0],$exist{group}->[0],$exist{group}->[1],$exist{group}->[2] ], [ 0, "1 Arg Numeric", $exist{uid}->[0] ], [ 0, "2 Arg Numeric", $exist{uid}->[0],$exist{gid}->[0] ], [ 0, "3 Arg Numeric", $exist{uid}->[0],$exist{gid}->[0],$exist{gid}->[1] ], [ 0, "3 Arg Numeric no primary gid", $exist{uid}->[0],undef,$exist{gid}->[0] ], [ 0, "4 Arg Numeric", $exist{uid}->[0],$exist{gid}->[0],$exist{gid}->[1],$exist{gid}->[2] ], [ 0, "Duplicates in Sup Groups", $exist{uid}->[0],$exist{gid}->[0],$exist{group}->[1],$exist{group}->[1] ], [ 0, "Duplicates in Sup Gids", $exist{uid}->[0],$exist{gid}->[0],$exist{gid}->[1],$exist{gid}->[1] ], [ 0, "Primary Gid also in Sup Gids", $exist{uid}->[0],$exist{gid}->[0],$exist{gid}->[0] ], [ 0, "Primary Group also in Sup Groups", $exist{uid}->[0],$exist{group}->[0],$exist{group}->[0] ], [ 0, "Lots of Groups", $exist{uid}->[0],$exist{group}->[0], $exist{group}->[1], $exist{group}->[2], $exist{group}->[3] ], [ 0, "Lots of Groups with duplicates", $exist{uid}->[0],$exist{group}->[0], $exist{group}->[1], $exist{group}->[2], $exist{group}->[1] ], [ 0, "Lots of Gids", $exist{uid}->[0],$exist{gid}->[0], $exist{gid}->[1], $exist{gid}->[2], $exist{gid}->[3] ], [ 0, "Lots of Gids with duplicates", $exist{uid}->[0],$exist{gid}->[0], $exist{gid}->[1], $exist{gid}->[2], $exist{gid}->[1] ], [ 1, "Croak when user is undef" ], [ 1, "Croak when user doesn't exist", $nonexist{user}->[0]], [ 1, "Croak when uid doesn't exist", $nonexist{uid}->[0]], [ 1, "Croak when group doesn't exist", $exist{user}->[0],$nonexist{group}->[0] ], [ 1, "Croak when gid doesn't exist", $exist{user}->[0],$nonexist{gid}->[0] ], [ 1, "Croak when supplimental group doesn't exist", $exist{user}->[0],undef, $nonexist{group}->[0] ], [ 1, "Croak when supplimental gid doesn't exist", $exist{user}->[0],undef, $nonexist{gid}->[0] ], [ 1, "Croak when supplimental group specified as undef", $exist{user}->[0], undef,undef ], ); for my $test (@tests) { my $croak_good = shift(@$test); my $description = shift(@$test); pipe(my $from, my $to); my $orig_handle = select(); select($to); $| = 1; select($from); $| = 1; select($orig_handle); my $pid = fork(); if ( !defined($pid) ) { die "Failed to fork!"; } if ( $pid == 0 ) { close($from); if ( @$test ) { no warnings; print "set_user(".join(", ",@$test).")\n"; } eval{ set_user(@$test) }; if ( $@ ) { chomp($@); print $to $@; exit; } my @errs; my $uid = to_int(shift(@$test)); if ( $< != $uid ) { push(@errs,"Failed to change uid to $uid"); } if ( $> != $uid ) { push(@errs,"Failed to change euid to $uid"); } my $prim_gid = shift(@$test); if ( !defined($prim_gid) ) { $prim_gid = (getpwuid($uid))[3]; } else { $prim_gid = to_int($prim_gid,'group'); } my @sup_gids; my $gid_string = $prim_gid; foreach my $group (@$test) { $gid_string .= ' '.to_int($group,'group'); } if ( !@$test ) { $gid_string .= ' '.$prim_gid; } if ( $( !~ /^(\d+)/ ) { die "Your system returned a gid that wasn't an int: '$('"; } my $now_prim_gid = $1; if ( $now_prim_gid != $prim_gid ) { push(@errs,"Primary Group is '$now_prim_gid' not '$prim_gid'"); } if ( $) ne $gid_string ) { my $now = join(' ',sort(split(' ',$)))); my $want = join(' ',sort(uniqe(split(' ',$gid_string)))); if ( $now ne $want ) { push(@errs,"Effective and supplimental are '$)' not " ."'$gid_string'"); } } my $suid = (getresuid())[2]; if ( $suid != $uid ) { push(@errs,"Saved Uid is $suid, not $uid"); } my $sgid = (getresgid())[2]; if ( $sgid != $prim_gid ) { push(@errs,"Saved gid is $sgid, not $prim_gid"); } #print "UID = $<, EUID = $>, GIDS = $(, EGIDS = $(\n"; if ( @errs ) { print $to join(', ',@errs)."\n"; exit; } print $to ''; exit; } close($to); my $err = <$from>; if ( !defined($err) ) { $err = ''; } waitpid($pid,0); if ( $croak_good ) { ok( $err ne '' , $description) || diag("Should have croaked!"); } else { ok( $err eq '' , $description) || diag($err."\n"); } } sub to_int { my ($thing,$type) = @_; if ( $thing =~ /^\d+$/ ) { return $thing; } if ( defined($type) ) { return scalar(getgrnam($thing)); } return scalar(getpwnam($thing)); } sub uniqe { my %hash; my @out; for my $thing (@_) { if ( $hash{$thing} ) { next; } push(@out,$thing); $hash{$thing} = 1; } return @out; }