1 /* $OpenBSD: cu.c,v 1.24 2015/10/16 07:01:53 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2012 Nicholas Marriott <nicm@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 MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/ioctl.h> 20 21 #include <ctype.h> 22 #include <err.h> 23 #include <event.h> 24 #include <fcntl.h> 25 #include <getopt.h> 26 #include <paths.h> 27 #include <pwd.h> 28 #include <signal.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <termios.h> 33 #include <unistd.h> 34 #include <limits.h> 35 36 #include "cu.h" 37 38 extern char *__progname; 39 40 FILE *record_file; 41 struct termios saved_tio; 42 struct bufferevent *input_ev; 43 struct bufferevent *output_ev; 44 int is_direct = -1; 45 const char *line_path = NULL; 46 int line_speed = -1; 47 int line_fd; 48 struct termios line_tio; 49 struct bufferevent *line_ev; 50 struct event sigterm_ev; 51 struct event sighup_ev; 52 enum { 53 STATE_NONE, 54 STATE_NEWLINE, 55 STATE_TILDE 56 } last_state = STATE_NEWLINE; 57 58 __dead void usage(void); 59 void signal_event(int, short, void *); 60 void stream_read(struct bufferevent *, void *); 61 void stream_error(struct bufferevent *, short, void *); 62 void line_read(struct bufferevent *, void *); 63 void line_error(struct bufferevent *, short, void *); 64 void try_remote(const char *, const char *, const char *); 65 66 __dead void 67 usage(void) 68 { 69 fprintf(stderr, "usage: %s [-d] [-l line] [-s speed | -speed]\n", 70 __progname); 71 fprintf(stderr, " %s [host]\n", __progname); 72 exit(1); 73 } 74 75 int 76 main(int argc, char **argv) 77 { 78 const char *errstr; 79 char *tmp, *s, *host; 80 int opt, i, flags; 81 82 if (pledge("stdio rpath wpath cpath getpw proc exec tty", 83 NULL) == -1) 84 err(1, "pledge"); 85 86 if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) 87 err(1, "tcgetattr"); 88 89 /* 90 * Convert obsolescent -### speed to modern -s### syntax which getopt() 91 * can handle. 92 */ 93 for (i = 1; i < argc; i++) { 94 if (strcmp("--", argv[i]) == 0) 95 break; 96 if (argv[i][0] != '-' || !isdigit((unsigned char)argv[i][1])) 97 continue; 98 99 if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1) 100 errx(1, "speed asprintf"); 101 } 102 103 while ((opt = getopt(argc, argv, "dl:s:")) != -1) { 104 switch (opt) { 105 case 'd': 106 is_direct = 1; 107 break; 108 case 'l': 109 line_path = optarg; 110 break; 111 case 's': 112 line_speed = strtonum(optarg, 0, INT_MAX, &errstr); 113 if (errstr != NULL) 114 errx(1, "speed is %s: %s", errstr, optarg); 115 break; 116 default: 117 usage(); 118 } 119 } 120 argc -= optind; 121 argv += optind; 122 if (argc != 0 && argc != 1) 123 usage(); 124 125 if (line_path != NULL || line_speed != -1 || is_direct != -1) { 126 if (argc != 0) 127 usage(); 128 } else { 129 if (argc == 1) 130 host = argv[0]; 131 else 132 host = getenv("HOST"); 133 if (host != NULL && *host != '\0') { 134 if (*host == '/') 135 line_path = host; 136 else { 137 s = getenv("REMOTE"); 138 if (s != NULL && *s == '/') 139 try_remote(host, s, NULL); 140 else 141 try_remote(host, NULL, s); 142 } 143 } 144 } 145 146 if (line_path == NULL) 147 line_path = "/dev/cua00"; 148 if (line_speed == -1) 149 line_speed = 9600; 150 if (is_direct == -1) 151 is_direct = 0; 152 153 if (strchr(line_path, '/') == NULL) { 154 if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1) 155 err(1, "asprintf"); 156 line_path = tmp; 157 } 158 159 flags = O_RDWR; 160 if (is_direct) 161 flags |= O_NONBLOCK; 162 line_fd = open(line_path, flags); 163 if (line_fd < 0) 164 err(1, "open(\"%s\")", line_path); 165 if (ioctl(line_fd, TIOCEXCL) != 0) 166 err(1, "ioctl(TIOCEXCL)"); 167 if (tcgetattr(line_fd, &line_tio) != 0) 168 err(1, "tcgetattr"); 169 if (set_line(line_speed) != 0) 170 err(1, "tcsetattr"); 171 172 event_init(); 173 174 signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); 175 signal_add(&sigterm_ev, NULL); 176 signal_set(&sighup_ev, SIGHUP, signal_event, NULL); 177 signal_add(&sighup_ev, NULL); 178 if (signal(SIGINT, SIG_IGN) == SIG_ERR) 179 err(1, "signal"); 180 if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) 181 err(1, "signal"); 182 183 set_termios(); /* after this use cu_err and friends */ 184 185 /* stdin and stdout get separate events */ 186 input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, 187 stream_error, NULL); 188 bufferevent_enable(input_ev, EV_READ); 189 output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, 190 NULL); 191 bufferevent_enable(output_ev, EV_WRITE); 192 193 set_blocking(line_fd, 0); 194 line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, 195 NULL); 196 bufferevent_enable(line_ev, EV_READ|EV_WRITE); 197 198 printf("Connected to %s (speed %d)\r\n", line_path, line_speed); 199 event_dispatch(); 200 201 restore_termios(); 202 printf("\r\n[EOT]\n"); 203 204 exit(0); 205 } 206 207 void 208 signal_event(int fd, short events, void *data) 209 { 210 restore_termios(); 211 printf("\r\n[SIG%s]\n", sys_signame[fd]); 212 213 exit(0); 214 } 215 216 void 217 set_blocking(int fd, int state) 218 { 219 int mode; 220 221 state = state ? 0 : O_NONBLOCK; 222 if ((mode = fcntl(fd, F_GETFL)) == -1) 223 cu_err(1, "fcntl"); 224 if ((mode & O_NONBLOCK) != state) { 225 mode = (mode & ~O_NONBLOCK) | state; 226 if (fcntl(fd, F_SETFL, mode) == -1) 227 cu_err(1, "fcntl"); 228 } 229 } 230 231 void 232 set_termios(void) 233 { 234 struct termios tio; 235 236 if (!isatty(STDIN_FILENO)) 237 return; 238 239 memcpy(&tio, &saved_tio, sizeof(tio)); 240 tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); 241 tio.c_iflag &= ~(INPCK|ICRNL); 242 tio.c_oflag &= ~OPOST; 243 tio.c_cc[VMIN] = 1; 244 tio.c_cc[VTIME] = 0; 245 tio.c_cc[VDISCARD] = _POSIX_VDISABLE; 246 tio.c_cc[VDSUSP] = _POSIX_VDISABLE; 247 tio.c_cc[VINTR] = _POSIX_VDISABLE; 248 tio.c_cc[VLNEXT] = _POSIX_VDISABLE; 249 tio.c_cc[VQUIT] = _POSIX_VDISABLE; 250 tio.c_cc[VSUSP] = _POSIX_VDISABLE; 251 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) 252 cu_err(1, "tcsetattr"); 253 } 254 255 void 256 restore_termios(void) 257 { 258 if (isatty(STDIN_FILENO)) 259 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); 260 } 261 262 int 263 set_line(int speed) 264 { 265 struct termios tio; 266 267 memcpy(&tio, &line_tio, sizeof(tio)); 268 tio.c_iflag &= ~(ISTRIP|ICRNL); 269 tio.c_oflag &= ~OPOST; 270 tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO); 271 tio.c_cflag &= ~(CSIZE|PARENB); 272 tio.c_cflag |= CREAD|CS8|CLOCAL; 273 tio.c_cc[VMIN] = 1; 274 tio.c_cc[VTIME] = 0; 275 cfsetspeed(&tio, speed); 276 if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) 277 return (-1); 278 return (0); 279 } 280 281 void 282 stream_read(struct bufferevent *bufev, void *data) 283 { 284 char *new_data, *ptr; 285 size_t new_size; 286 int state_change; 287 288 new_data = EVBUFFER_DATA(input_ev->input); 289 new_size = EVBUFFER_LENGTH(input_ev->input); 290 if (new_size == 0) 291 return; 292 293 state_change = isatty(STDIN_FILENO); 294 for (ptr = new_data; ptr < new_data + new_size; ptr++) { 295 switch (last_state) { 296 case STATE_NONE: 297 if (state_change && *ptr == '\r') 298 last_state = STATE_NEWLINE; 299 break; 300 case STATE_NEWLINE: 301 if (state_change && *ptr == '~') { 302 last_state = STATE_TILDE; 303 continue; 304 } 305 if (*ptr != '\r') 306 last_state = STATE_NONE; 307 break; 308 case STATE_TILDE: 309 do_command(*ptr); 310 last_state = STATE_NEWLINE; 311 continue; 312 } 313 314 bufferevent_write(line_ev, ptr, 1); 315 } 316 317 evbuffer_drain(input_ev->input, new_size); 318 } 319 320 void 321 stream_error(struct bufferevent *bufev, short what, void *data) 322 { 323 event_loopexit(NULL); 324 } 325 326 void 327 line_read(struct bufferevent *bufev, void *data) 328 { 329 char *new_data; 330 size_t new_size; 331 332 new_data = EVBUFFER_DATA(line_ev->input); 333 new_size = EVBUFFER_LENGTH(line_ev->input); 334 if (new_size == 0) 335 return; 336 337 if (record_file != NULL) 338 fwrite(new_data, 1, new_size, record_file); 339 bufferevent_write(output_ev, new_data, new_size); 340 341 evbuffer_drain(line_ev->input, new_size); 342 } 343 344 void 345 line_error(struct bufferevent *bufev, short what, void *data) 346 { 347 event_loopexit(NULL); 348 } 349 350 void 351 try_remote(const char *host, const char *path, const char *entry) 352 { 353 const char *paths[] = { "/etc/remote", NULL, NULL }; 354 char *cp, *s; 355 long l; 356 int error; 357 358 if (path != NULL) { 359 paths[0] = path; 360 paths[1] = "/etc/remote"; 361 } 362 363 if (entry != NULL && cgetset(entry) != 0) 364 cu_errx(1, "cgetset failed"); 365 error = cgetent(&cp, (char **)paths, (char *)host); 366 if (error < 0) { 367 switch (error) { 368 case -1: 369 cu_errx(1, "unknown host %s", host); 370 case -2: 371 cu_errx(1, "can't open remote file"); 372 case -3: 373 cu_errx(1, "loop in remote file"); 374 default: 375 cu_errx(1, "unknown error in remote file"); 376 } 377 } 378 379 if (is_direct == -1 && cgetcap(cp, "dc", ':') != NULL) 380 is_direct = 1; 381 382 if (line_path == NULL && cgetstr(cp, "dv", &s) >= 0) 383 line_path = s; 384 385 if (line_speed == -1 && cgetnum(cp, "br", &l) >= 0) { 386 if (l < 0 || l > INT_MAX) 387 cu_errx(1, "speed out of range"); 388 line_speed = l; 389 } 390 } 391 392 /* Expands tildes in the file name. Based on code from ssh/misc.c. */ 393 char * 394 tilde_expand(const char *filename1) 395 { 396 const char *filename, *path, *sep; 397 char user[128], *out; 398 struct passwd *pw; 399 u_int len, slash; 400 int rv; 401 402 if (*filename1 != '~') 403 goto no_change; 404 filename = filename1 + 1; 405 406 path = strchr(filename, '/'); 407 if (path != NULL && path > filename) { /* ~user/path */ 408 slash = path - filename; 409 if (slash > sizeof(user) - 1) 410 goto no_change; 411 memcpy(user, filename, slash); 412 user[slash] = '\0'; 413 if ((pw = getpwnam(user)) == NULL) 414 goto no_change; 415 } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ 416 goto no_change; 417 418 /* Make sure directory has a trailing '/' */ 419 len = strlen(pw->pw_dir); 420 if (len == 0 || pw->pw_dir[len - 1] != '/') 421 sep = "/"; 422 else 423 sep = ""; 424 425 /* Skip leading '/' from specified path */ 426 if (path != NULL) 427 filename = path + 1; 428 429 if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1) 430 cu_err(1, "asprintf"); 431 if (rv >= PATH_MAX) { 432 free(out); 433 goto no_change; 434 } 435 436 return (out); 437 438 no_change: 439 out = strdup(filename1); 440 if (out == NULL) 441 cu_err(1, "strdup"); 442 return (out); 443 } 444