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