tty.c revision 1.22
1/* $OpenBSD: tty.c,v 1.22 2019/06/28 13:35:02 deraadt 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 47struct 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 60static void tty_flush(struct tty *); 61static int tty_getc(struct tty *); 62static int tty_insert(struct tty *, int, int); 63static void tty_putc(struct tty *, int); 64static void tty_reset(struct tty *); 65static void tty_visc(struct tty *, int); 66 67static struct tty tty; 68static volatile sig_atomic_t ttysignal; /* Interrupted by a signal? */ 69 70/* 71 * Read all relevant header fields. 72 */ 73int 74grabh(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; 148out: 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 */ 170char * 171readtty(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 245redo: 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 */ 258void 259ttystop(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/*ARGSUSED*/ 285void 286ttyint(int s) 287{ 288 289 ttysignal = s; 290} 291 292static void 293tty_flush(struct tty *t) 294{ 295 size_t i, len; 296 int c; 297 298 if (t->cursor < t->len) { 299 for (; t->cursor < t->len; t->cursor++) 300 tty_visc(t, t->buf[t->cursor]); 301 } else if (t->cursor > t->len) { 302 len = t->cursor - t->len; 303 for (i = len; i > 0; i--) { 304 c = t->buf[--t->cursor]; 305 if (c == '\t') 306 len += TABWIDTH - 1; 307 else if (iscntrl(c)) 308 len++; /* account for leading ^ */ 309 } 310 for (i = 0; i < len; i++) 311 tty_putc(t, '\b'); 312 for (i = 0; i < len; i++) 313 tty_putc(t, ' '); 314 for (i = 0; i < len; i++) 315 tty_putc(t, '\b'); 316 t->cursor = t->len; 317 } 318 319 t->buf[t->len] = '\0'; 320} 321 322static int 323tty_getc(struct tty *t) 324{ 325 ssize_t n; 326 unsigned char c; 327 328 n = read(t->fdin, &c, 1); 329 switch (n) { 330 case -1: 331 t->flags |= TTY_ERR; 332 /* FALLTHROUGH */ 333 case 0: 334 return 0; 335 default: 336 return c & 0x7f; 337 } 338} 339 340static int 341tty_insert(struct tty *t, int c, int nocntrl) 342{ 343 const unsigned char *ws = " \t"; 344 345 if (CCEQ(t->keys[VERASE], c)) { 346 if (nocntrl) 347 return 0; 348 if (t->len > 0) 349 t->len--; 350 } else if (CCEQ(t->keys[VWERASE], c)) { 351 if (nocntrl) 352 return 0; 353 for (; t->len > 0; t->len--) 354 if (strchr(ws, t->buf[t->len - 1]) == NULL 355 && ((t->flags & TTY_ALTWERASE) == 0 356 || isalpha(t->buf[t->len - 1]))) 357 break; 358 for (; t->len > 0; t->len--) 359 if (strchr(ws, t->buf[t->len - 1]) != NULL 360 || ((t->flags & TTY_ALTWERASE) 361 && !isalpha(t->buf[t->len - 1]))) 362 break; 363 } else if (CCEQ(t->keys[VKILL], c)) { 364 if (nocntrl) 365 return 0; 366 t->len = 0; 367 } else { 368 if (t->len == t->size) 369 return 1; 370 t->buf[t->len++] = c; 371 } 372 373 return 0; 374} 375 376static void 377tty_putc(struct tty *t, int c) 378{ 379 unsigned char cc = c; 380 381 write(t->fdout, &cc, 1); 382} 383 384static void 385tty_reset(struct tty *t) 386{ 387 memset(t->buf, 0, t->len); 388 t->len = t->cursor = 0; 389} 390 391static void 392tty_visc(struct tty *t, int c) 393{ 394 int i; 395 396 if (c == '\t') { 397 for (i = 0; i < TABWIDTH; i++) 398 tty_putc(t, ' '); 399 } else if (iscntrl(c)) { 400 tty_putc(t, '^'); 401 if (c == 0x7F) 402 tty_putc(t, '?'); 403 else 404 tty_putc(t, (c | 0x40)); 405 } else { 406 tty_putc(t, c); 407 } 408} 409