1/*	$OpenBSD: script.c,v 1.35 2019/06/28 13:35:03 deraadt Exp $	*/
2/*	$NetBSD: script.c,v 1.3 1994/12/21 08:55:43 jtc Exp $	*/
3
4/*
5 * Copyright (c) 2001 Theo de Raadt
6 * 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 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * Copyright (c) 1980, 1992, 1993
31 *	The Regents of the University of California.  All rights reserved.
32 *
33 * Redistribution and use in source and binary forms, with or without
34 * modification, are permitted provided that the following conditions
35 * are met:
36 * 1. Redistributions of source code must retain the above copyright
37 *    notice, this list of conditions and the following disclaimer.
38 * 2. Redistributions in binary form must reproduce the above copyright
39 *    notice, this list of conditions and the following disclaimer in the
40 *    documentation and/or other materials provided with the distribution.
41 * 3. Neither the name of the University nor the names of its contributors
42 *    may be used to endorse or promote products derived from this software
43 *    without specific prior written permission.
44 *
45 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55 * SUCH DAMAGE.
56 */
57
58#include <sys/types.h>
59#include <sys/wait.h>
60#include <sys/stat.h>
61#include <sys/ioctl.h>
62#include <sys/time.h>
63
64#include <errno.h>
65#include <fcntl.h>
66#include <paths.h>
67#include <signal.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <termios.h>
72#include <unistd.h>
73
74#include <util.h>
75#include <err.h>
76
77FILE	*fscript;
78int	master, slave;
79volatile sig_atomic_t child;
80pid_t	subchild;
81char	*fname;
82
83volatile sig_atomic_t dead;
84volatile sig_atomic_t sigdeadstatus;
85volatile sig_atomic_t flush;
86
87struct	termios tt;
88int		istty;
89
90__dead void done(int);
91void dooutput(void);
92void doshell(char *);
93void fail(void);
94void finish(int);
95void scriptflush(int);
96void handlesigwinch(int);
97
98int
99main(int argc, char *argv[])
100{
101	extern char *__progname;
102	struct sigaction sa;
103	struct winsize win;
104	char ibuf[BUFSIZ];
105	char *cmd;
106	ssize_t cc, off;
107	int aflg, ch;
108
109	cmd = NULL;
110	aflg = 0;
111	while ((ch = getopt(argc, argv, "ac:")) != -1)
112		switch(ch) {
113		case 'a':
114			aflg = 1;
115			break;
116		case 'c':
117			cmd = optarg;
118			break;
119		default:
120			fprintf(stderr, "usage: %s [-a] [-c command] [file]\n",
121			    __progname);
122			exit(1);
123		}
124	argc -= optind;
125	argv += optind;
126
127	if (argc > 0)
128		fname = argv[0];
129	else
130		fname = "typescript";
131
132	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
133		err(1, "%s", fname);
134
135	if (isatty(0)) {
136		if (tcgetattr(STDIN_FILENO, &tt) == 0 &&
137		    ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == 0)
138			istty = 1;
139	}
140	if (openpty(&master, &slave, NULL, &tt, &win) == -1)
141		err(1, "openpty");
142
143	(void)printf("Script started, output file is %s\n", fname);
144	if (istty) {
145		struct termios rtt = tt;
146
147		cfmakeraw(&rtt);
148		rtt.c_lflag &= ~ECHO;
149		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
150	}
151
152	bzero(&sa, sizeof sa);
153	sigemptyset(&sa.sa_mask);
154	sa.sa_handler = handlesigwinch;
155	sa.sa_flags = SA_RESTART;
156	(void)sigaction(SIGWINCH, &sa, NULL);
157
158	child = fork();
159	if (child == -1) {
160		warn("fork");
161		fail();
162	}
163	if (child == 0) {
164		subchild = child = fork();
165		if (child == -1) {
166			warn("fork");
167			fail();
168		}
169		if (child)
170			dooutput();
171		else
172			doshell(cmd);
173	}
174
175	bzero(&sa, sizeof sa);
176	sigemptyset(&sa.sa_mask);
177	sa.sa_handler = finish;
178	(void)sigaction(SIGCHLD, &sa, NULL);
179
180	if (pledge("stdio proc tty", NULL) == -1)
181		err(1, "pledge");
182
183	(void)fclose(fscript);
184	while (1) {
185		if (dead)
186			break;
187		cc = read(STDIN_FILENO, ibuf, BUFSIZ);
188		if (cc == -1 && errno == EINTR)
189			continue;
190		if (cc <= 0)
191			break;
192		for (off = 0; off < cc; ) {
193			ssize_t n = write(master, ibuf + off, cc - off);
194			if (n == -1 && errno != EAGAIN)
195				break;
196			if (n == 0)
197				break;	/* skip writing */
198			if (n > 0)
199				off += n;
200		}
201	}
202	done(sigdeadstatus);
203}
204
205void
206finish(int signo)
207{
208	int save_errno = errno;
209	int status, e = 1;
210	pid_t pid;
211
212	while ((pid = wait3(&status, WNOHANG, 0)) > 0) {
213		if (pid == (pid_t)child) {
214			if (WIFEXITED(status))
215				e = WEXITSTATUS(status);
216		}
217	}
218	dead = 1;
219	sigdeadstatus = e;
220	errno = save_errno;
221}
222
223void
224handlesigwinch(int signo)
225{
226	int save_errno = errno;
227	struct winsize win;
228	pid_t pgrp;
229
230	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) != -1) {
231		ioctl(slave, TIOCSWINSZ, &win);
232		if (ioctl(slave, TIOCGPGRP, &pgrp) != -1)
233			killpg(pgrp, SIGWINCH);
234	}
235	errno = save_errno;
236}
237
238void
239dooutput(void)
240{
241	struct sigaction sa;
242	struct itimerval value;
243	sigset_t blkalrm;
244	char obuf[BUFSIZ];
245	time_t tvec;
246	ssize_t outcc = 0, cc, off;
247
248	(void)close(STDIN_FILENO);
249	tvec = time(NULL);
250	(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
251
252	sigemptyset(&blkalrm);
253	sigaddset(&blkalrm, SIGALRM);
254	bzero(&sa, sizeof sa);
255	sigemptyset(&sa.sa_mask);
256	sa.sa_handler = scriptflush;
257	(void)sigaction(SIGALRM, &sa, NULL);
258
259	bzero(&sa, sizeof sa);
260	sigemptyset(&sa.sa_mask);
261	sa.sa_handler = SIG_IGN;
262	(void)sigaction(SIGCHLD, &sa, NULL);
263
264	if (pledge("stdio proc", NULL) == -1)
265		err(1, "pledge");
266
267	value.it_interval.tv_sec = 30;
268	value.it_interval.tv_usec = 0;
269	value.it_value = value.it_interval;
270	(void)setitimer(ITIMER_REAL, &value, NULL);
271	for (;;) {
272		if (flush) {
273			if (outcc) {
274				(void)fflush(fscript);
275				outcc = 0;
276			}
277			flush = 0;
278		}
279		cc = read(master, obuf, sizeof (obuf));
280		if (cc == -1 && errno == EINTR)
281			continue;
282		if (cc <= 0)
283			break;
284		sigprocmask(SIG_BLOCK, &blkalrm, NULL);
285		for (off = 0; off < cc; ) {
286			ssize_t n = write(STDOUT_FILENO, obuf + off, cc - off);
287			if (n == -1 && errno != EAGAIN)
288				break;
289			if (n == 0)
290				break;	/* skip writing */
291			if (n > 0)
292				off += n;
293		}
294		(void)fwrite(obuf, 1, cc, fscript);
295		outcc += cc;
296		sigprocmask(SIG_UNBLOCK, &blkalrm, NULL);
297	}
298	done(0);
299}
300
301void
302scriptflush(int signo)
303{
304	flush = 1;
305}
306
307void
308doshell(char *cmd)
309{
310	char *shell;
311	char *argp[] = {"sh", "-c", NULL, NULL};
312
313	shell = getenv("SHELL");
314	if (shell == NULL)
315		shell = _PATH_BSHELL;
316
317	(void)close(master);
318	(void)fclose(fscript);
319	login_tty(slave);
320
321	if (cmd != NULL) {
322		argp[2] = cmd;
323		execv(_PATH_BSHELL, argp);
324		warn("unable to execute %s", _PATH_BSHELL);
325	} else {
326		execl(shell, shell, "-i", (char *)NULL);
327		warn("%s", shell);
328	}
329	fail();
330}
331
332void
333fail(void)
334{
335
336	(void)kill(0, SIGTERM);
337	done(1);
338}
339
340void
341done(int eval)
342{
343	time_t tvec;
344
345	if (subchild) {
346		tvec = time(NULL);
347		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
348		(void)fclose(fscript);
349		(void)close(master);
350	} else {
351		if (istty)
352			(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
353		(void)printf("Script done, output file is %s\n", fname);
354	}
355	exit(eval);
356}
357