##!/bin/sh -- # wish I were -*-Perl-*- #eval 'exec perl -S $0 ${1+"$@"}' # if !$$; #!/bin/perl # Look for trojan horses... # A trojan horse looks like a regular program. # however, if you execute it, the program may set up a back door to # your account, or modify one of your files, etc. # # This script reports on the different ways someone can drop a trojan hourse # in your searchpath. # # It does not check for set UID or GID programs on your file system, # and does not check NFS permissions of directories. # It only checks for executables in your searchpath, and reports who and how # someone can create a trojan horse. # # This program also provides a measurement of how vunerable you are to a # trojan horse. # # Bruce Barnett # Copyright 1994 GE # All commercial Rights reserved # # @(#)trojan.pl 1.9 26 May 1994 # # usage: # # perl trojan.pl [options] # # where options are any combination of the following # -b - brief report. Don't show reasons or executables # -a - analyze all files. Normally when a file is world writable, # don't check for group or user writable # the -a means look at all problems, and not the first # -w - just report on world writable problems (no group or user) # -g - report on group writable problems ( sets -w, no user) # -u - report on world, group and user writable problems (Default) # -A - report all files that cause a problem with a group writable # permission, not just the first one # # for debugging purposes, and for more information, try the following options # -v - verbose # -d - debug # # Examples # trojan.pl - reports world, group and user problems # shows reasons for problem # trojan.pl -b - reports world, group and user problems # Doesn't show reasons # trojan.pl -b -a - reports world, group and user problems # Doesn't show reasons # reports on ALL world, group and user # writable problems # trojan.pl -b -a -A - reports world, group and user problems # Doesn't show reasons # reports on ALL world, group and user # writable problems # Also reports all files that cause group write access # # # trojan.pl -w - reports world writable problems and reasons # trojan.pl -g - reports world + group writable problems and reasons # you probably want to start with trojan.pl -b # and fix some of those problems first # If you don't understand why it's a problem, omit the -b option # A malicious cracker will often use your co-workers accounts # as a stepping stone to getting root (or bin, daemon, sys, etc.) # access. Therefore you have to trust that none of the people who # could drop a trojan horse in front of you have had their accounts # compromised. If you don't trust them, then don't allow their # binaries in your searchpath. # $not_a_csh_script = 0; # this is used in case someone tries # "csh trojan.pl" # command line OPTIONS $all = 0; # print out a more detailed report, (all tests) $report_all = 0; # report all files, not just the first one $do_world = 1; # print out world writable items $do_group = 1; # print out group writable items $do_user = 1; # print out user specific info $brief = 0; # a short report $verbose=0; # print more information $debug = 0; # # VARIABLES $dot = 0; # have I seen the "." directory in the path yet? $programsafterdot = 0; # how many files were found after the dot? $TotalFiles = 0; # total programs or files found in the $PATH directories $FilesAfterGroupWritable = 0; # files found after a group writable directory found $GroupWritableDirectoryFound = 0; # boolean, true if a group writable diectory found $FilesAfterWorldWritable = 0; # files found after a world writable directory found $WorldWritableDirectoryFound = 0; # boolean, true if a world writable diectory found $world_writable_programs = 0; $group_writable_programs = 0; $ProgramsInSomeDir = 0; # constants $SEARCHPATH=1; $NOSEARCHPATH=0; # PERL variables $| = 1; # write to pipes immediately $revision = "1.9"; # SCCS fills 1.9 in $program = "trojan.pl"; # SCCS fills trojan.pl in if ($program =~ /.M./) { # does it match the trojan.pl SCCS string? $program = "Trojan"; # yes, fill in the name of the program } if ($revision =~ /%/) { # is '%' part of the revision $beta = 1; # A beta version } else { $beta = 0; } printf("%s, %s, a study in trust...\n", $program, $beta ? "Beta release" : "Revision $revision"); &getswitches(); &main(); &report(); exit 0; # --- SUBROUTINES --- sub getswitches { $FIRST = $[; # parse command line arguments while ($ARGV[$FIRST] =~ /^-/) { # 0 && printf("checkion option %s\n", $ARGV[$FIRST]); # verbose $ARGV[$FIRST] =~ /^-v/ && ($verbose++,shift(@ARGV),next); # debug flag $ARGV[$FIRST] =~ /^-d/ && ($debug++,shift(@ARGV),next); # all flag $ARGV[$FIRST] =~ /^-a/ && ($all++,shift(@ARGV),next); # report_all flag $ARGV[$FIRST] =~ /^-A/ && ($report_all++,shift(@ARGV),next); # brief flag $ARGV[$FIRST] =~ /^-b/ && ($brief++,shift(@ARGV),next); # -w flag $ARGV[$FIRST] =~ /^-w/ && ($do_world++,$do_group = 0, $do_user = 0,shift(@ARGV),next); # -g flag $ARGV[$FIRST] =~ /^-g/ && ($do_world++,$do_group++, $do_user = 0,shift(@ARGV),next); # -u flag $ARGV[$FIRST] =~ /^-u/ && ($do_world++,$do_group++, $do_user++,shift(@ARGV),next); last; } } sub main { &getusers(); &getgroups(); &dotrojans(); } sub dotrojans { &checkrootdir(); @dirs = split(/:/,$ENV{'PATH'}); foreach $dir (@dirs) { $debug && $verbose && printf("%s: \n",$dir); $reason = "$dir is in your searchpath"; if ($dir eq ".") { $dot++; $dir = `pwd`; chop $dir; } if ( -l $dir) { $link = readlink($dir); $debug && printf("$dir points to $link\n"); $reason .= " AND $dir -> $link"; if ($link !~ /^\// ) { # a relative link $link = &resolve($dir,$link); $reason .= " ($link) "; } &checkupdir($link,$reason,$SEARCHPATH); while ( -l $link ) { $oldlink = $link; $link = readlink($oldlink); # if ($link !~ /^\// ) { # a relative link $newlink = &resolve($dir,$link); $reason .= " ($newlink) "; } $reason .= "$oldlink -> $link AND"; &checkupdir($link,$reason,$SEARCHPATH); } if ( -d $link ) { &checkdir($link, $reason); &checkupdir($link,$reason,$SEARCHPATH); &checkexecsindir($link, $reason); } } elsif ( -d $dir ) { &checkdir($dir, $reason); &checkupdir($dir,$reason,$SEARCHPATH); &checkexecsindir($dir, $reason); } } } sub checkdir { # check the directory itself - it was in the searchpath local($dir, $reason) = @_; # does the directory exist? if ( -l $dir ) { printf(STDERR "ERROR: I am testing $dir and it is a link.\n"); } elsif ( -d $dir ) { &testdir($dir,$reason); } else { printf(STDERR "Missing Directory in searchpath : %s\n", $dir); } } sub testdir { # check the directory itself local($dir,$reason) = @_; local($hit) = 0; # does the directory exist? ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($dir); if ($mode & 002) { $hit = 1; $WorldWritableDirectoryFound = 1; &addworld_directory("$reason AND $dir is WORLD writable", $dir); } # if group writable AND (not world writable or all) if ((!$hit || $all) && ($mode & 020)) { $hit = 1; $GroupWritableDirectoryFound = 1; &addgroup_directory($gid,"$reason AND directory $dir is group writable", $dir); } if (!$hit || $all) { &adduser($uid,"$reason AND directory $dir writable by owner"); # owner can write to directory } } sub checkexecsindir { # check each executable in the directory local($dir, $problem) = @_; local($hit); local($program); local($myproblem); $verbose && printf("check execs in dir $dir, reason: $problem\n"); opendir(D, $dir) || return 0; while ($file = readdir(D)) { $myproblem = $problem; (($file eq ".") || ($file eq "..")) && next; $TotalFiles++; # increase number of files found $GroupWritableDirectoryFound && $FilesAfterGroupWritable++; $WorldWritableDirectoryFound && $FilesAfterWorldWritable++; # this is either a file, a directory, or a symbolic link. # if a directory, then don't worry about it. $program = "$dir/$file"; # if file, only worry about it if it's executable, if ( -l $program) { # this is a link. Does it point to a file or to a directory? # the file in the searchpath is a symbolic link # if it points to a directory, then check who owns the directory # it is pointing to while ( -l $program ) { $link = readlink($program); $myproblem .= " AND $program -> $link"; if ($link !~ /^\// ) { # a relative link $link = &resolve($program,$link); $myproblem .= " ($link) "; } $debug && printf("Problem is now: %s, new program is %s\n", $myproblem, $link); $newdir = $link; $newdir =~ s,/[^/]+$,,; # remove the executable from the path, and check the directory $debug && printf("YES: The directory to check now is %s\n", $newdir); &ProgramUsesDir($newdir); &checkupdir($newdir, "$myproblem ", $NOSEARCHPATH); $program = $link; } # no longer a link, it might be a file of directory # get the stat on the final file ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($link); if (!defined($dev)) { # find where it's pointing !$brief && printf("Warning: symbolic link %s/%s pointing to missing file: %s\n", $dir,$file, $link); &checkmissingdir($link,$program); } elsif ( -d $link ) { # a symbolic link points to a directory. # this is only a problem if the directory pointing to is inside # a directory that can be modified $verbose && printf("\n$dir/$file points to directory $link\n"); $newdir = $link; $newdir =~ s,/[^/]+$,,; $verbose && printf("HEY: $link is a directory, and $newdir should be checked\n"); &checkupdir($newdir, "$dir/$file -> $link AND ",$NOSEARCHPATH); } else { # printf("$program points to file $link\n"); $hit = 0; if ($mode & 0111) { # is this file executable? ($hit = ($mode & 002)) && &addworld_file("$dir/$file -> $link AND $link is WORLD writable", "$dir/$file"); ($hit = ($mode & 020)) && ($all || !$hit) && &addgroup_file($gid,"file $dir/$file -> $link AND $link is group writable", "$dir/$file"); } ($all || !$hit) && &adduser($uid,"file $dir/$file -> $link modifiable by owner"); # owner can modify the target file, and make it executable if it isn't # also check by going up the tree of the executable $newdir = $link; $newdir =~ s,/[^/]+$,,; $debug && printf("YO: link: $link, newdir: $newdir, calling checkupdir\n"); &ProgramUsesDir($newdir); &checkupdir($newdir, "$dir/$file -> $link AND ",$NOSEARCHPATH); # did I do this twice? } # # if it is a file, check the permission of the file # } elsif ( -d "$dir/$file" ) { # Not a link, maybe a directory? # yes a directory in our search path. Does this mean anything? # I guess not. We already go up the directory path } else { # not a link or directory - a file # stat the file &ProgramUsesDir($dir); &testfile("$dir/$file", "$dir/$file executable in path"); } } close(D); } sub testfile { local($file,$reason) = @_; ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($file); $hit = 0; if ($mode & 0111) { # is this file executable? # printf("Executable $dir/$file seen\n"); # increase the number of programs seen # if the "." directory has been seen, then # this program can be trojanized $dot && $programsafterdot++; if ($mode & 002) { # world writable $hit = 1; &addworld_file("$reason AND $file is WORLD writable", "$file"); } # if group writable AND (not world writable or all) if ((!$hit || $all) && ($mode & 020)) { $hit = 1; &addgroup_file($gid,"$reason AND file $file is group writable", "$file"); } } # it doesn't matter if the file is executable or not, # the owner can make it executable ($all || !$hit) && &adduser($uid,"$reason AND file $file modifiable by owner"); } sub adduser { local($user,$dir) = @_; if (defined($user{$user})) { if ($report_all) { ($user != "0" && $user != $< ) && printf("user %s can do it because of %s\n", $user, $dir); } else { $debug && $verbose && printf("user %s can do it because of %s\n", $user, $dir); } # add it to the list $user{$user} .= "\n$dir"; $usercount{$user}++; } else { $user{$user} = $dir; $usercount{$user} = 1; $verbose && printf("user %s can do it because of %s\n", $user, $dir); } } sub addgroup_directory { local($gid,$reason,$dir) = @_; # $GroupWritableDirectoryFound = 1; if (!defined($group_writable{$dir})) { &addgroup($gid, $reason, $dir); $group_writable{$dir} = 1; } else { $group_writable{$dir}++ ; $verbose && printf("Directory '$dir' found again\n"); } } sub addgroup_file { local($gid, $reason,$file) = @_; $verbose && printf("Group Writable program, gid: %d, file: %s, reasons: %s\n", $gid, $file, $reason); $group_writable_programs++; &addgroup($gid, "File $reason", $file); } sub addgroup { local($gid,$reason) = @_; if (defined($group{$gid})) { if ($report_all) { $all && printf("group %s can do it because of %s\n", $gid, $reason); } else { $all && $verbose && printf("group %s can do it because of %s\n", $gid, $reason); } # add it to the list $group{$gid} .= "\n$reason"; $groupcount{$gid}++; } else { $group{$gid} = $reason; $groupcount{$gid} = 1; $verbose && printf("group %s can do it because of %s\n", $gid, $reason); } } sub addworld_directory { local($reason,$dir) = @_; # $WorldWritableDirectoryFound = 1; if (!defined($world_writable{$dir})) { &addworld($reason); $world_writable{$dir} = 1; } else { $world_writable{$dir}++ ; $verbose && printf("Directory '$dir' found again\n"); } } sub addworld_file { local($reason,$file) = @_; $world_writable_programs++; &addworld("File $reason"); } sub addworld { local($reason) = @_; $reason =~ s/-\>/\n\t\t->/g; $reason =~ s/AND/\n\t\tAND/g; # remember world writable directories !$brief && printf("ANYONE can do it because of %s\n", $reason); } sub checkupdir { # check the paths leading to the directory local($dir, $reason,$onpath) = @_; # $onpath is true if this directory is on the searchpath, else false if (defined($did_checkup_dir{$dir})) { $debug && printf("already checked updir %s\n", $dir); return 0; # did it } else { $did_checkup_dir{$dir} = 1; } if ($dir eq "." ) { die " I should not see a dot in $dir while in checkupdir"; } elsif ( $dir =~ /^\.\// ) { die " I should not see a ./ in $dir while in checkupdir"; } elsif ( $dir =~ /\/\.\.\// ) { die " I should not see a /../ in $dir while in checkupdir"; } elsif ( $dir =~ /^\.\.\// ) { die " I should not see a ../ in $dir while in checkupdir"; } $verbose && printf("checking up dir %s, reason: %s\n", $dir, $reason); # $dir is the file we are checking, and $reason is why (i.e. "a/b -> /c and") # $origfile = $dir; while ($dir ne "") { #remove the last path 1 && $verbose && printf("checkupdir: checking %s\n", $dir); if ( -d $dir ) { ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$dir"); $hit = 0; if ($hit = ($mode & 002)) { $onpath && ($WorldWritableDirectoryFound = 1); &addworld_directory("$reason AND $dir is WORLD writable", $dir); } if ($hit = ($mode & 020)) { $onpath && ($GroupWritableDirectoryFound = 1); ($all || !$hit) && &addgroup_directory($gid,"$reason $dir is group writable", $dir); } ($all || !$hit) && &adduser($uid,"$reason $dir is writable by owner"); # owner can write to directory } elsif ( ! -e $dir ) { !$brief && printf(STDERR "WARNING: non-existing directory used: $dir\n"); } else { !$brief && printf(STDERR "WARNING: non-directory used: $dir\n"); } $dir =~ s,/[^/]*$,,; # remove last directory from path } } sub checkrootdir { # check the paths leading to the directory ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("/"); $hit = 0; ($hit = ($mode & 002)) && &addworld_directory("'/' is WORLD writable", "/"); ($hit = ($mode & 020)) && ($all || !$hit) && &addgroup_directory($gid,"Directory '/' is group writable", "/"); ($all || !$hit) && &adduser($uid,"Directory '/' is writable by owner"); # owner can write to directory } sub checkmissingdir { # this argument is a file that is missing # check to see if each directory up the ladder # has permission problems. local($file, $where) = @_; $origfile = $file; while ($file =~ s,/[^/]*$,, && $file ne "") { #remove the last path $debug && $verbose && printf("checking %s\n", $file); if ( -d $file ) { ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("$file"); $hit = 0; ($hit = ($mode & 002)) && &addworld_directory("$where -> $origfile AND $file is WORLD writable", $file); ($hit = ($mode & 020)) && ($all || !$hit) && &addgroup_directory($gid,"$where -> $origfile AND $file is group writable", $file); ($all || !$hit) && &adduser($uid,"$where -> $origfile AND directory $file is writable by owner"); # owner can write to directory } } } sub report { # final report if ($debug || $verbose ) { printf("Options: "); $brief && printf("brief "); $all && printf("all "); $do_world && printf("do_world "); $do_group && printf("do_group "); $do_user && printf("do_user "); $debug && printf("debug "); $verbose && printf("verbose "); printf("\n"); } $WorldWritableProgramsByDirectory = 0; foreach $d (keys %world_writable) { printf("World writable directory %s makes %d files vulnerable\n", $d, $ProgramsInDir{$d}); $WorldWritableProgramsByDirectory += $ProgramsInDir{$d}; } # now for each group if ($do_group) { $GroupWritableProgramsByDirectory = 0; foreach $d (keys %group_writable) { printf("Group writable directory %s makes %d files vulnerable\n", $d, $ProgramsInDir{$d}); $GroupWritableProgramsByDirectory += $ProgramsInDir{$d}; } foreach $g (keys %group) { $members = $ingroup{$g}; $name = $gid_to_name{$g}; $files = $group{$g}; $files =~ s/\n/\n\t/g; $files =~ s/AND/AND\n\t\t/g; # truncate all files but the first if (!$brief) { printf("\nGroup %s can do it %d ways: \n\t%s\n", $name, $groupcount{$g}, $files); if ($do_user) { if (defined($members)) { printf("\tmembers of this group are:\n"); undef(%dummy); foreach $m (split(/ /,$members)) { if (!defined($dummy{$m})) { printf("\t\t%s\n", $m); $dummy{$m}=1; } } } } } } } # now look for each user if ($do_user) { $NumberOfProgramsOwnerByOtherUsers = 0; foreach $u (keys %user) { $name = $inuid{$u}; $files = $user{$u}; if (!defined($name)) { printf("UNKNOWN USER, UID = %d, ", $u); } else { if (defined($user_to_passwd{$name})) { printf("User %s, UID: %d, ", $name, $u); } elsif ($name =~ / /) { # more than one person has this UID... printf("Users %s, UID: %d, ", $name, $u); } else { printf("Users %s, UID: %d, ", $name, $u); } } if ($u == 0) { printf("owns %d file, but you should be able to trust root", $usercount{$u}); } elsif ($u == $>) { printf("owns %d file, (but you should be able to trust yourself :-)", $usercount{$u}); } else { # truncate all files but the first ($file) = split("\n", $files); printf("owns %d file%s", $usercount{$u}, ($usercount{$u} == 1) ? "" : "s"); !$brief && printf(", Example %s", $file); $NumberOfProgramsOwnerByOtherUsers +=$usercount{$u}; } printf("\n"); } } # printf("Number of executable programs: %d\n", $programs); printf(" ---- Score (lower percentages are better) ----\n"); $ProgramsInSomeDir = $TotalFiles; printf("Number of programs/files in searchpath: %d\n", $ProgramsInSomeDir); $do_user && printf("Number of programs writable by others (excluding root and self): %d (%4.2f%%)\n", $NumberOfProgramsOwnerByOtherUsers, ( $NumberOfProgramsOwnerByOtherUsers/$ProgramsInSomeDir)*100 ); if ($do_group) { printf("Number of group writable programs: %d (%4.2f%%)\n", $group_writable_programs, ($group_writable_programs/$ProgramsInSomeDir)*100 ); $debug && printf("Number of executables in group writable directories: %d (%4.2f%%)\n", $GroupWritableProgramsByDirectory, ( $GroupWritableProgramsByDirectory /$ProgramsInSomeDir)*100 ); } printf("Number of world writable programs: %d (%4.2f%%)\n", $world_writable_programs, ($world_writable_programs/$ProgramsInSomeDir)*100 ); $debug && printf("Number of executables in world writable directories: %d (%4.2f%%)\n", $WorldWritableProgramsByDirectory, ( $WorldWritableProgramsByDirectory /$ProgramsInSomeDir)*100 ); if ($dot) { printf("You have included '.' (current working directory) in your searchpath\n"); if ($programsafterdot) { printf("%d files out of %d executable files (%4.2f%%) can be intercepted by a trojan horse depending on your current directory\n", $programsafterdot, $ProgramsInSomeDir, ($programsafterdot/$ProgramsInSomeDir)*100.0); printf("You are 100%% susceptible to a misspelled program in your current directory (e.g. 'mroe')\n"); } } if ($WorldWritableDirectoryFound) { printf("%6.2f%% of your files (%d out of %d) may be intercepted because of world writable directories\n", ($FilesAfterWorldWritable/$TotalFiles)*100, $FilesAfterWorldWritable, $TotalFiles); } if ($GroupWritableDirectoryFound) { printf("%6.2f%% of your files (%d out of %d) may be intercepted because of group writable directories\n", ($FilesAfterGroupWritable/$TotalFiles)*100, $FilesAfterGroupWritable, $TotalFiles); } printf("----\n"); printf("You may also want to check for set user or set group commands, using..\n"); printf("\tfind / -type f -perm -4000 -print\n"); printf("\tfind / -type f -perm -2000 -print\n"); printf("... but this will take a while.\n"); printf("You must also trust the systems that provide you with NFS directories\n"); } sub getusers { local($login,$passwd,$uid,$gid); # learn about all of the users via the /etc/passwd file setpwent(); # # initialize the passwd scan while (@list = getpwent) { # fetch the next entry ($login,$passwd,$uid,$gid) = @list[0,1,2,3]; #grab the first 4 fields if ($debug && (($uid == 2) || ($uid == 3) || ($gid == 2) || ($gid == 3))) { printf("User %s, UID: %d, GID: %d\n", $login, $uid, $gid); } &add_to_group($gid,$login); # list of people who belong to the group &add_to_uid($uid,$login); # list of accounts who have the same UID if (length($passwd) == 13) { $user_to_passwd{$login} = $passwd; # do they have a password? } else { # printf("user %s doesn't have a password\n", $login); # printf("length of password %s is %d\n", $passwd, length($passwd)); } } endpwent(); # end the scan } sub getgroups { # learn about all of the groups via the /etc/group file local($login,$passwd,$uid,$members); setgrent(); # # initialize the group scan while (@list = getgrent()) { # fetch the next entry ($login,$passwd,$gid,$members) = @list[0,1,2,3]; #grab the first 4 fields if ($debug && (($gid == 2) || ($gid == 3))) { printf("Group %s, GID: %d\n", $login, $gid); } if (!defined($gid_to_name{$gid})) { $gid_to_name{$gid} = $login; } else { # group already defined if ($gid_to_name{$gid} ne $login) { $verbose && printf("Group ID #%d, name: %s, also called %s - ignoring new name\n", $gid, $gid_to_name{$gid}, $login); } } # each of the members should be added to the group list foreach $m (split(/ /,$members)) { 0 && $debug && printf("adding %s to group %s(%d)\n", $m, $login, $gid); &add_to_group($gid,$m); # list of people who belong to the group } if (length($passwd) == 13) { # $group_to_passwd{$login} = $passwd; # do they have a password? } else { # printf("group %s doesn't have a password\n", $login); # printf("length of password %s is %d\n", $passwd, length($passwd)); } } endgrent(); # end the scan } sub add_to_group { local ($gid,$login) = @_; # list of people who belong to the group # add user $login to group $gid if (defined($ingroup{$gid})) { $ingroup{$gid} .= " $login"; } else { $ingroup{$gid} = "$login"; } } sub add_to_uid { local($uid,$login) = @_; # list of accounts who have the same UID # create map of UID -> USERS if (defined($inuid{$uid})) { # check to see if name is in the list $found = 0; foreach $u (split(/ /,$inuid{$uid})) { ($u eq $login) && $found++; } (!$found) && $inuid{$uid} .= " $login"; } else { $inuid{$uid} = "$login"; } # check for map of user -<> UIDs. #; if more than one, error if (defined($inuser{$login})) { if ($uid != $inuser{$login}) { $inuser{$login} .= " $uid"; printf(STDERR " User %s (UID: %d) has duplicate UID's : %s\n", $login, $uid, $inuser{$login}); } else { # saw this user twice, but the UID was the same } } else { $inuser{$login} = "$uid"; } } sub resolve { # resolve symbolic/soft links local($current,$link) = @_; local($newlink,$newcurrent); # we are faces with a relative symbolic link # that is, the firct character of $link is NOT a '/' # the following table is in a spefcial format that will allow # testing of each case. This is why there are so many cases # I have a script that extracts these tests and # verifies the input and output # START TEST # Current Link Output # test variations of "/" as left #;# / ../ / #;# / ../../ / #;# / ../x/y /x/y #;# / ../../x/y /x/y #;# / . / #;# / ./x /x #;# / ./x/y /x/y #;# /./ . / #;# /./ ./x /x #;# /./ ./x/y /x/y #;# /a/b x/y /a/x/y #;# /a x /x #;# /a x/y /x/y #;# /a/b/c . /a/b #;# /a/b/c ./x /a/b/x #;# /a/b/c ../x /a/x #;# /a/b/c ./../x /a/x #;# /a/b/c ../../x /x # END TEST $newlink = ""; if ($current =~ /^\.\.\// ) { die "ERROR : left side can't start with ../"; } elsif ($current =~ /^\.\// ) { die "ERROR : left side can't start with ./"; } elsif ($current =~ /^[^\/]/ ) { die "ERROR : left side can't start with non-/"; } if ($link =~ /^\.\.\//) { # ../ #resolve relative link -> ../ # remove last two items on current $newcurrent = $current; # change /a/b/c/d to /a/b $newcurrent =~ s,[^\/]+\/[^\/]+$,,; # remove ../ from ../xxxx $newlink = $link; $newlink =~ s,^\.\.\/,,; # combine two pieces $newlink = "$newcurrent$newlink"; # there may still be a ../ in there # change x/v/../ to nothing $newlink =~ s,[^\/]+\/\.\.,,g; $debug && printf("RESOLVE: $current -> $link is now $newlink\n"); } elsif ($link eq "." ) { # #resolve relative link -> . # remove last part of path $newcurrent = $current; # change /a/b/c/d to /a/b/c $newcurrent =~ s,\/[^\/]+$,,; # /a/b/c -> /a/b $newlink = "$newcurrent"; $debug && printf("RESOLVE: $current -> $link is now $newlink\n"); } elsif ($link =~ /^\.\//) { # starts with ./ #resolve relative link -> ./usr # remove last part of path $newcurrent = $current; # change /a/b/c/d to /a/b/c $newcurrent =~ s,\/[^\/]+$,,; # remove ./ from ./xxxx $newlink = $link; $newlink =~ s,^\.\/,,; # ./xyz -> xyz # combine two pieces $newlink = "$newcurrent/$newlink"; $debug && printf("RESOLVE: $current -> $link is now $newlink\n"); } elsif ($link =~ /^[^\/]/) { # starts with aaa/ #resolve relative link -> usr/ # remove last part of path $newcurrent = $current; # change /a/b/c/d to /a/b/c $newcurrent =~ s,\/[^\/]+$,,; # /a/b/c -> /a/b $newlink = $link; # combine two pieces $newlink = "$newcurrent/$newlink"; $debug && printf("RESOLVE: $current -> $link is now $newlink\n"); } else { printf(STDERR "$current/$link becomes ?????\n"); } $oldlink = ""; while ($newlink ne $oldlink) { # repeat until no change $oldlink = $newlink; # $debug && printf("RESOLVE: looping to fix $current -> $link which is now $newlink\n"); # change /./ to / # John P. Rouillard $newlink =~ s,\/\.\/,\/,g; # change X//Y to X/Y $newlink =~ s,\/\/,\/,g; # change A/B/../X to A/X $newlink =~ s,[^/]+\/\.\.,,g; # change ^/../ to / $newlink =~ s,^\/\.\.\/,\/,g; # change X/./Y to X/Y $newlink =~ s,/\./,\/,g; } if ($newlink !~ /^\//) { die "return value from RESOLVE ($newlink) invalid, input: ($current, $link)"; } elsif ($newlink =~ /\/\.\.\//) { die "return value from RESOLVE ($newlink) invalid, input: ($current, $link)"; } elsif ($newlink =~ /\/\.\//) { die "return value from RESOLVE ($newlink) invalid, input: ($current, $link)"; } return $newlink; } # end resolve sub ProgramUsesDir { # this procedure is called once for each program. # this input is a directory local($dir) = @_; if ( ! -d $dir ) { if (! -e $dir ) { # file doesn't exist return; } else { die "Directory $dir NOT a directory, serious bug, aborting"; } } $ProgramsInSomeDir++; if (defined($ProgramsInDir{$dir})) { $ProgramsInDir{$dir}++; } else { $ProgramsInDir{$dir} = 1; } # now do the same thing with each step up the directory tree while ($dir ne "/") { $dir =~ s,\/[^\/]*$,,; # Chris.Rouch@wg.estec.esa.nl found this bug if ($dir eq "") { $dir = "/"; } if (defined($ProgramsInDir{$dir})) { $ProgramsInDir{$dir}++; } else { $ProgramsInDir{$dir} = 1; } } }