1 /* $OpenBSD: tty.c,v 1.24 2023/11/03 19:32:28 anton Exp $ */ 2 /* $NetBSD: tty.c,v 1.7 1997/07/09 05:25:46 mikel 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 /* 34 * Mail -- a mail program 35 * 36 * Generally useful tty stuff. 37 */ 38 39 #include "rcv.h" 40 #include "extern.h" 41 #include <sys/ioctl.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 45 #define TABWIDTH 8 46 47 struct tty { 48 int fdin; 49 int fdout; 50 int flags; 51 #define TTY_ALTWERASE 0x1 52 #define TTY_ERR 0x2 53 cc_t *keys; 54 char *buf; 55 size_t size; 56 size_t len; 57 size_t cursor; 58 }; 59 60 static void tty_flush(struct tty *); 61 static int tty_getc(struct tty *); 62 static int tty_insert(struct tty *, int, int); 63 static void tty_putc(struct tty *, int); 64 static void tty_reset(struct tty *); 65 static void tty_visc(struct tty *, int); 66 67 static struct tty tty; 68 static volatile sig_atomic_t ttysignal; /* Interrupted by a signal? */ 69 70 /* 71 * Read all relevant header fields. 72 */ 73 int 74 grabh(struct header *hp, int gflags) 75 { 76 struct termios newtio, oldtio; 77 #ifdef TIOCEXT 78 int extproc; 79 int flag; 80 #endif 81 struct sigaction savetstp; 82 struct sigaction savettou; 83 struct sigaction savettin; 84 struct sigaction act; 85 char *s; 86 int error; 87 88 sigemptyset(&act.sa_mask); 89 act.sa_flags = SA_RESTART; 90 act.sa_handler = SIG_DFL; 91 (void)sigaction(SIGTSTP, &act, &savetstp); 92 (void)sigaction(SIGTTOU, &act, &savettou); 93 (void)sigaction(SIGTTIN, &act, &savettin); 94 error = 1; 95 memset(&tty, 0, sizeof(tty)); 96 tty.fdin = fileno(stdin); 97 tty.fdout = fileno(stdout); 98 if (tcgetattr(tty.fdin, &oldtio) == -1) { 99 warn("tcgetattr"); 100 return(-1); 101 } 102 tty.keys = oldtio.c_cc; 103 if (oldtio.c_lflag & ALTWERASE) 104 tty.flags |= TTY_ALTWERASE; 105 106 newtio = oldtio; 107 newtio.c_lflag &= ~(ECHO | ICANON); 108 newtio.c_cc[VMIN] = 1; 109 newtio.c_cc[VTIME] = 0; 110 if (tcsetattr(tty.fdin, TCSADRAIN, &newtio) == -1) { 111 warn("tcsetattr"); 112 return(-1); 113 } 114 115 #ifdef TIOCEXT 116 extproc = ((oldtio.c_lflag & EXTPROC) ? 1 : 0); 117 if (extproc) { 118 flag = 0; 119 if (ioctl(fileno(stdin), TIOCEXT, &flag) == -1) 120 warn("TIOCEXT: off"); 121 } 122 #endif 123 if (gflags & GTO) { 124 s = readtty("To: ", detract(hp->h_to, 0)); 125 if (s == NULL) 126 goto out; 127 hp->h_to = extract(s, GTO); 128 } 129 if (gflags & GSUBJECT) { 130 s = readtty("Subject: ", hp->h_subject); 131 if (s == NULL) 132 goto out; 133 hp->h_subject = s; 134 } 135 if (gflags & GCC) { 136 s = readtty("Cc: ", detract(hp->h_cc, 0)); 137 if (s == NULL) 138 goto out; 139 hp->h_cc = extract(s, GCC); 140 } 141 if (gflags & GBCC) { 142 s = readtty("Bcc: ", detract(hp->h_bcc, 0)); 143 if (s == NULL) 144 goto out; 145 hp->h_bcc = extract(s, GBCC); 146 } 147 error = 0; 148 out: 149 (void)sigaction(SIGTSTP, &savetstp, NULL); 150 (void)sigaction(SIGTTOU, &savettou, NULL); 151 (void)sigaction(SIGTTIN, &savettin, NULL); 152 #ifdef TIOCEXT 153 if (extproc) { 154 flag = 1; 155 if (ioctl(fileno(stdin), TIOCEXT, &flag) == -1) 156 warn("TIOCEXT: on"); 157 } 158 #endif 159 if (tcsetattr(tty.fdin, TCSADRAIN, &oldtio) == -1) 160 warn("tcsetattr"); 161 return(error); 162 } 163 164 /* 165 * Read up a header from standard input. 166 * The source string has the preliminary contents to 167 * be read. 168 * 169 */ 170 char * 171 readtty(char *pr, char *src) 172 { 173 struct sigaction act, saveint; 174 unsigned char canonb[BUFSIZ]; 175 char *cp; 176 sigset_t oset; 177 int c, done; 178 179 memset(canonb, 0, sizeof(canonb)); 180 tty.buf = canonb; 181 tty.size = sizeof(canonb) - 1; 182 183 for (cp = pr; *cp != '\0'; cp++) 184 tty_insert(&tty, *cp, 1); 185 tty_flush(&tty); 186 tty_reset(&tty); 187 188 if (src != NULL && strlen(src) > sizeof(canonb) - 2) { 189 puts("too long to edit"); 190 return(src); 191 } 192 if (src != NULL) { 193 for (cp = src; *cp != '\0'; cp++) 194 tty_insert(&tty, *cp, 1); 195 tty_flush(&tty); 196 } 197 198 sigemptyset(&act.sa_mask); 199 act.sa_flags = 0; /* Note: will not restart syscalls */ 200 act.sa_handler = ttyint; 201 (void)sigaction(SIGINT, &act, &saveint); 202 act.sa_handler = ttystop; 203 (void)sigaction(SIGTSTP, &act, NULL); 204 (void)sigaction(SIGTTOU, &act, NULL); 205 (void)sigaction(SIGTTIN, &act, NULL); 206 (void)sigprocmask(SIG_UNBLOCK, &intset, &oset); 207 for (;;) { 208 c = tty_getc(&tty); 209 switch (ttysignal) { 210 case SIGINT: 211 tty_visc(&tty, '\003'); /* output ^C */ 212 /* FALLTHROUGH */ 213 case 0: 214 break; 215 default: 216 ttysignal = 0; 217 goto redo; 218 } 219 if (c == 0) { 220 done = 1; 221 } else if (c == '\n') { 222 tty_putc(&tty, c); 223 done = 1; 224 } else { 225 done = tty_insert(&tty, c, 0); 226 tty_flush(&tty); 227 } 228 if (done) 229 break; 230 } 231 act.sa_handler = SIG_DFL; 232 sigemptyset(&act.sa_mask); 233 act.sa_flags = SA_RESTART; 234 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 235 (void)sigaction(SIGTSTP, &act, NULL); 236 (void)sigaction(SIGTTOU, &act, NULL); 237 (void)sigaction(SIGTTIN, &act, NULL); 238 (void)sigaction(SIGINT, &saveint, NULL); 239 if (tty.flags & TTY_ERR) { 240 if (ttysignal == SIGINT) { 241 ttysignal = 0; 242 return(NULL); /* user hit ^C */ 243 } 244 245 redo: 246 cp = strlen(canonb) > 0 ? canonb : NULL; 247 /* XXX - make iterative, not recursive */ 248 return(readtty(pr, cp)); 249 } 250 if (equal("", canonb)) 251 return(""); 252 return(savestr(canonb)); 253 } 254 255 /* 256 * Receipt continuation. 257 */ 258 void 259 ttystop(int s) 260 { 261 struct sigaction act, oact; 262 sigset_t nset; 263 int save_errno; 264 265 /* 266 * Save old handler and set to default. 267 * Unblock receipt of 's' and then resend it. 268 */ 269 save_errno = errno; 270 (void)sigemptyset(&act.sa_mask); 271 act.sa_flags = SA_RESTART; 272 act.sa_handler = SIG_DFL; 273 (void)sigaction(s, &act, &oact); 274 (void)sigemptyset(&nset); 275 (void)sigaddset(&nset, s); 276 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 277 (void)kill(0, s); 278 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 279 (void)sigaction(s, &oact, NULL); 280 ttysignal = s; 281 errno = save_errno; 282 } 283 284 void 285 ttyint(int s) 286 { 287 288 ttysignal = s; 289 } 290 291 static void 292 tty_flush(struct tty *t) 293 { 294 size_t i, len; 295 int c; 296 297 if (t->cursor < t->len) { 298 for (; t->cursor < t->len; t->cursor++) 299 tty_visc(t, t->buf[t->cursor]); 300 } else if (t->cursor > t->len) { 301 len = t->cursor - t->len; 302 for (i = len; i > 0; i--) { 303 c = t->buf[--t->cursor]; 304 if (c == '\t') 305 len += TABWIDTH - 1; 306 else if (iscntrl(c)) 307 len++; /* account for leading ^ */ 308 } 309 for (i = 0; i < len; i++) 310 tty_putc(t, '\b'); 311 for (i = 0; i < len; i++) 312 tty_putc(t, ' '); 313 for (i = 0; i < len; i++) 314 tty_putc(t, '\b'); 315 t->cursor = t->len; 316 } 317 318 t->buf[t->len] = '\0'; 319 } 320 321 static int 322 tty_getc(struct tty *t) 323 { 324 ssize_t n; 325 unsigned char c; 326 327 if (ttysignal != 0) 328 n = -1; 329 else 330 n = read(t->fdin, &c, 1); 331 switch (n) { 332 case -1: 333 t->flags |= TTY_ERR; 334 /* FALLTHROUGH */ 335 case 0: 336 return 0; 337 default: 338 return c & 0x7f; 339 } 340 } 341 342 static int 343 tty_insert(struct tty *t, int c, int nocntrl) 344 { 345 const unsigned char *ws = " \t"; 346 347 if (CCEQ(t->keys[VERASE], c)) { 348 if (nocntrl) 349 return 0; 350 if (t->len > 0) 351 t->len--; 352 } else if (CCEQ(t->keys[VWERASE], c)) { 353 if (nocntrl) 354 return 0; 355 for (; t->len > 0; t->len--) 356 if (strchr(ws, t->buf[t->len - 1]) == NULL 357 && ((t->flags & TTY_ALTWERASE) == 0 358 || isalpha(t->buf[t->len - 1]))) 359 break; 360 for (; t->len > 0; t->len--) 361 if (strchr(ws, t->buf[t->len - 1]) != NULL 362 || ((t->flags & TTY_ALTWERASE) 363 && !isalpha(t->buf[t->len - 1]))) 364 break; 365 } else if (CCEQ(t->keys[VKILL], c)) { 366 if (nocntrl) 367 return 0; 368 t->len = 0; 369 } else { 370 if (t->len == t->size) 371 return 1; 372 t->buf[t->len++] = c; 373 } 374 375 return 0; 376 } 377 378 static void 379 tty_putc(struct tty *t, int c) 380 { 381 unsigned char cc = c; 382 383 write(t->fdout, &cc, 1); 384 } 385 386 static void 387 tty_reset(struct tty *t) 388 { 389 memset(t->buf, 0, t->len); 390 t->len = t->cursor = 0; 391 } 392 393 static void 394 tty_visc(struct tty *t, int c) 395 { 396 int i; 397 398 if (c == '\t') { 399 for (i = 0; i < TABWIDTH; i++) 400 tty_putc(t, ' '); 401 } else if (iscntrl(c)) { 402 tty_putc(t, '^'); 403 if (c == 0x7F) 404 tty_putc(t, '?'); 405 else 406 tty_putc(t, (c | 0x40)); 407 } else { 408 tty_putc(t, c); 409 } 410 } 411