1*6ddaf43aSnicm /* $OpenBSD: cu.c,v 1.7 2012/07/10 13:00:09 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 22f391cec5Snicm #include <err.h> 23f391cec5Snicm #include <event.h> 24f391cec5Snicm #include <fcntl.h> 25f391cec5Snicm #include <getopt.h> 26f391cec5Snicm #include <paths.h> 27f391cec5Snicm #include <pwd.h> 28f391cec5Snicm #include <signal.h> 29f391cec5Snicm #include <stdio.h> 30f391cec5Snicm #include <stdlib.h> 31f391cec5Snicm #include <string.h> 32f391cec5Snicm #include <termios.h> 33f391cec5Snicm #include <unistd.h> 34f391cec5Snicm 35f391cec5Snicm #include "cu.h" 36f391cec5Snicm 37f391cec5Snicm extern char *__progname; 38f391cec5Snicm 3966d5e211Snicm FILE *record_file; 40f391cec5Snicm struct termios saved_tio; 41f391cec5Snicm struct bufferevent *input_ev; 42f391cec5Snicm struct bufferevent *output_ev; 43f391cec5Snicm int line_fd; 44f391cec5Snicm struct bufferevent *line_ev; 45f391cec5Snicm struct event sigterm_ev; 46f391cec5Snicm enum { 47f391cec5Snicm STATE_NONE, 48f391cec5Snicm STATE_NEWLINE, 49f391cec5Snicm STATE_TILDE 50f391cec5Snicm } last_state = STATE_NEWLINE; 51f391cec5Snicm 52f391cec5Snicm __dead void usage(void); 53f391cec5Snicm void signal_event(int, short, void *); 54f391cec5Snicm void stream_read(struct bufferevent *, void *); 55f391cec5Snicm void stream_error(struct bufferevent *, short, void *); 56f391cec5Snicm void line_read(struct bufferevent *, void *); 57f391cec5Snicm void line_error(struct bufferevent *, short, void *); 58f391cec5Snicm 59f391cec5Snicm __dead void 60f391cec5Snicm usage(void) 61f391cec5Snicm { 62*6ddaf43aSnicm fprintf(stderr, "usage: %s [-l line] [-s speed | -speed]\n", 63*6ddaf43aSnicm __progname); 64f391cec5Snicm exit(1); 65f391cec5Snicm } 66f391cec5Snicm 67f391cec5Snicm int 68f391cec5Snicm main(int argc, char **argv) 69f391cec5Snicm { 70f391cec5Snicm const char *line, *errstr; 71f391cec5Snicm char *tmp; 72f391cec5Snicm int opt, speed, i, ch; 73f391cec5Snicm static char sbuf[12]; 74f391cec5Snicm 75f391cec5Snicm line = "/dev/cua00"; 76f391cec5Snicm speed = 9600; 77f391cec5Snicm 78f391cec5Snicm /* 79f391cec5Snicm * Convert obsolescent -### speed to modern -s### syntax which getopt() 80f391cec5Snicm * can handle. 81f391cec5Snicm */ 82f391cec5Snicm for (i = 1; i < argc; i++) { 83f391cec5Snicm if (argv[i][0] == '-') { 84f391cec5Snicm switch (argv[i][1]) { 85f391cec5Snicm case '0': case '1': case '2': case '3': case '4': 86f391cec5Snicm case '5': case '6': case '7': case '8': case '9': 87f391cec5Snicm ch = snprintf(sbuf, sizeof(sbuf), "-s%s", 88f391cec5Snicm &argv[i][1]); 89f391cec5Snicm if (ch <= 0 || ch >= (int)sizeof(sbuf)) { 90f391cec5Snicm errx(1, "invalid speed: %s", 91f391cec5Snicm &argv[i][1]); 92f391cec5Snicm } 93f391cec5Snicm argv[i] = sbuf; 94f391cec5Snicm break; 95f391cec5Snicm case '-': 96f391cec5Snicm /* if we get "--" stop processing args */ 97f391cec5Snicm if (argv[i][2] == '\0') 98f391cec5Snicm goto getopt; 99f391cec5Snicm break; 100f391cec5Snicm } 101f391cec5Snicm } 102f391cec5Snicm } 103f391cec5Snicm 104f391cec5Snicm getopt: 105f391cec5Snicm while ((opt = getopt(argc, argv, "l:s:")) != -1) { 106f391cec5Snicm switch (opt) { 107f391cec5Snicm case 'l': 108f391cec5Snicm line = optarg; 109f391cec5Snicm break; 110f391cec5Snicm case 's': 111f391cec5Snicm speed = strtonum(optarg, 0, UINT_MAX, &errstr); 112f391cec5Snicm if (errstr != NULL) 113f391cec5Snicm errx(1, "speed is %s: %s", errstr, optarg); 114f391cec5Snicm break; 115f391cec5Snicm default: 116f391cec5Snicm usage(); 117f391cec5Snicm } 118f391cec5Snicm } 119f391cec5Snicm argc -= optind; 120f391cec5Snicm argv += optind; 121f391cec5Snicm if (argc != 0) 122f391cec5Snicm usage(); 123f391cec5Snicm 124f391cec5Snicm if (strchr(line, '/') == NULL) { 125f391cec5Snicm if (asprintf(&tmp, "%s%s", _PATH_DEV, line) == -1) 126f391cec5Snicm err(1, "asprintf"); 127f391cec5Snicm line = tmp; 128f391cec5Snicm } 129f391cec5Snicm 130f391cec5Snicm line_fd = open(line, O_RDWR); 131f391cec5Snicm if (line_fd < 0) 132f391cec5Snicm err(1, "open(\"%s\")", line); 133f391cec5Snicm if (ioctl(line_fd, TIOCEXCL) != 0) 134f391cec5Snicm err(1, "ioctl(TIOCEXCL)"); 135f391cec5Snicm 136909d4fcfSnicm if (set_line(speed) != 0) 13711403328Snicm err(1, "tcsetattr"); 138f391cec5Snicm 139f391cec5Snicm if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) 140f391cec5Snicm err(1, "tcgetattr"); 141f391cec5Snicm 142f391cec5Snicm event_init(); 143ca5f2657Snicm 144f391cec5Snicm signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); 145f391cec5Snicm signal_add(&sigterm_ev, NULL); 146ca5f2657Snicm if (signal(SIGINT, SIG_IGN) == SIG_ERR) 147ca5f2657Snicm err(1, "signal"); 148ca5f2657Snicm if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) 149ca5f2657Snicm err(1, "signal"); 150f391cec5Snicm 15111403328Snicm set_termios(); /* after this use cu_err and friends */ 152f391cec5Snicm 153f391cec5Snicm /* stdin and stdout get separate events */ 154f391cec5Snicm input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, 155f391cec5Snicm stream_error, NULL); 156f391cec5Snicm bufferevent_enable(input_ev, EV_READ); 157f391cec5Snicm output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, 158f391cec5Snicm NULL); 159f391cec5Snicm bufferevent_enable(output_ev, EV_WRITE); 160f391cec5Snicm 161f391cec5Snicm line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, 162f391cec5Snicm NULL); 163f391cec5Snicm bufferevent_enable(line_ev, EV_READ|EV_WRITE); 164f391cec5Snicm 165f391cec5Snicm printf("Connected (speed %u)\r\n", speed); 166f391cec5Snicm event_dispatch(); 167f391cec5Snicm 168654f0ea4Snicm restore_termios(); 169f391cec5Snicm printf("\r\n[EOT]\n"); 170f391cec5Snicm 171f391cec5Snicm exit(0); 172f391cec5Snicm } 173f391cec5Snicm 174f391cec5Snicm void 175f391cec5Snicm signal_event(int fd, short events, void *data) 176f391cec5Snicm { 177654f0ea4Snicm restore_termios(); 178f391cec5Snicm printf("\r\n[SIG%s]\n", sys_signame[fd]); 179f391cec5Snicm 180f391cec5Snicm exit(0); 181f391cec5Snicm } 182f391cec5Snicm 183f391cec5Snicm void 184f391cec5Snicm set_termios(void) 185f391cec5Snicm { 186f391cec5Snicm struct termios tio; 187f391cec5Snicm 188f391cec5Snicm if (!isatty(STDIN_FILENO)) 189f391cec5Snicm return; 190f391cec5Snicm 191f391cec5Snicm memcpy(&tio, &saved_tio, sizeof(tio)); 192f391cec5Snicm tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); 193f391cec5Snicm tio.c_iflag &= ~(INPCK|ICRNL); 194f391cec5Snicm tio.c_oflag &= ~OPOST; 195f391cec5Snicm tio.c_cc[VMIN] = 1; 196f391cec5Snicm tio.c_cc[VTIME] = 0; 197f391cec5Snicm tio.c_cc[VDISCARD] = _POSIX_VDISABLE; 198f391cec5Snicm tio.c_cc[VDSUSP] = _POSIX_VDISABLE; 199f391cec5Snicm tio.c_cc[VINTR] = _POSIX_VDISABLE; 200f391cec5Snicm tio.c_cc[VLNEXT] = _POSIX_VDISABLE; 201f391cec5Snicm tio.c_cc[VQUIT] = _POSIX_VDISABLE; 202f391cec5Snicm tio.c_cc[VSUSP] = _POSIX_VDISABLE; 203f391cec5Snicm if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) 20411403328Snicm cu_err(1, "tcsetattr"); 205f391cec5Snicm } 206f391cec5Snicm 207f391cec5Snicm void 208f391cec5Snicm restore_termios(void) 209f391cec5Snicm { 21011403328Snicm if (isatty(STDIN_FILENO)) 21111403328Snicm tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); 212f391cec5Snicm } 213f391cec5Snicm 214909d4fcfSnicm int 215909d4fcfSnicm set_line(int speed) 216909d4fcfSnicm { 217909d4fcfSnicm struct termios tio; 218909d4fcfSnicm 219909d4fcfSnicm cfmakeraw(&tio); 220909d4fcfSnicm tio.c_iflag = 0; 221909d4fcfSnicm tio.c_oflag = 0; 222909d4fcfSnicm tio.c_lflag = 0; 223909d4fcfSnicm tio.c_cflag = CREAD|CS8; 224909d4fcfSnicm tio.c_cc[VMIN] = 1; 225909d4fcfSnicm tio.c_cc[VTIME] = 0; 226909d4fcfSnicm cfsetspeed(&tio, speed); 22711403328Snicm if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) 228909d4fcfSnicm return (-1); 229909d4fcfSnicm return (0); 230909d4fcfSnicm } 231909d4fcfSnicm 232f391cec5Snicm void 233f391cec5Snicm stream_read(struct bufferevent *bufev, void *data) 234f391cec5Snicm { 235f391cec5Snicm char *new_data, *ptr; 236f391cec5Snicm size_t new_size; 237f391cec5Snicm int state_change; 238f391cec5Snicm 239f391cec5Snicm new_data = EVBUFFER_DATA(input_ev->input); 240f391cec5Snicm new_size = EVBUFFER_LENGTH(input_ev->input); 241f391cec5Snicm if (new_size == 0) 242f391cec5Snicm return; 243f391cec5Snicm 244f391cec5Snicm state_change = isatty(STDIN_FILENO); 245f391cec5Snicm for (ptr = new_data; ptr < new_data + new_size; ptr++) { 246f391cec5Snicm switch (last_state) { 247f391cec5Snicm case STATE_NONE: 248f391cec5Snicm if (state_change && *ptr == '\r') 249f391cec5Snicm last_state = STATE_NEWLINE; 250f391cec5Snicm break; 251f391cec5Snicm case STATE_NEWLINE: 252f391cec5Snicm if (state_change && *ptr == '~') { 253f391cec5Snicm last_state = STATE_TILDE; 254f391cec5Snicm continue; 255f391cec5Snicm } 256f391cec5Snicm if (*ptr != '\r') 257f391cec5Snicm last_state = STATE_NONE; 258f391cec5Snicm break; 259f391cec5Snicm case STATE_TILDE: 260f391cec5Snicm do_command(*ptr); 261f391cec5Snicm last_state = STATE_NEWLINE; 262f391cec5Snicm continue; 263f391cec5Snicm } 264f391cec5Snicm 265f391cec5Snicm bufferevent_write(line_ev, ptr, 1); 266f391cec5Snicm } 267f391cec5Snicm 268f391cec5Snicm evbuffer_drain(input_ev->input, new_size); 269f391cec5Snicm } 270f391cec5Snicm 271f391cec5Snicm void 272f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data) 273f391cec5Snicm { 274f391cec5Snicm event_loopexit(NULL); 275f391cec5Snicm } 276f391cec5Snicm 277f391cec5Snicm void 278f391cec5Snicm line_read(struct bufferevent *bufev, void *data) 279f391cec5Snicm { 280f391cec5Snicm char *new_data; 281f391cec5Snicm size_t new_size; 282f391cec5Snicm 283f391cec5Snicm new_data = EVBUFFER_DATA(line_ev->input); 284f391cec5Snicm new_size = EVBUFFER_LENGTH(line_ev->input); 285f391cec5Snicm if (new_size == 0) 286f391cec5Snicm return; 287f391cec5Snicm 28866d5e211Snicm if (record_file != NULL) 28966d5e211Snicm fwrite(new_data, 1, new_size, record_file); 290f391cec5Snicm bufferevent_write(output_ev, new_data, new_size); 291f391cec5Snicm 292f391cec5Snicm evbuffer_drain(line_ev->input, new_size); 293f391cec5Snicm } 294f391cec5Snicm 295f391cec5Snicm void 296f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data) 297f391cec5Snicm { 298f391cec5Snicm event_loopexit(NULL); 299f391cec5Snicm } 300f391cec5Snicm 301f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */ 302f391cec5Snicm char * 303f391cec5Snicm tilde_expand(const char *filename1) 304f391cec5Snicm { 305f391cec5Snicm const char *filename, *path; 306f391cec5Snicm char user[128], ret[MAXPATHLEN], *out; 307f391cec5Snicm struct passwd *pw; 308f391cec5Snicm u_int len, slash; 309f391cec5Snicm 310f391cec5Snicm if (*filename1 != '~') 311f391cec5Snicm goto no_change; 312f391cec5Snicm filename = filename1 + 1; 313f391cec5Snicm 314f391cec5Snicm path = strchr(filename, '/'); 315f391cec5Snicm if (path != NULL && path > filename) { /* ~user/path */ 316f391cec5Snicm slash = path - filename; 317f391cec5Snicm if (slash > sizeof(user) - 1) 318f391cec5Snicm goto no_change; 319f391cec5Snicm memcpy(user, filename, slash); 320f391cec5Snicm user[slash] = '\0'; 321f391cec5Snicm if ((pw = getpwnam(user)) == NULL) 322f391cec5Snicm goto no_change; 323f391cec5Snicm } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ 324f391cec5Snicm goto no_change; 325f391cec5Snicm 326f391cec5Snicm if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret)) 327f391cec5Snicm goto no_change; 328f391cec5Snicm 329f391cec5Snicm /* Make sure directory has a trailing '/' */ 330f391cec5Snicm len = strlen(pw->pw_dir); 331f391cec5Snicm if ((len == 0 || pw->pw_dir[len - 1] != '/') && 332f391cec5Snicm strlcat(ret, "/", sizeof(ret)) >= sizeof(ret)) 333f391cec5Snicm goto no_change; 334f391cec5Snicm 335f391cec5Snicm /* Skip leading '/' from specified path */ 336f391cec5Snicm if (path != NULL) 337f391cec5Snicm filename = path + 1; 338f391cec5Snicm if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret)) 339f391cec5Snicm goto no_change; 340f391cec5Snicm 341f391cec5Snicm out = strdup(ret); 342f391cec5Snicm if (out == NULL) 34311403328Snicm cu_err(1, "strdup"); 344f391cec5Snicm return (out); 345f391cec5Snicm 346f391cec5Snicm no_change: 347f391cec5Snicm out = strdup(filename1); 348f391cec5Snicm if (out == NULL) 34911403328Snicm cu_err(1, "strdup"); 350f391cec5Snicm return (out); 351f391cec5Snicm } 352