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);
36 void redirect(void);
37 void restore(void);
38 void timeout(int);
39 void terminate(int);
40 void iostdin(int);
41
42 FILE *lg;
43 char ptyname[16], *console, *username, *logfile, *tty;
44 int mfd, sfd;
45
46 __dead void
usage()47 usage()
48 {
49 fprintf(stderr, "usage: %s /dev/console|username logfile\n",
50 getprogname());
51 exit(2);
52 }
53
54 int
main(int argc,char * argv[])55 main(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
146 void
redirect(void)147 redirect(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
176 void
restore(void)177 restore(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
196 void
timeout(int sig)197 timeout(int sig)
198 {
199 fprintf(lg, "signal timeout %d\n", sig);
200 restore();
201 errx(3, "timeout");
202 }
203
204 void
terminate(int sig)205 terminate(int sig)
206 {
207 fprintf(lg, "signal terminate %d\n", sig);
208 restore();
209 errx(3, "terminate");
210 }
211
212 void
iostdin(int sig)213 iostdin(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