1/*	$OpenBSD: edit.c,v 1.21 2019/06/28 13:35:01 deraadt Exp $	*/
2/*	$NetBSD: edit.c,v 1.5 1996/06/08 19:48:20 christos Exp $	*/
3
4/*
5 * Copyright (c) 1980, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/wait.h>
35
36#include "rcv.h"
37#include <errno.h>
38#include <fcntl.h>
39#include "extern.h"
40
41int editit(const char *, const char *);
42
43/*
44 * Mail -- a mail program
45 *
46 * Perform message editing functions.
47 */
48
49/*
50 * Edit a message list.
51 */
52int
53editor(void *v)
54{
55	int *msgvec = v;
56
57	return(edit1(msgvec, 'e'));
58}
59
60/*
61 * Invoke the visual editor on a message list.
62 */
63int
64visual(void *v)
65{
66	int *msgvec = v;
67
68	return(edit1(msgvec, 'v'));
69}
70
71/*
72 * Edit a message by writing the message into a funnily-named file
73 * (which should not exist) and forking an editor on it.
74 * We get the editor from the stuff above.
75 */
76int
77edit1(int *msgvec, int type)
78{
79	int nl = 0, c, i;
80	FILE *fp;
81	struct sigaction oact;
82	sigset_t oset;
83	struct message *mp;
84	off_t size;
85
86	/*
87	 * Deal with each message to be edited . . .
88	 */
89	for (i = 0; msgvec[i] && i < msgCount; i++) {
90		if (i > 0) {
91			char buf[100];
92			char *p;
93
94			printf("Edit message %d [ynq]? ", msgvec[i]);
95			if (fgets(buf, sizeof(buf), stdin) == NULL)
96				break;
97			for (p = buf; *p == ' ' || *p == '\t'; p++)
98				;
99			if (*p == 'q')
100				break;
101			if (*p == 'n')
102				continue;
103		}
104		dot = mp = &message[msgvec[i] - 1];
105		touch(mp);
106		(void)ignoresig(SIGINT, &oact, &oset);
107		fp = run_editor(setinput(mp), (off_t)mp->m_size, type, readonly);
108		if (fp != NULL) {
109			(void)fseek(otf, 0L, SEEK_END);
110			size = ftell(otf);
111			mp->m_block = blockof(size);
112			mp->m_offset = offsetof(size);
113			mp->m_size = fsize(fp);
114			mp->m_lines = 0;
115			mp->m_flag |= MODIFY;
116			rewind(fp);
117			while ((c = getc(fp)) != EOF) {
118				if (c == '\n') {
119					mp->m_lines++;
120					nl++;
121				} else
122					nl = 0;
123				if (putc(c, otf) == EOF)
124					break;
125			}
126			for (; nl < 2; nl++) {
127				mp->m_lines++;
128				mp->m_size++;
129				putc('\n', otf);
130			}
131			if (ferror(otf))
132				warn("%s", tmpdir);
133			(void)Fclose(fp);
134		}
135		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
136		(void)sigaction(SIGINT, &oact, NULL);
137	}
138	return(0);
139}
140
141/*
142 * Run an editor on the file at "fpp" of "size" bytes,
143 * and return a new file pointer.
144 * Signals must be handled by the caller.
145 * "Type" is 'e' for _PATH_EX, 'v' for _PATH_VI.
146 */
147FILE *
148run_editor(FILE *fp, off_t size, int type, int readonly)
149{
150	FILE *nf = NULL;
151	int t;
152	time_t modtime;
153	char *edit, tempname[PATHSIZE];
154	struct stat statb;
155
156	(void)snprintf(tempname, sizeof(tempname),
157	    "%s/mail.ReXXXXXXXXXX", tmpdir);
158	if ((t = mkstemp(tempname)) == -1 ||
159	    (nf = Fdopen(t, "w")) == NULL) {
160		warn("%s", tempname);
161		goto out;
162	}
163	if (readonly && fchmod(t, 0400) == -1) {
164		warn("%s", tempname);
165		(void)rm(tempname);
166		goto out;
167	}
168	if (size >= 0)
169		while (--size >= 0 && (t = getc(fp)) != EOF)
170			(void)putc(t, nf);
171	else
172		while ((t = getc(fp)) != EOF)
173			(void)putc(t, nf);
174	(void)fflush(nf);
175	if (fstat(fileno(nf), &statb) == -1)
176		modtime = 0;
177	else
178		modtime = statb.st_mtime;
179	if (ferror(nf)) {
180		(void)Fclose(nf);
181		warn("%s", tempname);
182		(void)rm(tempname);
183		nf = NULL;
184		goto out;
185	}
186	if (Fclose(nf) < 0) {
187		warn("%s", tempname);
188		(void)rm(tempname);
189		nf = NULL;
190		goto out;
191	}
192	nf = NULL;
193	if (type == 'e') {
194		edit = value("EDITOR");
195		if (edit == NULL || edit[0] == '\0')
196			edit = _PATH_EX;
197	} else {
198		edit = value("VISUAL");
199		if (edit == NULL || edit[0] == '\0')
200			edit = _PATH_VI;
201	}
202	if (editit(edit, tempname) == -1) {
203		(void)rm(tempname);
204		goto out;
205	}
206	/*
207	 * If in read only mode or file unchanged, just remove the editor
208	 * temporary and return.
209	 */
210	if (readonly) {
211		(void)rm(tempname);
212		goto out;
213	}
214	if (stat(tempname, &statb) == -1) {
215		warn("%s", tempname);
216		goto out;
217	}
218	if (modtime == statb.st_mtime) {
219		(void)rm(tempname);
220		goto out;
221	}
222	/*
223	 * Now switch to new file.
224	 */
225	if ((nf = Fopen(tempname, "a+")) == NULL) {
226		warn("%s", tempname);
227		(void)rm(tempname);
228		goto out;
229	}
230	(void)rm(tempname);
231out:
232	return(nf);
233}
234
235/*
236 * Execute an editor on the specified pathname, which is interpreted
237 * from the shell.  This means flags may be included.
238 *
239 * Returns -1 on error, or the exit value on success.
240 */
241int
242editit(const char *ed, const char *pathname)
243{
244	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
245	sig_t sighup, sigint, sigquit, sigchld;
246	pid_t pid;
247	int saved_errno, st, ret = -1;
248
249	if (ed == NULL)
250		ed = getenv("VISUAL");
251	if (ed == NULL || ed[0] == '\0')
252		ed = getenv("EDITOR");
253	if (ed == NULL || ed[0] == '\0')
254		ed = _PATH_VI;
255	if (asprintf(&p, "%s %s", ed, pathname) == -1)
256		return (-1);
257	argp[2] = p;
258
259	sighup = signal(SIGHUP, SIG_IGN);
260	sigint = signal(SIGINT, SIG_IGN);
261	sigquit = signal(SIGQUIT, SIG_IGN);
262	sigchld = signal(SIGCHLD, SIG_DFL);
263	if ((pid = fork()) == -1)
264		goto fail;
265	if (pid == 0) {
266		execv(_PATH_BSHELL, argp);
267		_exit(127);
268	}
269	while (waitpid(pid, &st, 0) == -1)
270		if (errno != EINTR)
271			goto fail;
272	if (!WIFEXITED(st))
273		errno = EINTR;
274	else
275		ret = WEXITSTATUS(st);
276
277 fail:
278	saved_errno = errno;
279	(void)signal(SIGHUP, sighup);
280	(void)signal(SIGINT, sigint);
281	(void)signal(SIGQUIT, sigquit);
282	(void)signal(SIGCHLD, sigchld);
283	free(p);
284	errno = saved_errno;
285	return (ret);
286}
287