1#!/usr/bin/perl 2# 3# $OpenBSD: recover,v 1.13 2018/09/17 15:41:17 millert Exp $ 4# 5# Script to (safely) recover nvi edit sessions. 6# 7 8use warnings; 9use strict; 10use Fcntl; 11 12my $recoverdir = $ARGV[0] || "/tmp/vi.recover"; 13my $sendmail = "/usr/sbin/sendmail"; 14 15die "Sorry, $0 must be run as root\n" if $>; 16 17# Make the recovery dir if it does not already exist. 18if (!sysopen(DIR, $recoverdir, O_RDONLY|O_NOFOLLOW) || !stat(DIR)) { 19 die "Warning! $recoverdir is a symbolic link! (ignoring)\n" 20 if -l $recoverdir; 21 mkdir($recoverdir, 01777) || die "Unable to create $recoverdir: $!\n"; 22 chmod(01777, $recoverdir); 23 exit(0); 24} 25 26# 27# Sanity check the vi recovery dir 28# 29die "Warning! $recoverdir is not a directory! (ignoring)\n" 30 unless -d _; 31die "$0: can't chdir to $recoverdir: $!\n" unless chdir DIR; 32if (! -O _) { 33 warn "Warning! $recoverdir is not owned by root! (fixing)\n"; 34 chown(0, 0, "."); 35} 36if (((stat(_))[2] & 07777) != 01777) { 37 warn "Warning! $recoverdir is not mode 01777! (fixing)\n"; 38 chmod(01777, "."); 39} 40 41# Check editor backup files. 42opendir(RECDIR, ".") || die "$0: can't open $recoverdir: $!\n"; 43foreach my $file (readdir(RECDIR)) { 44 next unless $file =~ /^vi\./; 45 46 # 47 # Unmodified vi editor backup files either have the 48 # execute bit set or are zero length. Delete them. 49 # Anything that is not a normal file gets deleted too. 50 # 51 lstat($file) || die "$0: can't stat $file: $!\n"; 52 if (-x _ || ! -s _ || ! -f _) { 53 unlink($file) unless -d _; 54 } 55} 56 57# 58# It is possible to get incomplete recovery files if the editor crashes 59# at the right time. 60# 61rewinddir(RECDIR); 62foreach my $file (readdir(RECDIR)) { 63 next unless $file =~ /^recover\./; 64 65 if (!sysopen(RECFILE, $file, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) { 66 warn "$0: can't open $file: $!\n"; 67 next; 68 } 69 70 # 71 # Delete anything that is not a regular file as that is either 72 # filesystem corruption from fsck or an exploit attempt. 73 # Real vi recovery files are created with mode 0600, ignore others. 74 # 75 if (!stat(RECFILE)) { 76 warn "$0: can't stat $file: $!\n"; 77 close(RECFILE); 78 next; 79 } 80 if (((stat(_))[2] & 07777) != 0600) { 81 close(RECFILE); 82 next; 83 } 84 my $owner = (stat(_))[4]; 85 if (! -f _ || ! -s _) { 86 unlink($file) unless -d _; 87 close(RECFILE); 88 next; 89 } 90 91 # 92 # Slurp in the recover.* file and search for X-vi-recover-path 93 # (which should point to an existing vi.* file). 94 # 95 my @recfile = <RECFILE>; 96 close(RECFILE); 97 98 # 99 # Delete any recovery files that have no (or more than one) 100 # corresponding backup file. 101 # 102 my @backups = grep(m#^X-vi-recover-path:\s*\Q$recoverdir\E/+#, @recfile); 103 if (@backups != 1) { 104 unlink($file); 105 next; 106 } 107 108 # 109 # Make a copy of the backup file path. 110 # We must not modify @backups directly since it contains 111 # references to data in @recfile which we pipe to sendmail. 112 # 113 $backups[0] =~ m#^X-vi-recover-path:\s*\Q$recoverdir\E/+(.*)[\r\n]*$#; 114 my $backup = $1; 115 116 # 117 # If backup file is not rooted in the recover dir, ignore it. 118 # If backup file owner doesn't match recovery file owner, ignore it. 119 # If backup file is zero length or not a regular file, remove it. 120 # Else send mail to the user. 121 # 122 if ($backup =~ m#/# || !lstat($backup)) { 123 unlink($file); 124 } elsif ($owner != 0 && (stat(_))[4] != $owner) { 125 unlink($file); 126 } elsif (! -f _ || ! -s _) { 127 unlink($file, $backup); 128 } else { 129 open(SENDMAIL, "|$sendmail -t") || 130 die "$0: can't run $sendmail -t: $!\n"; 131 print SENDMAIL @recfile; 132 close(SENDMAIL); 133 } 134} 135closedir(RECDIR); 136close(DIR); 137 138exit(0); 139