1 /* $OpenBSD: cu.c,v 1.19 2014/04/12 12:47:43 nicm 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 const char *line_path = NULL; 45 int line_speed = -1; 46 int line_fd; 47 struct termios line_tio; 48 struct bufferevent *line_ev; 49 struct event sigterm_ev; 50 struct event sighup_ev; 51 enum { 52 STATE_NONE, 53 STATE_NEWLINE, 54 STATE_TILDE 55 } last_state = STATE_NEWLINE; 56 57 __dead void usage(void); 58 void signal_event(int, short, void *); 59 void stream_read(struct bufferevent *, void *); 60 void stream_error(struct bufferevent *, short, void *); 61 void line_read(struct bufferevent *, void *); 62 void line_error(struct bufferevent *, short, void *); 63 void try_remote(const char *, const char *, const char *); 64 65 __dead void 66 usage(void) 67 { 68 fprintf(stderr, "usage: %s [-l line] [-s speed | -speed] [host]\n", 69 __progname); 70 exit(1); 71 } 72 73 int 74 main(int argc, char **argv) 75 { 76 const char *errstr; 77 char *tmp, *s, *host; 78 int opt, i; 79 80 if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0) 81 err(1, "tcgetattr"); 82 83 /* 84 * Convert obsolescent -### speed to modern -s### syntax which getopt() 85 * can handle. 86 */ 87 for (i = 1; i < argc; i++) { 88 if (strcmp("--", argv[i]) == 0) 89 break; 90 if (argv[i][0] != '-' || !isdigit((unsigned char)argv[i][1])) 91 continue; 92 93 if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1) 94 errx(1, "speed asprintf"); 95 } 96 97 while ((opt = getopt(argc, argv, "l:s:")) != -1) { 98 switch (opt) { 99 case 'l': 100 line_path = optarg; 101 break; 102 case 's': 103 line_speed = strtonum(optarg, 0, INT_MAX, &errstr); 104 if (errstr != NULL) 105 errx(1, "speed is %s: %s", errstr, optarg); 106 break; 107 default: 108 usage(); 109 } 110 } 111 argc -= optind; 112 argv += optind; 113 if (argc != 0 && argc != 1) 114 usage(); 115 116 if (argc == 1) 117 host = argv[0]; 118 else 119 host = getenv("HOST"); 120 if (host != NULL && *host != '\0') { 121 if (*host == '/') { 122 if (line_path == NULL) 123 line_path = host; 124 } else { 125 s = getenv("REMOTE"); 126 if (s != NULL && *s == '/') 127 try_remote(host, s, NULL); 128 else 129 try_remote(host, NULL, s); 130 } 131 } 132 133 if (line_path == NULL) 134 line_path = "/dev/cua00"; 135 if (line_speed == -1) 136 line_speed = 9600; 137 138 if (strchr(line_path, '/') == NULL) { 139 if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1) 140 err(1, "asprintf"); 141 line_path = tmp; 142 } 143 144 line_fd = open(line_path, O_RDWR); 145 if (line_fd < 0) 146 err(1, "open(\"%s\")", line_path); 147 if (ioctl(line_fd, TIOCEXCL) != 0) 148 err(1, "ioctl(TIOCEXCL)"); 149 if (tcgetattr(line_fd, &line_tio) != 0) 150 err(1, "tcgetattr"); 151 if (set_line(line_speed) != 0) 152 err(1, "tcsetattr"); 153 154 event_init(); 155 156 signal_set(&sigterm_ev, SIGTERM, signal_event, NULL); 157 signal_add(&sigterm_ev, NULL); 158 signal_set(&sighup_ev, SIGHUP, signal_event, NULL); 159 signal_add(&sighup_ev, NULL); 160 if (signal(SIGINT, SIG_IGN) == SIG_ERR) 161 err(1, "signal"); 162 if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) 163 err(1, "signal"); 164 165 set_termios(); /* after this use cu_err and friends */ 166 167 /* stdin and stdout get separate events */ 168 input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL, 169 stream_error, NULL); 170 bufferevent_enable(input_ev, EV_READ); 171 output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error, 172 NULL); 173 bufferevent_enable(output_ev, EV_WRITE); 174 175 line_ev = bufferevent_new(line_fd, line_read, NULL, line_error, 176 NULL); 177 bufferevent_enable(line_ev, EV_READ|EV_WRITE); 178 179 printf("Connected to %s (speed %d)\r\n", line_path, line_speed); 180 event_dispatch(); 181 182 restore_termios(); 183 printf("\r\n[EOT]\n"); 184 185 exit(0); 186 } 187 188 void 189 signal_event(int fd, short events, void *data) 190 { 191 restore_termios(); 192 printf("\r\n[SIG%s]\n", sys_signame[fd]); 193 194 exit(0); 195 } 196 197 void 198 set_termios(void) 199 { 200 struct termios tio; 201 202 if (!isatty(STDIN_FILENO)) 203 return; 204 205 memcpy(&tio, &saved_tio, sizeof(tio)); 206 tio.c_lflag &= ~(ICANON|IEXTEN|ECHO); 207 tio.c_iflag &= ~(INPCK|ICRNL); 208 tio.c_oflag &= ~OPOST; 209 tio.c_cc[VMIN] = 1; 210 tio.c_cc[VTIME] = 0; 211 tio.c_cc[VDISCARD] = _POSIX_VDISABLE; 212 tio.c_cc[VDSUSP] = _POSIX_VDISABLE; 213 tio.c_cc[VINTR] = _POSIX_VDISABLE; 214 tio.c_cc[VLNEXT] = _POSIX_VDISABLE; 215 tio.c_cc[VQUIT] = _POSIX_VDISABLE; 216 tio.c_cc[VSUSP] = _POSIX_VDISABLE; 217 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0) 218 cu_err(1, "tcsetattr"); 219 } 220 221 void 222 restore_termios(void) 223 { 224 if (isatty(STDIN_FILENO)) 225 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio); 226 } 227 228 int 229 set_line(int speed) 230 { 231 struct termios tio; 232 233 memcpy(&tio, &line_tio, sizeof(tio)); 234 tio.c_iflag &= ~(ISTRIP|ICRNL); 235 tio.c_oflag &= ~OPOST; 236 tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO); 237 tio.c_cflag &= ~(CSIZE|PARENB); 238 tio.c_cflag |= CREAD|CS8|CLOCAL; 239 tio.c_cc[VMIN] = 1; 240 tio.c_cc[VTIME] = 0; 241 cfsetspeed(&tio, speed); 242 if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0) 243 return (-1); 244 return (0); 245 } 246 247 void 248 stream_read(struct bufferevent *bufev, void *data) 249 { 250 char *new_data, *ptr; 251 size_t new_size; 252 int state_change; 253 254 new_data = EVBUFFER_DATA(input_ev->input); 255 new_size = EVBUFFER_LENGTH(input_ev->input); 256 if (new_size == 0) 257 return; 258 259 state_change = isatty(STDIN_FILENO); 260 for (ptr = new_data; ptr < new_data + new_size; ptr++) { 261 switch (last_state) { 262 case STATE_NONE: 263 if (state_change && *ptr == '\r') 264 last_state = STATE_NEWLINE; 265 break; 266 case STATE_NEWLINE: 267 if (state_change && *ptr == '~') { 268 last_state = STATE_TILDE; 269 continue; 270 } 271 if (*ptr != '\r') 272 last_state = STATE_NONE; 273 break; 274 case STATE_TILDE: 275 do_command(*ptr); 276 last_state = STATE_NEWLINE; 277 continue; 278 } 279 280 bufferevent_write(line_ev, ptr, 1); 281 } 282 283 evbuffer_drain(input_ev->input, new_size); 284 } 285 286 void 287 stream_error(struct bufferevent *bufev, short what, void *data) 288 { 289 event_loopexit(NULL); 290 } 291 292 void 293 line_read(struct bufferevent *bufev, void *data) 294 { 295 char *new_data; 296 size_t new_size; 297 298 new_data = EVBUFFER_DATA(line_ev->input); 299 new_size = EVBUFFER_LENGTH(line_ev->input); 300 if (new_size == 0) 301 return; 302 303 if (record_file != NULL) 304 fwrite(new_data, 1, new_size, record_file); 305 bufferevent_write(output_ev, new_data, new_size); 306 307 evbuffer_drain(line_ev->input, new_size); 308 } 309 310 void 311 line_error(struct bufferevent *bufev, short what, void *data) 312 { 313 event_loopexit(NULL); 314 } 315 316 void 317 try_remote(const char *host, const char *path, const char *entry) 318 { 319 const char *paths[] = { "/etc/remote", NULL, NULL }; 320 char *cp, *s; 321 long l; 322 int error; 323 324 if (path != NULL) { 325 paths[0] = path; 326 paths[1] = "/etc/remote"; 327 } 328 329 if (entry != NULL && cgetset(entry) != 0) 330 cu_errx(1, "cgetset failed"); 331 error = cgetent(&cp, (char**)paths, (char*)host); 332 if (error < 0) { 333 switch (error) { 334 case -1: 335 cu_errx(1, "unknown host %s", host); 336 case -2: 337 cu_errx(1, "can't open remote file"); 338 case -3: 339 cu_errx(1, "loop in remote file"); 340 default: 341 cu_errx(1, "unknown error in remote file"); 342 } 343 } 344 345 if (line_path == NULL && cgetstr(cp, "dv", &s) >= 0) 346 line_path = s; 347 348 if (line_speed == -1 && cgetnum(cp, "br", &l) >= 0) { 349 if (l < 0 || l > INT_MAX) 350 cu_errx(1, "speed out of range"); 351 line_speed = l; 352 } 353 } 354 355 /* Expands tildes in the file name. Based on code from ssh/misc.c. */ 356 char * 357 tilde_expand(const char *filename1) 358 { 359 const char *filename, *path, *sep; 360 char user[128], *out; 361 struct passwd *pw; 362 u_int len, slash; 363 int rv; 364 365 if (*filename1 != '~') 366 goto no_change; 367 filename = filename1 + 1; 368 369 path = strchr(filename, '/'); 370 if (path != NULL && path > filename) { /* ~user/path */ 371 slash = path - filename; 372 if (slash > sizeof(user) - 1) 373 goto no_change; 374 memcpy(user, filename, slash); 375 user[slash] = '\0'; 376 if ((pw = getpwnam(user)) == NULL) 377 goto no_change; 378 } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */ 379 goto no_change; 380 381 /* Make sure directory has a trailing '/' */ 382 len = strlen(pw->pw_dir); 383 if (len == 0 || pw->pw_dir[len - 1] != '/') 384 sep = "/"; 385 else 386 sep = ""; 387 388 /* Skip leading '/' from specified path */ 389 if (path != NULL) 390 filename = path + 1; 391 392 if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1) 393 cu_err(1, "asprintf"); 394 if (rv >= MAXPATHLEN) { 395 free(out); 396 goto no_change; 397 } 398 399 return (out); 400 401 no_change: 402 out = strdup(filename1); 403 if (out == NULL) 404 cu_err(1, "strdup"); 405 return (out); 406 } 407