1*89ee0e62Snicm /* $OpenBSD: cu.c,v 1.12 2013/01/17 11:15:22 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; 44f391cec5Snicm int line_fd; 45*89ee0e62Snicm struct termios line_tio; 46f391cec5Snicm struct bufferevent *line_ev; 47f391cec5Snicm struct event sigterm_ev; 48c30d1c6aSnicm struct event sighup_ev; 49f391cec5Snicm enum { 50f391cec5Snicm STATE_NONE, 51f391cec5Snicm STATE_NEWLINE, 52f391cec5Snicm STATE_TILDE 53f391cec5Snicm } last_state = STATE_NEWLINE; 54f391cec5Snicm 55f391cec5Snicm __dead void usage(void); 56f391cec5Snicm void signal_event(int, short, void *); 57f391cec5Snicm void stream_read(struct bufferevent *, void *); 58f391cec5Snicm void stream_error(struct bufferevent *, short, void *); 59f391cec5Snicm void line_read(struct bufferevent *, void *); 60f391cec5Snicm void line_error(struct bufferevent *, short, void *); 61f391cec5Snicm 62f391cec5Snicm __dead void 63f391cec5Snicm usage(void) 64f391cec5Snicm { 656ddaf43aSnicm fprintf(stderr, "usage: %s [-l line] [-s speed | -speed]\n", 666ddaf43aSnicm __progname); 67f391cec5Snicm exit(1); 68f391cec5Snicm } 69f391cec5Snicm 70f391cec5Snicm int 71f391cec5Snicm main(int argc, char **argv) 72f391cec5Snicm { 73f391cec5Snicm const char *line, *errstr; 74f391cec5Snicm char *tmp; 75023045f1Shalex int opt, speed, i; 76f391cec5Snicm 77f391cec5Snicm line = "/dev/cua00"; 78f391cec5Snicm speed = 9600; 79f391cec5Snicm 80f391cec5Snicm /* 81f391cec5Snicm * Convert obsolescent -### speed to modern -s### syntax which getopt() 82f391cec5Snicm * can handle. 83f391cec5Snicm */ 84f391cec5Snicm for (i = 1; i < argc; i++) { 850630af0bSdlg if (strcmp("--", argv[i]) == 0) 86f391cec5Snicm break; 870630af0bSdlg if (argv[i][0] != '-' || !isdigit(argv[i][1])) 880630af0bSdlg continue; 890630af0bSdlg 900630af0bSdlg if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1) 910630af0bSdlg errx(1, "speed asprintf"); 92f391cec5Snicm } 93f391cec5Snicm 94f391cec5Snicm while ((opt = getopt(argc, argv, "l:s:")) != -1) { 95f391cec5Snicm switch (opt) { 96f391cec5Snicm case 'l': 97f391cec5Snicm line = optarg; 98f391cec5Snicm break; 99f391cec5Snicm case 's': 100f391cec5Snicm speed = strtonum(optarg, 0, UINT_MAX, &errstr); 101f391cec5Snicm if (errstr != NULL) 102f391cec5Snicm errx(1, "speed is %s: %s", errstr, optarg); 103f391cec5Snicm break; 104f391cec5Snicm default: 105f391cec5Snicm usage(); 106f391cec5Snicm } 107f391cec5Snicm } 108f391cec5Snicm argc -= optind; 109f391cec5Snicm argv += optind; 110f391cec5Snicm if (argc != 0) 111f391cec5Snicm usage(); 112f391cec5Snicm 113f391cec5Snicm if (strchr(line, '/') == NULL) { 114f391cec5Snicm if (asprintf(&tmp, "%s%s", _PATH_DEV, line) == -1) 115f391cec5Snicm err(1, "asprintf"); 116f391cec5Snicm line = tmp; 117f391cec5Snicm } 118f391cec5Snicm 119f391cec5Snicm line_fd = open(line, O_RDWR); 120f391cec5Snicm if (line_fd < 0) 121f391cec5Snicm err(1, "open(\"%s\")", line); 122f391cec5Snicm if (ioctl(line_fd, TIOCEXCL) != 0) 123f391cec5Snicm err(1, "ioctl(TIOCEXCL)"); 124*89ee0e62Snicm if (tcgetattr(line_fd, &line_tio) != 0) 125*89ee0e62Snicm err(1, "tcgetattr"); 126909d4fcfSnicm if (set_line(speed) != 0) 12711403328Snicm err(1, "tcsetattr"); 128f391cec5Snicm 129f391cec5Snicm if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) 130f391cec5Snicm err(1, "tcgetattr"); 131f391cec5Snicm 132f391cec5Snicm event_init(); 133ca5f2657Snicm 134f391cec5Snicm signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); 135f391cec5Snicm signal_add(&sigterm_ev, NULL); 136c30d1c6aSnicm signal_set(&sighup_ev, SIGHUP, signal_event, NULL); 137c30d1c6aSnicm signal_add(&sighup_ev, NULL); 138ca5f2657Snicm if (signal(SIGINT, SIG_IGN) == SIG_ERR) 139ca5f2657Snicm err(1, "signal"); 140ca5f2657Snicm if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) 141ca5f2657Snicm err(1, "signal"); 142f391cec5Snicm 14311403328Snicm set_termios(); /* after this use cu_err and friends */ 144f391cec5Snicm 145f391cec5Snicm /* stdin and stdout get separate events */ 146f391cec5Snicm input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, 147f391cec5Snicm stream_error, NULL); 148f391cec5Snicm bufferevent_enable(input_ev, EV_READ); 149f391cec5Snicm output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, 150f391cec5Snicm NULL); 151f391cec5Snicm bufferevent_enable(output_ev, EV_WRITE); 152f391cec5Snicm 153f391cec5Snicm line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, 154f391cec5Snicm NULL); 155f391cec5Snicm bufferevent_enable(line_ev, EV_READ|EV_WRITE); 156f391cec5Snicm 157f391cec5Snicm printf("Connected (speed %u)\r\n", speed); 158f391cec5Snicm event_dispatch(); 159f391cec5Snicm 160654f0ea4Snicm restore_termios(); 161f391cec5Snicm printf("\r\n[EOT]\n"); 162f391cec5Snicm 163f391cec5Snicm exit(0); 164f391cec5Snicm } 165f391cec5Snicm 166f391cec5Snicm void 167f391cec5Snicm signal_event(int fd, short events, void *data) 168f391cec5Snicm { 169654f0ea4Snicm restore_termios(); 170f391cec5Snicm printf("\r\n[SIG%s]\n", sys_signame[fd]); 171f391cec5Snicm 172f391cec5Snicm exit(0); 173f391cec5Snicm } 174f391cec5Snicm 175f391cec5Snicm void 176f391cec5Snicm set_termios(void) 177f391cec5Snicm { 178f391cec5Snicm struct termios tio; 179f391cec5Snicm 180f391cec5Snicm if (!isatty(STDIN_FILENO)) 181f391cec5Snicm return; 182f391cec5Snicm 183f391cec5Snicm memcpy(&tio, &saved_tio, sizeof(tio)); 184f391cec5Snicm tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); 185f391cec5Snicm tio.c_iflag &= ~(INPCK|ICRNL); 186f391cec5Snicm tio.c_oflag &= ~OPOST; 187f391cec5Snicm tio.c_cc[VMIN] = 1; 188f391cec5Snicm tio.c_cc[VTIME] = 0; 189f391cec5Snicm tio.c_cc[VDISCARD] = _POSIX_VDISABLE; 190f391cec5Snicm tio.c_cc[VDSUSP] = _POSIX_VDISABLE; 191f391cec5Snicm tio.c_cc[VINTR] = _POSIX_VDISABLE; 192f391cec5Snicm tio.c_cc[VLNEXT] = _POSIX_VDISABLE; 193f391cec5Snicm tio.c_cc[VQUIT] = _POSIX_VDISABLE; 194f391cec5Snicm tio.c_cc[VSUSP] = _POSIX_VDISABLE; 195f391cec5Snicm if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) 19611403328Snicm cu_err(1, "tcsetattr"); 197f391cec5Snicm } 198f391cec5Snicm 199f391cec5Snicm void 200f391cec5Snicm restore_termios(void) 201f391cec5Snicm { 20211403328Snicm if (isatty(STDIN_FILENO)) 20311403328Snicm tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); 204f391cec5Snicm } 205f391cec5Snicm 206909d4fcfSnicm int 207909d4fcfSnicm set_line(int speed) 208909d4fcfSnicm { 209909d4fcfSnicm struct termios tio; 210909d4fcfSnicm 211*89ee0e62Snicm memcpy(&tio, &line_tio, sizeof(tio)); 212*89ee0e62Snicm tio.c_iflag &= ~(ISTRIP|ICRNL); 213*89ee0e62Snicm tio.c_oflag &= ~OPOST; 214*89ee0e62Snicm tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO); 215*89ee0e62Snicm tio.c_cflag &= ~(CSIZE|PARENB); 216*89ee0e62Snicm tio.c_cflag |= CREAD|CS8|CLOCAL; 217909d4fcfSnicm tio.c_cc[VMIN] = 1; 218909d4fcfSnicm tio.c_cc[VTIME] = 0; 219909d4fcfSnicm cfsetspeed(&tio, speed); 22011403328Snicm if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) 221909d4fcfSnicm return (-1); 222909d4fcfSnicm return (0); 223909d4fcfSnicm } 224909d4fcfSnicm 225f391cec5Snicm void 226f391cec5Snicm stream_read(struct bufferevent *bufev, void *data) 227f391cec5Snicm { 228f391cec5Snicm char *new_data, *ptr; 229f391cec5Snicm size_t new_size; 230f391cec5Snicm int state_change; 231f391cec5Snicm 232f391cec5Snicm new_data = EVBUFFER_DATA(input_ev->input); 233f391cec5Snicm new_size = EVBUFFER_LENGTH(input_ev->input); 234f391cec5Snicm if (new_size == 0) 235f391cec5Snicm return; 236f391cec5Snicm 237f391cec5Snicm state_change = isatty(STDIN_FILENO); 238f391cec5Snicm for (ptr = new_data; ptr < new_data + new_size; ptr++) { 239f391cec5Snicm switch (last_state) { 240f391cec5Snicm case STATE_NONE: 241f391cec5Snicm if (state_change && *ptr == '\r') 242f391cec5Snicm last_state = STATE_NEWLINE; 243f391cec5Snicm break; 244f391cec5Snicm case STATE_NEWLINE: 245f391cec5Snicm if (state_change && *ptr == '~') { 246f391cec5Snicm last_state = STATE_TILDE; 247f391cec5Snicm continue; 248f391cec5Snicm } 249f391cec5Snicm if (*ptr != '\r') 250f391cec5Snicm last_state = STATE_NONE; 251f391cec5Snicm break; 252f391cec5Snicm case STATE_TILDE: 253f391cec5Snicm do_command(*ptr); 254f391cec5Snicm last_state = STATE_NEWLINE; 255f391cec5Snicm continue; 256f391cec5Snicm } 257f391cec5Snicm 258f391cec5Snicm bufferevent_write(line_ev, ptr, 1); 259f391cec5Snicm } 260f391cec5Snicm 261f391cec5Snicm evbuffer_drain(input_ev->input, new_size); 262f391cec5Snicm } 263f391cec5Snicm 264f391cec5Snicm void 265f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data) 266f391cec5Snicm { 267f391cec5Snicm event_loopexit(NULL); 268f391cec5Snicm } 269f391cec5Snicm 270f391cec5Snicm void 271f391cec5Snicm line_read(struct bufferevent *bufev, void *data) 272f391cec5Snicm { 273f391cec5Snicm char *new_data; 274f391cec5Snicm size_t new_size; 275f391cec5Snicm 276f391cec5Snicm new_data = EVBUFFER_DATA(line_ev->input); 277f391cec5Snicm new_size = EVBUFFER_LENGTH(line_ev->input); 278f391cec5Snicm if (new_size == 0) 279f391cec5Snicm return; 280f391cec5Snicm 28166d5e211Snicm if (record_file != NULL) 28266d5e211Snicm fwrite(new_data, 1, new_size, record_file); 283f391cec5Snicm bufferevent_write(output_ev, new_data, new_size); 284f391cec5Snicm 285f391cec5Snicm evbuffer_drain(line_ev->input, new_size); 286f391cec5Snicm } 287f391cec5Snicm 288f391cec5Snicm void 289f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data) 290f391cec5Snicm { 291f391cec5Snicm event_loopexit(NULL); 292f391cec5Snicm } 293f391cec5Snicm 294f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */ 295f391cec5Snicm char * 296f391cec5Snicm tilde_expand(const char *filename1) 297f391cec5Snicm { 298f391cec5Snicm const char *filename, *path; 299f391cec5Snicm char user[128], ret[MAXPATHLEN], *out; 300f391cec5Snicm struct passwd *pw; 301f391cec5Snicm u_int len, slash; 302f391cec5Snicm 303f391cec5Snicm if (*filename1 != '~') 304f391cec5Snicm goto no_change; 305f391cec5Snicm filename = filename1 + 1; 306f391cec5Snicm 307f391cec5Snicm path = strchr(filename, '/'); 308f391cec5Snicm if (path != NULL && path > filename) { /* ~user/path */ 309f391cec5Snicm slash = path - filename; 310f391cec5Snicm if (slash > sizeof(user) - 1) 311f391cec5Snicm goto no_change; 312f391cec5Snicm memcpy(user, filename, slash); 313f391cec5Snicm user[slash] = '\0'; 314f391cec5Snicm if ((pw = getpwnam(user)) == NULL) 315f391cec5Snicm goto no_change; 316f391cec5Snicm } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ 317f391cec5Snicm goto no_change; 318f391cec5Snicm 319f391cec5Snicm if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret)) 320f391cec5Snicm goto no_change; 321f391cec5Snicm 322f391cec5Snicm /* Make sure directory has a trailing '/' */ 323f391cec5Snicm len = strlen(pw->pw_dir); 324f391cec5Snicm if ((len == 0 || pw->pw_dir[len - 1] != '/') && 325f391cec5Snicm strlcat(ret, "/", sizeof(ret)) >= sizeof(ret)) 326f391cec5Snicm goto no_change; 327f391cec5Snicm 328f391cec5Snicm /* Skip leading '/' from specified path */ 329f391cec5Snicm if (path != NULL) 330f391cec5Snicm filename = path + 1; 331f391cec5Snicm if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret)) 332f391cec5Snicm goto no_change; 333f391cec5Snicm 334f391cec5Snicm out = strdup(ret); 335f391cec5Snicm if (out == NULL) 33611403328Snicm cu_err(1, "strdup"); 337f391cec5Snicm return (out); 338f391cec5Snicm 339f391cec5Snicm no_change: 340f391cec5Snicm out = strdup(filename1); 341f391cec5Snicm if (out == NULL) 34211403328Snicm cu_err(1, "strdup"); 343f391cec5Snicm return (out); 344f391cec5Snicm } 345