Oops... the version I posted earlier was not the right one.
Here's the real current version:
% cat /usr/local/bin/lazurus-expworm #!/usr/local/bin/perl5 # -*-Fundamental-*-
# $Id: //depot/tools/main/lazurus/lazurus-expworm#4 $
# Richard Geiger Network Appliance June 10 1999 # # (Resurrected :-) form the the original "lazurus" # of late 1997... #
sub dirname { local($dir) = @_;
$dir =~ s%^$%.%; $dir = "$dir/"; if ($dir =~ m%^/[^/]*//*$%) { return "/"; } if ($dir =~ m%^.*[^/]//*[^/][^/]*//*$%) { $dir =~ s%^(.*[^/])//*[^/][^/]*//*$%$1%; { return $dir; } } return "."; }
select STDERR; $| = 1; select STDOUT; $| = 1;
use Carp; $| = 1;
($Myname = $0) =~ s%^.*/%%; $Mydir = &dirname($0); $Hsave = `/bin/pwd`; chomp $Hsave; chdir $Mydir || die; $Mydir = `/bin/pwd`; chomp $Mydir; chdir $Hsave || die; $Mypath = "$Mydir/$Myname";
$Usage = <<LIT; $Myname: usage:
$Myname [-snap <snapshot>] [-mode find|fix|signoff] [-repint <n>] [-tracelev <n>]
-snap specified the snapshot to compare to
-mode specifies whether to build the list to stdout, or recover based on the list on stdin
-repint set the progress report interval, (per # of files checked).
-tracelev sets the directory coverage trace message depth
LIT
sub usage { print STDERR $Usage; exit 1; }
sub help { print STDERR <<LIT; $Usage
$Myname is a helper for recovering files from a snapshot. This version deals with files deleted by the "WormExplore.zip" virus, so by default, it uses the snapshot ".snapshot/virus-snap-one" as the basis for finding files that may have been removed by the virus.
It works on files in the tree rooted at the current directory where it is run. E.g., to repair your home directory, run it while cd'ed to your home directory.
The idea is to run it once in "find" mode, to build a list of files in the snapshot that are nononzero length in the snapshot, but zero length (with a mod time after the first know incidence of the virus) in the current filesystem. You can examine this list, and edit it however you like, and then feed it back to $Myname in "-mode fix" mode.
The -repint and -tracelev options allow you to adjust the verbosity with which $Myname will display information progress messages (to the standard error stream).
In find mode, the list of potentially lost file paths is written to the standard output.
In fix mode, $Myname starts a "cpio -p" process, set up so as to restore files based on the pathnames it reads from the standard input. Files in the "live" tree newer than those in the snapshot will not be overwritten. Files will be recovered with modification times as per those in the snapshot. Restored files will be created with the ownership and group of the user running $Myname, unless it is executed by "root", in which case the file will be created with the owner and group of the original file in the snapshot.
In fix mode, lines beginning with a " " or "$Myname: " will be ignored; thus you can run
cd ~ { for example, to repair any damage in your home directory } $Myname > FILES { edit FILES, removing lines or commenting them out with a leading space } $Myname -mode fix < FILES
LIT exit 1; }
$First = "."; $Go = 1;
sub traverse { local($dir, $lev, $onfile, $ondir, $onsymlink) = @_;
local($dirent); local($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks);
local($dirhandle) = "dh$lev";
opendir($dirhandle, $dir);
while (($dirent = readdir($dirhandle))) { if ($dirent eq "." || $dirent eq "..") { next; } ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = lstat("$dir/$dirent"); typsw: { -f _ && do { if ($Go && defined(&$onfile)) { &$onfile("$dir", "$dirent", $lev); } last typsw; } ; -d _ && do { if (defined(&$ondir)) { &$ondir("$dir", "$dirent", $lev); } if ($lev == 0 && $dirent =~ /$First/) { $Go = 1; } if ($Go) { do traverse("$dir/$dirent", $lev+1, $onfile, $ondir, $onsymlink) if -d _; } last typsw; } ; -l "$dir/$dirent" && do { if ($Go && defined(&$onsymlink)) { &$onsymlink("$dir", "$dirent", $lev); } last typsw; } ; } } closedir($dirhandle); }
$Nfound = 0; $Nchecked = 0;
sub dir { my($dir, $file, $lev) = @_;
my($path) = "$dir/$file"; $path =~ s/^.///; my($rpath) = "$Here/$path";
if ($lev < $Tracelev) { printf STDERR "$Myname: checking in $path\n"; } }
sub find { my($dir, $file, $lev) = @_;
my($path) = "$dir/$file"; $path =~ s/^.///; my($rpath) = "$Here/$path";
if (! -z $path && -z $rpath) { @s = stat(_); if ($#s < 0) { die "stat retrieve on "$path" failed?: $!"; } if ($s[9] >= 929041200) # 6/10/99 12noon PDT { print STDOUT "$path\n"; $Nfound++; } }
$Nchecked++; if (($Nchecked % $Repint) == 0) { &report; } }
sub ts { my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d/%02d/%02d_%02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec); }
sub report { printf STDERR "$Myname: %s checked $Nchecked files; found $Nfound candidates.\n", &ts; }
sub fix { $ret = 0;
chdir "$Snapshot/$Snap" || die "Can't chdir $Snapshot/$Snap: $!";
# fire us up our ole pal cpio... # $cmd = "| $Mypath -mode unlink | /bin/cpio -pdmv $Here"; if (! open(CPIO, $cmd)) { die "couldn't start "$cmd": $!"; }
print STDERR "$Myname: > $cmd\n";
while (<>) { if (! /^($Myname: | )/) { print CPIO $_; } } close CPIO; $sts = $?; if ($sts) { my $sig = $sts & 0x0f; $sts = $sts >> 8; print STDERR "$Myname: *** "$cmd" exited with signal $sig status $sts.\n"; $ret = $sts; }
chdir $Here || dir; open(LAZ, ">>.lazurus") || die "Can't open ">>.lazurus": $!"; printf LAZ "%s $Username fix exit $ret\n", &ts; close LAZ;
exit $ret; }
sub signoff { open(LAZ, ">>.lazurus") || die "Can't open ">>.lazurus": $!"; printf LAZ "%s $Username signoff\n", &ts; close LAZ; exit 0; }
# option switch variables get defaults here...
(@pwent) = getpwuid($<); if ($#pwent < 7) { print STDERR "$Myname: can't get your passwd file entry.\n"; exit 1; } $Username = $pwent[0];
$Snapshot = ".snapshot"; $Snap = "virus-snap-one"; $Mode = "find"; $Repint = 1000; $Tracelev = 0;
while ($#ARGV >= 0) { if ($ARGV[0] eq "-boolopt") { $Boolopt = 1; shift; next; } elsif ($ARGV[0] eq "-snap") { shift; if ($ARGV[0] < 0) { &usage; } $Snap = $ARGV[0]; shift; next; } elsif ($ARGV[0] =~ /^[-]{0,1}signoff/) { shift; $Mode = "signoff"; next; } elsif ($ARGV[0] eq "-mode") { shift; if ($ARGV[0] < 0) { &usage; } $Mode = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-repint") { shift; if ($ARGV[0] < 0) { &usage; } $Repint = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-tracelev") { shift; if ($ARGV[0] < 0) { &usage; } $Tracelev = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-shot") { shift; if ($ARGV[0] < 0) { &usage; } $Snapshot = $ARGV[0]; shift; next; } elsif ($ARGV[0] eq "-help") { &help; } elsif ($ARGV[0] =~ /^-/) { &usage; } if ($Args ne "") { $Args .= " "; } push(@Args, $ARGV[0]); shift; }
if ($Mode !~ /^(find|fix|signoff|unlink)$/) { die "bad mode"; } # TBD
if ($Mode eq "unlink") { while (<>) { chomp; unlink $_; print $_."\n"; } exit 0; }
$Here = `/bin/pwd`; chop $Here;
print STDERR "$Myname: starting in $Mode mode in "$Here"...\n";
if ($Mode eq "fix") { &fix; }
if ($Mode eq "signoff") { &signoff; }
open(LAZ, ">>.lazurus") || die "Can't open ">>.lazurus": $!"; printf LAZ "%s $Username\n", &ts; close LAZ;
chdir "$Snapshot/$Snap" || die "Can't chdir $Snapshot/$Snap: $!";
&traverse(".", 0, $Mode, "dir", undef);
&report; print STDERR "$Myname: done.\n";