summaryrefslogtreecommitdiff
path: root/usr.bin/vi/build/recover
blob: 42f1d7c4ef3ebbebe6aa527b9c59d568d2356602 (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
#!/usr/bin/perl
#
# $OpenBSD: recover,v 1.13 2018/09/17 15:41:17 millert Exp $
#
# Script to (safely) recover nvi edit sessions.
#

use warnings;
use strict;
use Fcntl;

my $recoverdir = $ARGV[0] || "/tmp/vi.recover";
my $sendmail = "/usr/sbin/sendmail";

die "Sorry, $0 must be run as root\n" if $>;

# Make the recovery dir if it does not already exist.
if (!sysopen(DIR, $recoverdir, O_RDONLY|O_NOFOLLOW) || !stat(DIR)) {
	die "Warning! $recoverdir is a symbolic link! (ignoring)\n"
	    if -l $recoverdir;
	mkdir($recoverdir, 01777) || die "Unable to create $recoverdir: $!\n";
	chmod(01777, $recoverdir);
	exit(0);
}

#
# Sanity check the vi recovery dir
#
die "Warning! $recoverdir is not a directory! (ignoring)\n"
    unless -d _;
die "$0: can't chdir to $recoverdir: $!\n" unless chdir DIR;
if (! -O _) {
	warn "Warning! $recoverdir is not owned by root! (fixing)\n";
	chown(0, 0, ".");
}
if (((stat(_))[2] & 07777) != 01777) {
	warn "Warning! $recoverdir is not mode 01777! (fixing)\n";
	chmod(01777, ".");
}

# Check editor backup files.
opendir(RECDIR, ".") || die "$0: can't open $recoverdir: $!\n";
foreach my $file (readdir(RECDIR)) {
	next unless $file =~ /^vi\./;

	#
	# Unmodified vi editor backup files either have the
	# execute bit set or are zero length.  Delete them.
	# Anything that is not a normal file gets deleted too.
	#
	lstat($file) || die "$0: can't stat $file: $!\n";
	if (-x _ || ! -s _ || ! -f _) {
		unlink($file) unless -d _;
	}
}

#
# It is possible to get incomplete recovery files if the editor crashes
# at the right time.
#
rewinddir(RECDIR);
foreach my $file (readdir(RECDIR)) {
	next unless $file =~ /^recover\./;

	if (!sysopen(RECFILE, $file, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) {
	    warn "$0: can't open $file: $!\n";
	    next;
	}

	#
	# Delete anything that is not a regular file as that is either
	# filesystem corruption from fsck or an exploit attempt.
	# Real vi recovery files are created with mode 0600, ignore others.
	#
	if (!stat(RECFILE)) {
		warn "$0: can't stat $file: $!\n";
		close(RECFILE);
		next;
	}
	if (((stat(_))[2] & 07777) != 0600) {
		close(RECFILE);
		next;
	}
	my $owner = (stat(_))[4];
	if (! -f _ || ! -s _) {
		unlink($file) unless -d _;
		close(RECFILE);
		next;
	}

	#
	# Slurp in the recover.* file and search for X-vi-recover-path
	# (which should point to an existing vi.* file).
	#
	my @recfile = <RECFILE>;
	close(RECFILE);

	#
	# Delete any recovery files that have no (or more than one)
	# corresponding backup file.
	#
	my @backups = grep(m#^X-vi-recover-path:\s*\Q$recoverdir\E/+#, @recfile);
	if (@backups != 1) {
		unlink($file);
		next;
	}

	#
	# Make a copy of the backup file path.
	# We must not modify @backups directly since it contains
	# references to data in @recfile which we pipe to sendmail.
	#
	$backups[0] =~ m#^X-vi-recover-path:\s*\Q$recoverdir\E/+(.*)[\r\n]*$#;
	my $backup = $1;

	#
	# If backup file is not rooted in the recover dir, ignore it.
	# If backup file owner doesn't match recovery file owner, ignore it.
	# If backup file is zero length or not a regular file, remove it.
	# Else send mail to the user.
	#
	if ($backup =~ m#/# || !lstat($backup)) {
		unlink($file);
	} elsif ($owner != 0 && (stat(_))[4] != $owner) {
		unlink($file);
	} elsif (! -f _ || ! -s _) {
		unlink($file, $backup);
	} else {
		open(SENDMAIL, "|$sendmail -t") ||
		    die "$0: can't run $sendmail -t: $!\n";
		print SENDMAIL @recfile;
		close(SENDMAIL);
	}
}
closedir(RECDIR);
close(DIR);

exit(0);