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