/* $OpenBSD: cu.c,v 1.3 2012/07/10 08:42:43 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cu.h" extern char *__progname; struct termios saved_tio; struct bufferevent *input_ev; struct bufferevent *output_ev; int line_fd; struct bufferevent *line_ev; struct event sigterm_ev; enum { STATE_NONE, STATE_NEWLINE, STATE_TILDE } last_state = STATE_NEWLINE; __dead void usage(void); void signal_event(int, short, void *); void stream_read(struct bufferevent *, void *); void stream_error(struct bufferevent *, short, void *); void line_read(struct bufferevent *, void *); void line_error(struct bufferevent *, short, void *); __dead void usage(void) { fprintf(stderr, "usage: %s [-l line] [-s speed]\n", __progname); exit(1); } int main(int argc, char **argv) { const char *line, *errstr; char *tmp; int opt, speed, i, ch; static char sbuf[12]; line = "/dev/cua00"; speed = 9600; /* * Convert obsolescent -### speed to modern -s### syntax which getopt() * can handle. */ for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ch = snprintf(sbuf, sizeof(sbuf), "-s%s", &argv[i][1]); if (ch <= 0 || ch >= (int)sizeof(sbuf)) { errx(1, "invalid speed: %s", &argv[i][1]); } argv[i] = sbuf; break; case '-': /* if we get "--" stop processing args */ if (argv[i][2] == '\0') goto getopt; break; } } } getopt: while ((opt = getopt(argc, argv, "l:s:")) != -1) { switch (opt) { case 'l': line = optarg; break; case 's': speed = strtonum(optarg, 0, UINT_MAX, &errstr); if (errstr != NULL) errx(1, "speed is %s: %s", errstr, optarg); break; default: usage(); } } argc -= optind; argv += optind; if (argc != 0) usage(); if (strchr(line, '/') == NULL) { if (asprintf(&tmp, "%s%s", _PATH_DEV, line) == -1) err(1, "asprintf"); line = tmp; } line_fd = open(line, O_RDWR); if (line_fd < 0) err(1, "open(\"%s\")", line); if (ioctl(line_fd, TIOCEXCL) != 0) err(1, "ioctl(TIOCEXCL)"); if (set_line(speed) != 0) exit(1); if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) err(1, "tcgetattr"); event_init(); signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); signal_add(&sigterm_ev, NULL); if (signal(SIGINT, SIG_IGN) == SIG_ERR) err(1, "signal"); if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) err(1, "signal"); set_termios(); /* stdin and stdout get separate events */ input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, stream_error, NULL); bufferevent_enable(input_ev, EV_READ); output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, NULL); bufferevent_enable(output_ev, EV_WRITE); line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, NULL); bufferevent_enable(line_ev, EV_READ|EV_WRITE); printf("Connected (speed %u)\r\n", speed); event_dispatch(); if (isatty(STDIN_FILENO)) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); printf("\r\n[EOT]\n"); exit(0); } void signal_event(int fd, short events, void *data) { if (isatty(STDIN_FILENO)) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); printf("\r\n[SIG%s]\n", sys_signame[fd]); exit(0); } void set_termios(void) { struct termios tio; if (!isatty(STDIN_FILENO)) return; memcpy(&tio, &saved_tio, sizeof(tio)); tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); tio.c_iflag &= ~(INPCK|ICRNL); tio.c_oflag &= ~OPOST; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; tio.c_cc[VDISCARD] = _POSIX_VDISABLE; tio.c_cc[VDSUSP] = _POSIX_VDISABLE; tio.c_cc[VINTR] = _POSIX_VDISABLE; tio.c_cc[VLNEXT] = _POSIX_VDISABLE; tio.c_cc[VQUIT] = _POSIX_VDISABLE; tio.c_cc[VSUSP] = _POSIX_VDISABLE; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) err(1, "tcsetattr"); } void restore_termios(void) { if (!isatty(STDIN_FILENO)) return; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio) != 0) err(1, "tcsetattr"); } int set_line(int speed) { struct termios tio; cfmakeraw(&tio); tio.c_iflag = 0; tio.c_oflag = 0; tio.c_lflag = 0; tio.c_cflag = CREAD|CS8; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; cfsetspeed(&tio, speed); if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) { warn("tcsetattr"); return (-1); } return (0); } void stream_read(struct bufferevent *bufev, void *data) { char *new_data, *ptr; size_t new_size; int state_change; new_data = EVBUFFER_DATA(input_ev->input); new_size = EVBUFFER_LENGTH(input_ev->input); if (new_size == 0) return; state_change = isatty(STDIN_FILENO); for (ptr = new_data; ptr < new_data + new_size; ptr++) { switch (last_state) { case STATE_NONE: if (state_change && *ptr == '\r') last_state = STATE_NEWLINE; break; case STATE_NEWLINE: if (state_change && *ptr == '~') { last_state = STATE_TILDE; continue; } if (*ptr != '\r') last_state = STATE_NONE; break; case STATE_TILDE: do_command(*ptr); last_state = STATE_NEWLINE; continue; } bufferevent_write(line_ev, ptr, 1); } evbuffer_drain(input_ev->input, new_size); } void stream_error(struct bufferevent *bufev, short what, void *data) { event_loopexit(NULL); } void line_read(struct bufferevent *bufev, void *data) { char *new_data; size_t new_size; new_data = EVBUFFER_DATA(line_ev->input); new_size = EVBUFFER_LENGTH(line_ev->input); if (new_size == 0) return; bufferevent_write(output_ev, new_data, new_size); evbuffer_drain(line_ev->input, new_size); } void line_error(struct bufferevent *bufev, short what, void *data) { event_loopexit(NULL); } /* Expands tildes in the file name. Based on code from ssh/misc.c. */ char * tilde_expand(const char *filename1) { const char *filename, *path; char user[128], ret[MAXPATHLEN], *out; struct passwd *pw; u_int len, slash; if (*filename1 != '~') goto no_change; filename = filename1 + 1; path = strchr(filename, '/'); if (path != NULL && path > filename) { /* ~user/path */ slash = path - filename; if (slash > sizeof(user) - 1) goto no_change; memcpy(user, filename, slash); user[slash] = '\0'; if ((pw = getpwnam(user)) == NULL) goto no_change; } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ goto no_change; if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret)) goto no_change; /* Make sure directory has a trailing '/' */ len = strlen(pw->pw_dir); if ((len == 0 || pw->pw_dir[len - 1] != '/') && strlcat(ret, "/", sizeof(ret)) >= sizeof(ret)) goto no_change; /* Skip leading '/' from specified path */ if (path != NULL) filename = path + 1; if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret)) goto no_change; out = strdup(ret); if (out == NULL) err(1, "strdup"); return (out); no_change: out = strdup(filename1); if (out == NULL) err(1, "strdup"); return (out); }