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 47 usage() 48 { 49 fprintf(stderr, "usage: %s /dev/console|username logfile\n", 50 getprogname()); 51 exit(2); 52 } 53 54 int 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 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 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 197 timeout(int sig) 198 { 199 fprintf(lg, "signal timeout %d\n", sig); 200 restore(); 201 errx(3, "timeout"); 202 } 203 204 void 205 terminate(int sig) 206 { 207 fprintf(lg, "signal terminate %d\n", sig); 208 restore(); 209 errx(3, "terminate"); 210 } 211 212 void 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