1/*	$OpenBSD: ttylog.c,v 1.8 2021/07/06 11:50:34 bluhm Exp $	*/
2
3/*
4 * Copyright (c) 2015 Alexander Bluhm <bluhm@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/ioctl.h>
20#include <sys/sockio.h>
21
22#include <errno.h>
23#include <err.h>
24#include <fcntl.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <signal.h>
28#include <string.h>
29#include <termios.h>
30#include <time.h>
31#include <unistd.h>
32#include <util.h>
33#include <utmp.h>
34
35__dead void usage(void);
36void redirect(void);
37void restore(void);
38void timeout(int);
39void terminate(int);
40void iostdin(int);
41
42FILE *lg;
43char ptyname[16], *console, *username, *logfile, *tty;
44int mfd, sfd;
45
46__dead void
47usage()
48{
49	fprintf(stderr, "usage: %s /dev/console|username logfile\n",
50	    getprogname());
51	exit(2);
52}
53
54int
55main(int argc, char *argv[])
56{
57	char buf[8192];
58	struct sigaction act;
59	sigset_t set;
60	ssize_t n;
61
62	if (argc != 3)
63		usage();
64	if (strcmp(argv[1], "/dev/console") == 0)
65		console = argv[1];
66	else
67		username = argv[1];
68	logfile = argv[2];
69
70	sigemptyset(&set);
71	sigaddset(&set, SIGTERM);
72	sigaddset(&set, SIGIO);
73	if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
74		err(1, "sigprocmask block init");
75
76	if ((lg = fopen(logfile, "w")) == NULL)
77		err(1, "fopen %s", logfile);
78	if (setvbuf(lg, NULL, _IOLBF, 0) != 0)
79		err(1, "setlinebuf");
80
81	memset(&act, 0, sizeof(act));
82	act.sa_mask = set;
83	act.sa_flags = SA_RESTART;
84	act.sa_handler = terminate;
85	if (sigaction(SIGTERM, &act, NULL) == -1)
86		err(1, "sigaction SIGTERM");
87	if (sigaction(SIGINT, &act, NULL) == -1)
88		err(1, "sigaction SIGINT");
89
90	if (openpty(&mfd, &sfd, ptyname, NULL, NULL) == -1)
91		err(1, "openpty");
92	fprintf(lg, "openpty %s\n", ptyname);
93	if ((tty = strrchr(ptyname, '/')) == NULL)
94		errx(1, "tty: %s", ptyname);
95	tty++;
96
97	/* login(3) searches for a controlling tty, use the created one */
98	if (dup2(sfd, 1) == -1)
99		err(1, "dup2 stdout");
100
101	redirect();
102
103	act.sa_handler = iostdin;
104	if (sigaction(SIGIO, &act, NULL) == -1)
105		err(1, "sigaction SIGIO");
106	if (setpgid(0, 0) == -1)
107		err(1, "setpgid");
108	if (fcntl(0, F_SETOWN, getpid()) == -1)
109		err(1, "fcntl F_SETOWN");
110	if (fcntl(0, F_SETFL, O_ASYNC) == -1)
111		err(1, "fcntl O_ASYNC");
112
113	act.sa_handler = timeout;
114	if (sigaction(SIGALRM, &act, NULL) == -1)
115		err(1, "sigaction SIGALRM");
116	alarm(30);
117
118	fprintf(lg, "%s: started\n", getprogname());
119
120	if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1)
121		err(1, "sigprocmask unblock init");
122
123	/* do not block signals during read, it has to be interrupted */
124	while ((n = read(mfd, buf, sizeof(buf))) > 0) {
125		if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
126			err(1, "sigprocmask block write");
127		fprintf(lg, ">>> ");
128		if (fwrite(buf, 1, n, lg) != (size_t)n)
129			err(1, "fwrite %s", logfile);
130		if (buf[n-1] != '\n')
131			fprintf(lg, "\n");
132		if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1)
133			err(1, "sigprocmask unblock write");
134	}
135	if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
136		err(1, "sigprocmask block exit");
137	if (n < 0)
138		err(1, "read %s", ptyname);
139	fprintf(lg, "EOF %s\n", ptyname);
140
141	restore();
142
143	errx(3, "EOF");
144}
145
146void
147redirect(void)
148{
149	struct utmp utmp;
150	int fd, on;
151
152	if (console) {
153		/* first remove any existing console redirection */
154		on = 0;
155		if ((fd = open("/dev/console", O_WRONLY)) == -1)
156			err(1, "open /dev/console");
157		if (ioctl(fd, TIOCCONS, &on) == -1)
158			err(1, "ioctl TIOCCONS");
159		close(fd);
160		/* then redirect console to our pseudo tty */
161		on = 1;
162		if (ioctl(sfd, TIOCCONS, &on) == -1)
163			err(1, "ioctl TIOCCONS on");
164		fprintf(lg, "console %s on %s\n", console, tty);
165	}
166	if (username) {
167		memset(&utmp, 0, sizeof(utmp));
168		strlcpy(utmp.ut_line, tty, sizeof(utmp.ut_line));
169		strlcpy(utmp.ut_name, username, sizeof(utmp.ut_name));
170		time(&utmp.ut_time);
171		login(&utmp);
172		fprintf(lg, "login %s %s\n", username, tty);
173	}
174}
175
176void
177restore(void)
178{
179	int on;
180
181	if (tty == NULL)
182		return;
183	if (console) {
184		on = 0;
185		if (ioctl(sfd, TIOCCONS, &on) == -1)
186			err(1, "ioctl TIOCCONS off");
187		fprintf(lg, "console %s off\n", tty);
188	}
189	if (username) {
190		if (logout(tty) == 0)
191			errx(1, "logout %s", tty);
192		fprintf(lg, "logout %s\n", tty);
193	}
194}
195
196void
197timeout(int sig)
198{
199	fprintf(lg, "signal timeout %d\n", sig);
200	restore();
201	errx(3, "timeout");
202}
203
204void
205terminate(int sig)
206{
207	fprintf(lg, "signal terminate %d\n", sig);
208	restore();
209	errx(3, "terminate");
210}
211
212void
213iostdin(int sig)
214{
215	char buf[8192];
216	ssize_t n;
217
218	fprintf(lg, "signal iostdin %d\n", sig);
219
220	/* try to read as many log messages as possible before terminating */
221	if (fcntl(mfd, F_SETFL, O_NONBLOCK) == -1)
222		err(1, "fcntl O_NONBLOCK");
223	while ((n = read(mfd, buf, sizeof(buf))) > 0) {
224		fprintf(lg, ">>> ");
225		if (fwrite(buf, 1, n, lg) != (size_t)n)
226			err(1, "fwrite %s", logfile);
227		if (buf[n-1] != '\n')
228			fprintf(lg, "\n");
229	}
230	if (n < 0 && errno != EAGAIN)
231		err(1, "read %s", ptyname);
232
233	if ((n = read(0, buf, sizeof(buf))) < 0)
234		err(1, "read stdin");
235	restore();
236	if (n > 0)
237		errx(3, "read stdin %zd bytes", n);
238	exit(0);
239}
240