1*439243a3Snicm /* $OpenBSD: cu.c,v 1.16 2014/03/26 13:00:50 nicm Exp $ */ 2f391cec5Snicm 3f391cec5Snicm /* 4f391cec5Snicm * Copyright (c) 2012 Nicholas Marriott <nicm@openbsd.org> 5f391cec5Snicm * 6f391cec5Snicm * Permission to use, copy, modify, and distribute this software for any 7f391cec5Snicm * purpose with or without fee is hereby granted, provided that the above 8f391cec5Snicm * copyright notice and this permission notice appear in all copies. 9f391cec5Snicm * 10f391cec5Snicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11f391cec5Snicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12f391cec5Snicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13f391cec5Snicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14f391cec5Snicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15f391cec5Snicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16f391cec5Snicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17f391cec5Snicm */ 18f391cec5Snicm 19f391cec5Snicm #include <sys/param.h> 20f391cec5Snicm #include <sys/ioctl.h> 21f391cec5Snicm 22023045f1Shalex #include <ctype.h> 23f391cec5Snicm #include <err.h> 24f391cec5Snicm #include <event.h> 25f391cec5Snicm #include <fcntl.h> 26f391cec5Snicm #include <getopt.h> 27f391cec5Snicm #include <paths.h> 28f391cec5Snicm #include <pwd.h> 29f391cec5Snicm #include <signal.h> 30f391cec5Snicm #include <stdio.h> 31f391cec5Snicm #include <stdlib.h> 32f391cec5Snicm #include <string.h> 33f391cec5Snicm #include <termios.h> 34f391cec5Snicm #include <unistd.h> 35f391cec5Snicm 36f391cec5Snicm #include "cu.h" 37f391cec5Snicm 38f391cec5Snicm extern char *__progname; 39f391cec5Snicm 4066d5e211Snicm FILE *record_file; 41f391cec5Snicm struct termios saved_tio; 42f391cec5Snicm struct bufferevent *input_ev; 43f391cec5Snicm struct bufferevent *output_ev; 44*439243a3Snicm const char *line_path = NULL; 45*439243a3Snicm int line_speed = -1; 46f391cec5Snicm int line_fd; 4789ee0e62Snicm struct termios line_tio; 48f391cec5Snicm struct bufferevent *line_ev; 49f391cec5Snicm struct event sigterm_ev; 50c30d1c6aSnicm struct event sighup_ev; 51f391cec5Snicm enum { 52f391cec5Snicm STATE_NONE, 53f391cec5Snicm STATE_NEWLINE, 54f391cec5Snicm STATE_TILDE 55f391cec5Snicm } last_state = STATE_NEWLINE; 56f391cec5Snicm 57f391cec5Snicm __dead void usage(void); 58f391cec5Snicm void signal_event(int, short, void *); 59f391cec5Snicm void stream_read(struct bufferevent *, void *); 60f391cec5Snicm void stream_error(struct bufferevent *, short, void *); 61f391cec5Snicm void line_read(struct bufferevent *, void *); 62f391cec5Snicm void line_error(struct bufferevent *, short, void *); 63*439243a3Snicm void try_remote(const char *, const char *); 64f391cec5Snicm 65f391cec5Snicm __dead void 66f391cec5Snicm usage(void) 67f391cec5Snicm { 68*439243a3Snicm fprintf(stderr, "usage: %s [-l line] [-s speed | -speed] [host]\n", 696ddaf43aSnicm __progname); 70f391cec5Snicm exit(1); 71f391cec5Snicm } 72f391cec5Snicm 73f391cec5Snicm int 74f391cec5Snicm main(int argc, char **argv) 75f391cec5Snicm { 76*439243a3Snicm const char *errstr; 77*439243a3Snicm char *tmp, *s; 78*439243a3Snicm int opt, i; 79f391cec5Snicm 80*439243a3Snicm if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) 81*439243a3Snicm err(1, "tcgetattr"); 82f391cec5Snicm 83f391cec5Snicm /* 84f391cec5Snicm * Convert obsolescent -### speed to modern -s### syntax which getopt() 85f391cec5Snicm * can handle. 86f391cec5Snicm */ 87f391cec5Snicm for (i = 1; i < argc; i++) { 880630af0bSdlg if (strcmp("--", argv[i]) == 0) 89f391cec5Snicm break; 90e889c1f5Sderaadt if (argv[i][0] != '-' || !isdigit((unsigned char)argv[i][1])) 910630af0bSdlg continue; 920630af0bSdlg 930630af0bSdlg if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1) 940630af0bSdlg errx(1, "speed asprintf"); 95f391cec5Snicm } 96f391cec5Snicm 97f391cec5Snicm while ((opt = getopt(argc, argv, "l:s:")) != -1) { 98f391cec5Snicm switch (opt) { 99f391cec5Snicm case 'l': 100*439243a3Snicm line_path = optarg; 101f391cec5Snicm break; 102f391cec5Snicm case 's': 103*439243a3Snicm line_speed = strtonum(optarg, 0, INT_MAX, &errstr); 104f391cec5Snicm if (errstr != NULL) 105f391cec5Snicm errx(1, "speed is %s: %s", errstr, optarg); 106f391cec5Snicm break; 107f391cec5Snicm default: 108f391cec5Snicm usage(); 109f391cec5Snicm } 110f391cec5Snicm } 111f391cec5Snicm argc -= optind; 112f391cec5Snicm argv += optind; 113*439243a3Snicm if (argc != 0 && argc != 1) 114f391cec5Snicm usage(); 115f391cec5Snicm 116*439243a3Snicm s = getenv("REMOTE"); 117*439243a3Snicm if (argc == 1) { 118*439243a3Snicm if (s != NULL && *s == '/') 119*439243a3Snicm try_remote(argv[0], s); 120*439243a3Snicm else 121*439243a3Snicm try_remote(argv[0], NULL); 122*439243a3Snicm } else if (s != NULL && *s != '/') 123*439243a3Snicm try_remote(s, NULL); 124*439243a3Snicm 125*439243a3Snicm if (line_path == NULL) 126*439243a3Snicm line_path = "/dev/cua00"; 127*439243a3Snicm if (line_speed == -1) 128*439243a3Snicm line_speed = 9600; 129*439243a3Snicm 130*439243a3Snicm if (strchr(line_path, '/') == NULL) { 131*439243a3Snicm if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1) 132f391cec5Snicm err(1, "asprintf"); 133*439243a3Snicm line_path = tmp; 134f391cec5Snicm } 135f391cec5Snicm 136*439243a3Snicm line_fd = open(line_path, O_RDWR); 137f391cec5Snicm if (line_fd < 0) 138*439243a3Snicm err(1, "open(\"%s\")", line_path); 139f391cec5Snicm if (ioctl(line_fd, TIOCEXCL) != 0) 140f391cec5Snicm err(1, "ioctl(TIOCEXCL)"); 14189ee0e62Snicm if (tcgetattr(line_fd, &line_tio) != 0) 14289ee0e62Snicm err(1, "tcgetattr"); 143*439243a3Snicm if (set_line(line_speed) != 0) 14411403328Snicm err(1, "tcsetattr"); 145f391cec5Snicm 146f391cec5Snicm event_init(); 147ca5f2657Snicm 148f391cec5Snicm signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); 149f391cec5Snicm signal_add(&sigterm_ev, NULL); 150c30d1c6aSnicm signal_set(&sighup_ev, SIGHUP, signal_event, NULL); 151c30d1c6aSnicm signal_add(&sighup_ev, NULL); 152ca5f2657Snicm if (signal(SIGINT, SIG_IGN) == SIG_ERR) 153ca5f2657Snicm err(1, "signal"); 154ca5f2657Snicm if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) 155ca5f2657Snicm err(1, "signal"); 156f391cec5Snicm 15711403328Snicm set_termios(); /* after this use cu_err and friends */ 158f391cec5Snicm 159f391cec5Snicm /* stdin and stdout get separate events */ 160f391cec5Snicm input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, 161f391cec5Snicm stream_error, NULL); 162f391cec5Snicm bufferevent_enable(input_ev, EV_READ); 163f391cec5Snicm output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, 164f391cec5Snicm NULL); 165f391cec5Snicm bufferevent_enable(output_ev, EV_WRITE); 166f391cec5Snicm 167f391cec5Snicm line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, 168f391cec5Snicm NULL); 169f391cec5Snicm bufferevent_enable(line_ev, EV_READ|EV_WRITE); 170f391cec5Snicm 171*439243a3Snicm printf("Connected to %s (speed %d)\r\n", line_path, line_speed); 172f391cec5Snicm event_dispatch(); 173f391cec5Snicm 174654f0ea4Snicm restore_termios(); 175f391cec5Snicm printf("\r\n[EOT]\n"); 176f391cec5Snicm 177f391cec5Snicm exit(0); 178f391cec5Snicm } 179f391cec5Snicm 180f391cec5Snicm void 181f391cec5Snicm signal_event(int fd, short events, void *data) 182f391cec5Snicm { 183654f0ea4Snicm restore_termios(); 184f391cec5Snicm printf("\r\n[SIG%s]\n", sys_signame[fd]); 185f391cec5Snicm 186f391cec5Snicm exit(0); 187f391cec5Snicm } 188f391cec5Snicm 189f391cec5Snicm void 190f391cec5Snicm set_termios(void) 191f391cec5Snicm { 192f391cec5Snicm struct termios tio; 193f391cec5Snicm 194f391cec5Snicm if (!isatty(STDIN_FILENO)) 195f391cec5Snicm return; 196f391cec5Snicm 197f391cec5Snicm memcpy(&tio, &saved_tio, sizeof(tio)); 198f391cec5Snicm tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); 199f391cec5Snicm tio.c_iflag &= ~(INPCK|ICRNL); 200f391cec5Snicm tio.c_oflag &= ~OPOST; 201f391cec5Snicm tio.c_cc[VMIN] = 1; 202f391cec5Snicm tio.c_cc[VTIME] = 0; 203f391cec5Snicm tio.c_cc[VDISCARD] = _POSIX_VDISABLE; 204f391cec5Snicm tio.c_cc[VDSUSP] = _POSIX_VDISABLE; 205f391cec5Snicm tio.c_cc[VINTR] = _POSIX_VDISABLE; 206f391cec5Snicm tio.c_cc[VLNEXT] = _POSIX_VDISABLE; 207f391cec5Snicm tio.c_cc[VQUIT] = _POSIX_VDISABLE; 208f391cec5Snicm tio.c_cc[VSUSP] = _POSIX_VDISABLE; 209f391cec5Snicm if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) 21011403328Snicm cu_err(1, "tcsetattr"); 211f391cec5Snicm } 212f391cec5Snicm 213f391cec5Snicm void 214f391cec5Snicm restore_termios(void) 215f391cec5Snicm { 21611403328Snicm if (isatty(STDIN_FILENO)) 21711403328Snicm tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); 218f391cec5Snicm } 219f391cec5Snicm 220909d4fcfSnicm int 221909d4fcfSnicm set_line(int speed) 222909d4fcfSnicm { 223909d4fcfSnicm struct termios tio; 224909d4fcfSnicm 22589ee0e62Snicm memcpy(&tio, &line_tio, sizeof(tio)); 22689ee0e62Snicm tio.c_iflag &= ~(ISTRIP|ICRNL); 22789ee0e62Snicm tio.c_oflag &= ~OPOST; 22889ee0e62Snicm tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO); 22989ee0e62Snicm tio.c_cflag &= ~(CSIZE|PARENB); 23089ee0e62Snicm tio.c_cflag |= CREAD|CS8|CLOCAL; 231909d4fcfSnicm tio.c_cc[VMIN] = 1; 232909d4fcfSnicm tio.c_cc[VTIME] = 0; 233909d4fcfSnicm cfsetspeed(&tio, speed); 23411403328Snicm if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) 235909d4fcfSnicm return (-1); 236909d4fcfSnicm return (0); 237909d4fcfSnicm } 238909d4fcfSnicm 239f391cec5Snicm void 240f391cec5Snicm stream_read(struct bufferevent *bufev, void *data) 241f391cec5Snicm { 242f391cec5Snicm char *new_data, *ptr; 243f391cec5Snicm size_t new_size; 244f391cec5Snicm int state_change; 245f391cec5Snicm 246f391cec5Snicm new_data = EVBUFFER_DATA(input_ev->input); 247f391cec5Snicm new_size = EVBUFFER_LENGTH(input_ev->input); 248f391cec5Snicm if (new_size == 0) 249f391cec5Snicm return; 250f391cec5Snicm 251f391cec5Snicm state_change = isatty(STDIN_FILENO); 252f391cec5Snicm for (ptr = new_data; ptr < new_data + new_size; ptr++) { 253f391cec5Snicm switch (last_state) { 254f391cec5Snicm case STATE_NONE: 255f391cec5Snicm if (state_change && *ptr == '\r') 256f391cec5Snicm last_state = STATE_NEWLINE; 257f391cec5Snicm break; 258f391cec5Snicm case STATE_NEWLINE: 259f391cec5Snicm if (state_change && *ptr == '~') { 260f391cec5Snicm last_state = STATE_TILDE; 261f391cec5Snicm continue; 262f391cec5Snicm } 263f391cec5Snicm if (*ptr != '\r') 264f391cec5Snicm last_state = STATE_NONE; 265f391cec5Snicm break; 266f391cec5Snicm case STATE_TILDE: 267f391cec5Snicm do_command(*ptr); 268f391cec5Snicm last_state = STATE_NEWLINE; 269f391cec5Snicm continue; 270f391cec5Snicm } 271f391cec5Snicm 272f391cec5Snicm bufferevent_write(line_ev, ptr, 1); 273f391cec5Snicm } 274f391cec5Snicm 275f391cec5Snicm evbuffer_drain(input_ev->input, new_size); 276f391cec5Snicm } 277f391cec5Snicm 278f391cec5Snicm void 279f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data) 280f391cec5Snicm { 281f391cec5Snicm event_loopexit(NULL); 282f391cec5Snicm } 283f391cec5Snicm 284f391cec5Snicm void 285f391cec5Snicm line_read(struct bufferevent *bufev, void *data) 286f391cec5Snicm { 287f391cec5Snicm char *new_data; 288f391cec5Snicm size_t new_size; 289f391cec5Snicm 290f391cec5Snicm new_data = EVBUFFER_DATA(line_ev->input); 291f391cec5Snicm new_size = EVBUFFER_LENGTH(line_ev->input); 292f391cec5Snicm if (new_size == 0) 293f391cec5Snicm return; 294f391cec5Snicm 29566d5e211Snicm if (record_file != NULL) 29666d5e211Snicm fwrite(new_data, 1, new_size, record_file); 297f391cec5Snicm bufferevent_write(output_ev, new_data, new_size); 298f391cec5Snicm 299f391cec5Snicm evbuffer_drain(line_ev->input, new_size); 300f391cec5Snicm } 301f391cec5Snicm 302f391cec5Snicm void 303f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data) 304f391cec5Snicm { 305f391cec5Snicm event_loopexit(NULL); 306f391cec5Snicm } 307f391cec5Snicm 308*439243a3Snicm void 309*439243a3Snicm try_remote(const char *host, const char *path) 310*439243a3Snicm { 311*439243a3Snicm const char *paths[] = { "/etc/remote", NULL, NULL }; 312*439243a3Snicm char *cp, *s; 313*439243a3Snicm long l; 314*439243a3Snicm int error; 315*439243a3Snicm 316*439243a3Snicm if (path != NULL) { 317*439243a3Snicm paths[0] = path; 318*439243a3Snicm paths[1] = "/etc/remote"; 319*439243a3Snicm } 320*439243a3Snicm 321*439243a3Snicm error = cgetent(&cp, (char**)paths, (char*)host); 322*439243a3Snicm if (error < 0) { 323*439243a3Snicm switch (error) { 324*439243a3Snicm case -1: 325*439243a3Snicm cu_errx(1, "unknown host %s", host); 326*439243a3Snicm case -2: 327*439243a3Snicm cu_errx(1, "can't open remote file"); 328*439243a3Snicm case -3: 329*439243a3Snicm cu_errx(1, "loop in remote file"); 330*439243a3Snicm default: 331*439243a3Snicm cu_errx(1, "unknown error in remote file"); 332*439243a3Snicm } 333*439243a3Snicm } 334*439243a3Snicm 335*439243a3Snicm if (line_path == NULL && cgetstr(cp, "dv", &s) >= 0) 336*439243a3Snicm line_path = s; 337*439243a3Snicm 338*439243a3Snicm if (line_speed == -1 && cgetnum(cp, "br", &l) >= 0) { 339*439243a3Snicm if (l < 0 || l > INT_MAX) 340*439243a3Snicm cu_errx(1, "speed out of range"); 341*439243a3Snicm line_speed = l; 342*439243a3Snicm } 343*439243a3Snicm } 344*439243a3Snicm 345f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */ 346f391cec5Snicm char * 347f391cec5Snicm tilde_expand(const char *filename1) 348f391cec5Snicm { 34934574ca2Stedu const char *filename, *path, *sep; 35034574ca2Stedu char user[128], *out; 351f391cec5Snicm struct passwd *pw; 352f391cec5Snicm u_int len, slash; 35334574ca2Stedu int rv; 354f391cec5Snicm 355f391cec5Snicm if (*filename1 != '~') 356f391cec5Snicm goto no_change; 357f391cec5Snicm filename = filename1 + 1; 358f391cec5Snicm 359f391cec5Snicm path = strchr(filename, '/'); 360f391cec5Snicm if (path != NULL && path > filename) { /* ~user/path */ 361f391cec5Snicm slash = path - filename; 362f391cec5Snicm if (slash > sizeof(user) - 1) 363f391cec5Snicm goto no_change; 364f391cec5Snicm memcpy(user, filename, slash); 365f391cec5Snicm user[slash] = '\0'; 366f391cec5Snicm if ((pw = getpwnam(user)) == NULL) 367f391cec5Snicm goto no_change; 368f391cec5Snicm } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ 369f391cec5Snicm goto no_change; 370f391cec5Snicm 371f391cec5Snicm /* Make sure directory has a trailing '/' */ 372f391cec5Snicm len = strlen(pw->pw_dir); 37334574ca2Stedu if (len == 0 || pw->pw_dir[len - 1] != '/') 37434574ca2Stedu sep = "/"; 37534574ca2Stedu else 37634574ca2Stedu sep = ""; 377f391cec5Snicm 378f391cec5Snicm /* Skip leading '/' from specified path */ 379f391cec5Snicm if (path != NULL) 380f391cec5Snicm filename = path + 1; 381f391cec5Snicm 38234574ca2Stedu if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1) 38334574ca2Stedu cu_err(1, "asprintf"); 38434574ca2Stedu if (rv >= MAXPATHLEN) { 38534574ca2Stedu free(out); 38634574ca2Stedu goto no_change; 38734574ca2Stedu } 38834574ca2Stedu 389f391cec5Snicm return (out); 390f391cec5Snicm 391f391cec5Snicm no_change: 392f391cec5Snicm out = strdup(filename1); 393f391cec5Snicm if (out == NULL) 39411403328Snicm cu_err(1, "strdup"); 395f391cec5Snicm return (out); 396f391cec5Snicm } 397