1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 * Copyright (c) 2016 by Delphix. All rights reserved.
26 */
27
28/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29/*	  All Rights Reserved  	*/
30
31
32/* Copyright (c) 1981 Regents of the University of California */
33
34#pragma ident	"%Z%%M%	%I%	%E% SMI"
35
36#include <ctype.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <sys/fcntl.h>
40#include <errno.h>
41#include <dirent.h>
42#include <pwd.h>
43#include <locale.h>
44#include <limits.h>
45#include <unistd.h>
46
47#define	BUFSIZE	(LINE_MAX*2)	/* This should agree with what's in ex.h */
48
49#include "ex_tune.h"
50
51#define	FTYPE(A)	(A.st_mode)
52#define	FMODE(A)	(A.st_mode)
53#define	IDENTICAL(A, B)	(A.st_dev == B.st_dev && A.st_ino == B.st_ino)
54#define	ISBLK(A)	((A.st_mode & S_IFMT) == S_IFBLK)
55#define	ISCHR(A)	((A.st_mode & S_IFMT) == S_IFCHR)
56#define	ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
57#define	ISFIFO(A)	((A.st_mode & S_IFMT) == S_IFIFO)
58#define	ISREG(A)	((A.st_mode & S_IFMT) == S_IFREG)
59
60/*
61 * Expreserve - preserve a file in usrpath(preserve)
62 *
63 * This routine is very naive - it doesn't remove anything from
64 * usrpath(preserve)... this may mean that we  * stuff there...
65 * the danger in doing anything with usrpath(preserve)
66 * is that the clock may be messed up and we may get confused.
67 *
68 * We are called in two ways - first from the editor with no arguments
69 * and the standard input open on the temp file. Second with an argument
70 * to preserve the entire contents of /var/tmp (root only).
71 *
72 * BUG: should do something about preserving Rx... (register contents)
73 *      temporaries.
74 */
75
76struct 	header {
77	time_t	Time;			/* Time temp file last updated */
78	int	Uid;			/* This user's identity */
79#ifndef VMUNIX
80	short	Flines;			/* Number of lines in file */
81#else
82	int	Flines;
83#endif
84	unsigned char	Savedfile[FNSIZE];	/* The current file name */
85	short	Blocks[LBLKS];		/* Blocks where line pointers stashed */
86	short	encrypted;		/* Encrypted temp file flag */
87} H;
88
89#define	eq(a, b) strcmp(a, b) == 0
90
91void notify(int, unsigned char *, int, int);
92void mkdigits(unsigned char *);
93
94int
95main(argc)
96	int argc;
97{
98	DIR *tf;
99	struct dirent64 *direntry;
100	unsigned char *filname;
101	struct stat64 stbuf;
102
103	(void) setlocale(LC_ALL, "");
104#if !defined(TEXT_DOMAIN)
105#define	TEXT_DOMAIN "SYS_TEST"
106#endif
107	(void) textdomain(TEXT_DOMAIN);
108	/*
109	 * If only one argument, then preserve the standard input.
110	 */
111	if (argc == 1) {
112		if (copyout((unsigned char *) 0))
113			return (1);
114		return (0);
115	}
116
117	/*
118	 * If not super user, then can only preserve standard input.
119	 */
120	if (getuid()) {
121		fprintf(stderr, gettext("NOT super user\n"));
122		return (1);
123	}
124
125	/*
126	 * ... else preserve all the stuff in /var/tmp, removing
127	 * it as we go.
128	 */
129	if (chdir(TMPDIR) < 0) {
130		perror(TMPDIR);
131		return (1);
132	}
133
134	if ((tf = opendir(".")) == NULL)
135	{
136		perror(TMPDIR);
137		return (1);
138	}
139	while ((direntry = readdir64(tf)) != NULL)
140	{
141		filname = (unsigned char *)direntry->d_name;
142		/*
143		 * Ex temporaries must begin with Ex;
144		 * we check that the 12th character of the name is null
145		 * so we won't have to worry about non-null terminated names
146		 * later on.
147		 */
148		if (filname[0] != 'E' || filname[1] != 'x' || filname[12])
149			continue;
150		if (stat64((char *)filname, &stbuf))
151			continue;
152		if (!ISREG(stbuf))
153			continue;
154		/*
155		 * Save the file.
156		 */
157		(void) copyout(filname);
158	}
159	closedir(tf);
160	return (0);
161}
162
163unsigned char	mydir[] =	USRPRESERVE;
164unsigned char	pattern[] =	"/Exaa`XXXXXXXXXX";
165
166/*
167 * Copy file name into usrpath(preserve)/...
168 * If name is (char *) 0, then do the standard input.
169 * We make some checks on the input to make sure it is
170 * really an editor temporary, generate a name for the
171 * file (this is the slowest thing since we must stat
172 * to find a unique name), and finally copy the file.
173 */
174int
175copyout(unsigned char *name)
176{
177	int i;
178	static int reenter;
179	unsigned char buf[BUFSIZE];
180	unsigned char	savdir[PATH_MAX+1];
181	unsigned char	savfil[PATH_MAX+1];
182	struct passwd *pp;
183	struct stat64	stbuf;
184	int savfild;
185
186	/*
187	 * The first time we put in the digits of our
188	 * process number at the end of the pattern.
189	 */
190	if (reenter == 0) {
191		mkdigits(pattern);
192		reenter++;
193	}
194
195	/*
196	 * If a file name was given, make it the standard
197	 * input if possible.
198	 */
199	if (name != 0) {
200		(void) close(0);
201		/*
202		 * Need read/write access for arcane reasons
203		 * (see below).
204		 */
205		if (open(name, O_RDWR) < 0)
206			return (-1);
207	}
208
209	/*
210	 * Get the header block.
211	 */
212	(void) lseek(0, 0l, 0);
213	if (read(0, (char *)&H, sizeof (H)) != sizeof (H)) {
214format:
215		if (name == 0)
216			fprintf(stderr, gettext("Buffer format error\t"));
217		else {
218			/*
219			 * avoid having a bunch of NULL Ex* files
220			 * hanging around
221			 */
222			struct stat64 stbuf;
223
224			if (stat64((char *)name, &stbuf) == 0)
225			if (stbuf.st_size == 0)
226				(void) unlink((char *)name);
227		}
228		return (-1);
229	}
230
231	/*
232	 * Consistency checks so we don't copy out garbage.
233	 */
234	if (H.Flines < 0) {
235#ifdef DEBUG
236		fprintf(stderr, "Negative number of lines\n");
237#endif
238		goto format;
239	}
240	if (H.Blocks[0] != HBLKS || H.Blocks[1] != HBLKS+1) {
241#ifdef DEBUG
242		fprintf(stderr, "Blocks %d %d\n", H.Blocks[0], H.Blocks[1]);
243#endif
244		goto format;
245	}
246	if (name == 0 && H.Uid != getuid()) {
247#ifdef DEBUG
248		fprintf(stderr, "Wrong user-id\n");
249#endif
250		goto format;
251	}
252	if (lseek(0, 0l, 0)) {
253#ifdef DEBUG
254		fprintf(stderr, gettext("Negative number of lines\n"));
255#endif
256		goto format;
257	}
258
259	/*
260	 * If no name was assigned to the file, then give it the name
261	 * LOST, by putting this in the header.
262	 */
263	if (H.Savedfile[0] == 0) {
264		(void) strcpy(H.Savedfile, "LOST");
265		(void) write(0, (char *) &H, sizeof (H));
266		H.Savedfile[0] = 0;
267		(void) lseek(0, 0l, 0);
268	}
269
270	/*
271	 * See if preservation directory for user exists.
272	 */
273
274	strcpy(savdir, mydir);
275	pp = getpwuid(H.Uid);
276	if (pp)
277		strcat(savdir, pp->pw_name);
278	else {
279		fprintf(stderr, gettext("Unable to get uid for user.\n"));
280		return (-1);
281	}
282	if (lstat64((char *)savdir, &stbuf) < 0 || !S_ISDIR(stbuf.st_mode)) {
283		/* It doesn't exist or it isn't a directory, safe to unlink */
284		(void) unlink((char *)savdir);
285		if (mkdir((char *)savdir, 0700) < 0) {
286			fprintf(stderr,
287				gettext("Unable to create directory \"%s\"\n"),
288				savdir);
289			perror("");
290			return (-1);
291		}
292		(void) chmod((char *)savdir, 0700);
293		(void) chown((char *)savdir, H.Uid, 2);
294	}
295
296	/*
297	 * File is good.  Get a name and create a file for the copy.
298	 */
299	(void) close(1);
300	if ((savfild = mknext(savdir, pattern)) < 0) {
301		if (name == 0)
302			perror((char *)savfil);
303		return	(1);
304	}
305	strcpy(savfil, savdir);
306	strcat(savfil, pattern);
307	/*
308	 * Make target owned by user.
309	 */
310
311	(void) fchown(savfild, H.Uid, 2);
312
313	/*
314	 * Copy the file.
315	 */
316	for (;;) {
317		i = read(0, buf, BUFSIZE);
318		if (i < 0) {
319			if (name)
320				perror(gettext("Buffer read error"));
321			(void) unlink((char *)savfil);
322			return (-1);
323		}
324		if (i == 0) {
325			if (name)
326				(void) unlink((char *)name);
327			notify(H.Uid, H.Savedfile, (int) name, H.encrypted);
328			return (0);
329		}
330		if (write(savfild, buf, i) != i) {
331			if (name == 0)
332				perror((char *)savfil);
333			(void) unlink((char *)savfil);
334			return (-1);
335		}
336	}
337}
338
339/*
340 * Blast the last 5 characters of cp to be the process number.
341 */
342void
343mkdigits(unsigned char *cp)
344{
345	pid_t i;
346	int j;
347
348	for (i = getpid(), j = 10, cp += strlen(cp); j > 0; i /= 10, j--)
349		*--cp = i % 10 | '0';
350}
351
352/*
353 * Make the name in cp be unique by clobbering up to
354 * three alphabetic characters into a sequence of the form 'aab', 'aac', etc.
355 * Mktemp gets weird names too quickly to be useful here.
356 */
357int
358mknext(unsigned char *dir, unsigned char *cp)
359{
360	unsigned char *dcp;
361	struct stat stb;
362	unsigned char path[PATH_MAX+1];
363	int fd;
364
365	strcpy(path, dir);
366	strcat(path, cp);
367	dcp = path + strlen(path) - 1;
368
369	while (isdigit(*dcp))
370		dcp--;
371
372	do {
373		if (dcp[0] == 'z') {
374			dcp[0] = 'a';
375			if (dcp[-1] == 'z') {
376				dcp[-1] = 'a';
377				if (dcp[-2] == 'z') {
378					fprintf(stderr,
379						gettext("Can't find a name\t"));
380						return (-1);
381				}
382				dcp[-2]++;
383			} else
384				dcp[-1]++;
385		} else
386			dcp[0]++;
387
388	} while (((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, 0600)) < 0) &&
389		errno == EEXIST);
390	/* copy out patern */
391	strcpy(cp, path + strlen(dir));
392	return (fd);
393}
394
395/*
396 * Notify user uid that their file fname has been saved.
397 */
398void
399notify(int uid, unsigned char *fname, int flag, int cryflag)
400{
401
402#define MAXHOSTNAMELEN 256
403
404	struct passwd *pp = getpwuid(uid);
405	FILE *mf;
406	unsigned char cmd[BUFSIZE];
407
408	char hostname[MAXHOSTNAMELEN];
409	int namelen = MAXHOSTNAMELEN ;
410
411	if (gethostname((char *)hostname, namelen) == -1)
412	  return;
413
414	if (pp == NULL)
415		return;
416	sprintf((char *)cmd, "/usr/bin/mail %s", pp->pw_name);
417	mf = popen((char *)cmd, "w");
418	if (mf == NULL)
419		return;
420	setbuf(mf, (char *)cmd);
421	if (fname[0] == 0) {
422		fprintf(mf, flag ?
423"A copy of an editor buffer of yours was saved on %s when the system went down.\n" :
424"A copy of an editor buffer of yours was saved on %s when the editor was killed\nor was unable to save your changes.\n", hostname);
425		fprintf(mf,
426"No name was associated with this buffer so it has been named \"LOST\".\n");
427	} else
428		fprintf(mf, flag ?
429"A copy of an editor buffer of your file \"%s\"%s was saved on %s\nwhen the system \
430went down.\n" :
431"A copy of an editor buffer of your file \"%s\"%s was saved on %s\nwhen the editor \
432was killed or was unable to save your changes.\n", fname, (cryflag) ? "[ENCRYPTED]" : "", hostname);
433		/*
434		 * "the editor was killed" is perhaps still not an ideal
435		 * error message.  Usually, either it was forceably terminated
436		 * or the phone was hung up, but we don't know which.
437		 */
438	fprintf(mf,
439"This buffer can be retrieved using the \"recover\" command of the editor.\n");
440	fprintf(mf,
441"An easy way to do this is to give the command \"vi -r %s\".\n",
442		(fname[0] == 0) ? "LOST" : (char *) fname);
443	fprintf(mf, "This works for \"edit\" and \"ex\" also.\n");
444	(void) pclose(mf);
445}
446