xref: /openbsd-src/usr.bin/vi/build/recover (revision 8280ef193be08a9060f7c5daadb69934d7b7d937)
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