Annotation of src/usr.bin/cu/cu.c, Revision 1.31
1.31 ! deraadt 1: /* $OpenBSD: cu.c,v 1.30 2023/12/21 11:25:38 jca Exp $ */
1.1 nicm 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:
1.29 krw 19: #include <sys/types.h>
1.1 nicm 20: #include <sys/ioctl.h>
1.29 krw 21: #include <sys/sysctl.h>
1.1 nicm 22:
1.11 halex 23: #include <ctype.h>
1.1 nicm 24: #include <err.h>
1.29 krw 25: #include <errno.h>
1.1 nicm 26: #include <event.h>
27: #include <fcntl.h>
28: #include <getopt.h>
29: #include <paths.h>
30: #include <pwd.h>
31: #include <signal.h>
32: #include <stdio.h>
33: #include <stdlib.h>
34: #include <string.h>
35: #include <termios.h>
36: #include <unistd.h>
1.20 deraadt 37: #include <limits.h>
1.1 nicm 38:
39: #include "cu.h"
40:
41: extern char *__progname;
42:
1.6 nicm 43: FILE *record_file;
1.1 nicm 44: struct termios saved_tio;
45: struct bufferevent *input_ev;
46: struct bufferevent *output_ev;
1.27 nicm 47: int escape_char = '~';
1.22 nicm 48: int is_direct = -1;
1.26 deraadt 49: int restricted = 0;
1.16 nicm 50: const char *line_path = NULL;
51: int line_speed = -1;
1.1 nicm 52: int line_fd;
1.12 nicm 53: struct termios line_tio;
1.1 nicm 54: struct bufferevent *line_ev;
55: struct event sigterm_ev;
1.8 nicm 56: struct event sighup_ev;
1.1 nicm 57: enum {
58: STATE_NONE,
59: STATE_NEWLINE,
1.27 nicm 60: STATE_ESCAPE
1.1 nicm 61: } last_state = STATE_NEWLINE;
62:
63: __dead void usage(void);
64: void signal_event(int, short, void *);
65: void stream_read(struct bufferevent *, void *);
66: void stream_error(struct bufferevent *, short, void *);
67: void line_read(struct bufferevent *, void *);
68: void line_error(struct bufferevent *, short, void *);
1.17 nicm 69: void try_remote(const char *, const char *, const char *);
1.29 krw 70: char *get_ucomnames(void);
71: char *find_ucom(const char *, char *);
1.1 nicm 72:
73: __dead void
74: usage(void)
75: {
1.27 nicm 76: fprintf(stderr, "usage: %s [-dr] [-E escape_char] [-l line] "
77: "[-s speed | -speed]\n", __progname);
1.21 nicm 78: fprintf(stderr, " %s [host]\n", __progname);
1.1 nicm 79: exit(1);
80: }
81:
82: int
83: main(int argc, char **argv)
84: {
1.16 nicm 85: const char *errstr;
1.29 krw 86: char *tmp, *s, *host, *ucomnames;
1.22 nicm 87: int opt, i, flags;
1.24 deraadt 88:
1.29 krw 89: ucomnames = get_ucomnames();
90:
1.24 deraadt 91: if (pledge("stdio rpath wpath cpath getpw proc exec tty",
92: NULL) == -1)
93: err(1, "pledge");
1.1 nicm 94:
1.16 nicm 95: if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0)
96: err(1, "tcgetattr");
1.1 nicm 97:
98: /*
99: * Convert obsolescent -### speed to modern -s### syntax which getopt()
100: * can handle.
101: */
102: for (i = 1; i < argc; i++) {
1.10 dlg 103: if (strcmp("--", argv[i]) == 0)
104: break;
1.27 nicm 105: if (argv[i][0] != '-' || !isdigit((u_char)argv[i][1]))
1.10 dlg 106: continue;
107:
108: if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1)
109: errx(1, "speed asprintf");
1.1 nicm 110: }
111:
1.27 nicm 112: while ((opt = getopt(argc, argv, "drE:l:s:")) != -1) {
1.1 nicm 113: switch (opt) {
1.22 nicm 114: case 'd':
115: is_direct = 1;
116: break;
1.26 deraadt 117: case 'r':
118: if (pledge("stdio rpath wpath tty", NULL) == -1)
119: err(1, "pledge");
120: restricted = 1;
121: break;
1.27 nicm 122: case 'E':
123: if (optarg[0] == '^' && optarg[2] == '\0' &&
124: (u_char)optarg[1] >= 64 && (u_char)optarg[1] < 128)
125: escape_char = (u_char)optarg[1] & 31;
126: else if (strlen(optarg) == 1)
127: escape_char = (u_char)optarg[0];
128: else
129: errx(1, "invalid escape character: %s", optarg);
130: break;
1.1 nicm 131: case 'l':
1.16 nicm 132: line_path = optarg;
1.1 nicm 133: break;
134: case 's':
1.16 nicm 135: line_speed = strtonum(optarg, 0, INT_MAX, &errstr);
1.1 nicm 136: if (errstr != NULL)
137: errx(1, "speed is %s: %s", errstr, optarg);
138: break;
139: default:
140: usage();
141: }
142: }
143: argc -= optind;
144: argv += optind;
1.16 nicm 145: if (argc != 0 && argc != 1)
1.1 nicm 146: usage();
147:
1.22 nicm 148: if (line_path != NULL || line_speed != -1 || is_direct != -1) {
1.21 nicm 149: if (argc != 0)
150: usage();
151: } else {
152: if (argc == 1)
153: host = argv[0];
154: else
155: host = getenv("HOST");
156: if (host != NULL && *host != '\0') {
157: if (*host == '/')
1.19 nicm 158: line_path = host;
1.21 nicm 159: else {
160: s = getenv("REMOTE");
161: if (s != NULL && *s == '/')
162: try_remote(host, s, NULL);
163: else
164: try_remote(host, NULL, s);
165: }
1.18 nicm 166: }
1.17 nicm 167: }
1.16 nicm 168:
169: if (line_path == NULL)
170: line_path = "/dev/cua00";
171: if (line_speed == -1)
172: line_speed = 9600;
1.22 nicm 173: if (is_direct == -1)
174: is_direct = 0;
1.16 nicm 175:
1.29 krw 176: if (strncasecmp(line_path, "usb", 3) == 0) {
177: tmp = find_ucom(line_path, ucomnames);
178: if (tmp == NULL)
179: errx(1, "No ucom matched '%s'", line_path);
180: line_path = tmp;
181: }
1.16 nicm 182: if (strchr(line_path, '/') == NULL) {
183: if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1)
1.1 nicm 184: err(1, "asprintf");
1.16 nicm 185: line_path = tmp;
1.1 nicm 186: }
187:
1.22 nicm 188: flags = O_RDWR;
189: if (is_direct)
190: flags |= O_NONBLOCK;
191: line_fd = open(line_path, flags);
1.28 deraadt 192: if (line_fd == -1)
1.16 nicm 193: err(1, "open(\"%s\")", line_path);
1.26 deraadt 194: if (restricted && pledge("stdio tty", NULL) == -1)
195: err(1, "pledge");
1.25 mestre 196: if (!isatty(line_fd))
197: err(1, "%s", line_path);
1.1 nicm 198: if (ioctl(line_fd, TIOCEXCL) != 0)
199: err(1, "ioctl(TIOCEXCL)");
1.12 nicm 200: if (tcgetattr(line_fd, &line_tio) != 0)
201: err(1, "tcgetattr");
1.16 nicm 202: if (set_line(line_speed) != 0)
1.4 nicm 203: err(1, "tcsetattr");
1.1 nicm 204:
205: event_init();
1.3 nicm 206:
1.1 nicm 207: signal_set(&sigterm_ev, SIGTERM, signal_event, NULL);
208: signal_add(&sigterm_ev, NULL);
1.8 nicm 209: signal_set(&sighup_ev, SIGHUP, signal_event, NULL);
210: signal_add(&sighup_ev, NULL);
1.3 nicm 211: if (signal(SIGINT, SIG_IGN) == SIG_ERR)
212: err(1, "signal");
213: if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
214: err(1, "signal");
1.1 nicm 215:
1.4 nicm 216: set_termios(); /* after this use cu_err and friends */
1.1 nicm 217:
218: /* stdin and stdout get separate events */
219: input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL,
220: stream_error, NULL);
221: bufferevent_enable(input_ev, EV_READ);
222: output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error,
223: NULL);
224: bufferevent_enable(output_ev, EV_WRITE);
225:
1.23 nicm 226: set_blocking(line_fd, 0);
1.1 nicm 227: line_ev = bufferevent_new(line_fd, line_read, NULL, line_error,
228: NULL);
229: bufferevent_enable(line_ev, EV_READ|EV_WRITE);
230:
1.16 nicm 231: printf("Connected to %s (speed %d)\r\n", line_path, line_speed);
1.1 nicm 232: event_dispatch();
233:
1.5 nicm 234: restore_termios();
1.1 nicm 235: printf("\r\n[EOT]\n");
236:
237: exit(0);
238: }
239:
240: void
241: signal_event(int fd, short events, void *data)
242: {
1.5 nicm 243: restore_termios();
1.1 nicm 244: printf("\r\n[SIG%s]\n", sys_signame[fd]);
245:
246: exit(0);
247: }
248:
249: void
1.23 nicm 250: set_blocking(int fd, int state)
251: {
252: int mode;
253:
254: state = state ? 0 : O_NONBLOCK;
255: if ((mode = fcntl(fd, F_GETFL)) == -1)
256: cu_err(1, "fcntl");
257: if ((mode & O_NONBLOCK) != state) {
258: mode = (mode & ~O_NONBLOCK) | state;
259: if (fcntl(fd, F_SETFL, mode) == -1)
260: cu_err(1, "fcntl");
261: }
262: }
263:
264: void
1.1 nicm 265: set_termios(void)
266: {
267: struct termios tio;
268:
269: if (!isatty(STDIN_FILENO))
270: return;
271:
272: memcpy(&tio, &saved_tio, sizeof(tio));
273: tio.c_lflag &= ~(ICANON|IEXTEN|ECHO);
274: tio.c_iflag &= ~(INPCK|ICRNL);
275: tio.c_oflag &= ~OPOST;
276: tio.c_cc[VMIN] = 1;
277: tio.c_cc[VTIME] = 0;
278: tio.c_cc[VDISCARD] = _POSIX_VDISABLE;
279: tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
280: tio.c_cc[VINTR] = _POSIX_VDISABLE;
281: tio.c_cc[VLNEXT] = _POSIX_VDISABLE;
282: tio.c_cc[VQUIT] = _POSIX_VDISABLE;
283: tio.c_cc[VSUSP] = _POSIX_VDISABLE;
284: if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0)
1.4 nicm 285: cu_err(1, "tcsetattr");
1.1 nicm 286: }
287:
288: void
289: restore_termios(void)
290: {
1.4 nicm 291: if (isatty(STDIN_FILENO))
292: tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio);
1.2 nicm 293: }
294:
295: int
296: set_line(int speed)
297: {
298: struct termios tio;
299:
1.12 nicm 300: memcpy(&tio, &line_tio, sizeof(tio));
301: tio.c_iflag &= ~(ISTRIP|ICRNL);
302: tio.c_oflag &= ~OPOST;
303: tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
304: tio.c_cflag &= ~(CSIZE|PARENB);
305: tio.c_cflag |= CREAD|CS8|CLOCAL;
1.2 nicm 306: tio.c_cc[VMIN] = 1;
307: tio.c_cc[VTIME] = 0;
308: cfsetspeed(&tio, speed);
1.4 nicm 309: if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0)
1.2 nicm 310: return (-1);
311: return (0);
1.1 nicm 312: }
313:
314: void
315: stream_read(struct bufferevent *bufev, void *data)
316: {
317: char *new_data, *ptr;
318: size_t new_size;
319: int state_change;
320:
321: new_data = EVBUFFER_DATA(input_ev->input);
322: new_size = EVBUFFER_LENGTH(input_ev->input);
323: if (new_size == 0)
324: return;
325:
326: state_change = isatty(STDIN_FILENO);
327: for (ptr = new_data; ptr < new_data + new_size; ptr++) {
328: switch (last_state) {
329: case STATE_NONE:
330: if (state_change && *ptr == '\r')
331: last_state = STATE_NEWLINE;
332: break;
333: case STATE_NEWLINE:
1.27 nicm 334: if (state_change && (u_char)*ptr == escape_char) {
335: last_state = STATE_ESCAPE;
1.1 nicm 336: continue;
337: }
338: if (*ptr != '\r')
339: last_state = STATE_NONE;
340: break;
1.27 nicm 341: case STATE_ESCAPE:
1.1 nicm 342: do_command(*ptr);
343: last_state = STATE_NEWLINE;
344: continue;
345: }
346:
347: bufferevent_write(line_ev, ptr, 1);
348: }
349:
350: evbuffer_drain(input_ev->input, new_size);
351: }
352:
353: void
354: stream_error(struct bufferevent *bufev, short what, void *data)
355: {
356: event_loopexit(NULL);
357: }
358:
359: void
360: line_read(struct bufferevent *bufev, void *data)
361: {
362: char *new_data;
363: size_t new_size;
364:
365: new_data = EVBUFFER_DATA(line_ev->input);
366: new_size = EVBUFFER_LENGTH(line_ev->input);
367: if (new_size == 0)
368: return;
369:
1.6 nicm 370: if (record_file != NULL)
371: fwrite(new_data, 1, new_size, record_file);
1.1 nicm 372: bufferevent_write(output_ev, new_data, new_size);
373:
374: evbuffer_drain(line_ev->input, new_size);
375: }
376:
377: void
378: line_error(struct bufferevent *bufev, short what, void *data)
379: {
380: event_loopexit(NULL);
1.16 nicm 381: }
382:
383: void
1.17 nicm 384: try_remote(const char *host, const char *path, const char *entry)
1.16 nicm 385: {
386: const char *paths[] = { "/etc/remote", NULL, NULL };
387: char *cp, *s;
388: long l;
389: int error;
390:
391: if (path != NULL) {
392: paths[0] = path;
393: paths[1] = "/etc/remote";
394: }
395:
1.17 nicm 396: if (entry != NULL && cgetset(entry) != 0)
397: cu_errx(1, "cgetset failed");
1.23 nicm 398: error = cgetent(&cp, (char **)paths, (char *)host);
1.16 nicm 399: if (error < 0) {
400: switch (error) {
401: case -1:
402: cu_errx(1, "unknown host %s", host);
403: case -2:
404: cu_errx(1, "can't open remote file");
405: case -3:
406: cu_errx(1, "loop in remote file");
407: default:
408: cu_errx(1, "unknown error in remote file");
409: }
410: }
1.22 nicm 411:
412: if (is_direct == -1 && cgetcap(cp, "dc", ':') != NULL)
413: is_direct = 1;
1.16 nicm 414:
415: if (line_path == NULL && cgetstr(cp, "dv", &s) >= 0)
416: line_path = s;
417:
418: if (line_speed == -1 && cgetnum(cp, "br", &l) >= 0) {
419: if (l < 0 || l > INT_MAX)
420: cu_errx(1, "speed out of range");
421: line_speed = l;
422: }
1.1 nicm 423: }
424:
425: /* Expands tildes in the file name. Based on code from ssh/misc.c. */
426: char *
427: tilde_expand(const char *filename1)
428: {
1.14 tedu 429: const char *filename, *path, *sep;
430: char user[128], *out;
1.1 nicm 431: struct passwd *pw;
432: u_int len, slash;
1.14 tedu 433: int rv;
1.1 nicm 434:
435: if (*filename1 != '~')
436: goto no_change;
437: filename = filename1 + 1;
438:
439: path = strchr(filename, '/');
440: if (path != NULL && path > filename) { /* ~user/path */
441: slash = path - filename;
442: if (slash > sizeof(user) - 1)
443: goto no_change;
444: memcpy(user, filename, slash);
445: user[slash] = '\0';
446: if ((pw = getpwnam(user)) == NULL)
447: goto no_change;
448: } else if ((pw = getpwuid(getuid())) == NULL) /* ~/path */
449: goto no_change;
450:
451: /* Make sure directory has a trailing '/' */
452: len = strlen(pw->pw_dir);
1.14 tedu 453: if (len == 0 || pw->pw_dir[len - 1] != '/')
454: sep = "/";
455: else
456: sep = "";
1.1 nicm 457:
458: /* Skip leading '/' from specified path */
459: if (path != NULL)
460: filename = path + 1;
1.14 tedu 461:
462: if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1)
463: cu_err(1, "asprintf");
1.20 deraadt 464: if (rv >= PATH_MAX) {
1.14 tedu 465: free(out);
1.1 nicm 466: goto no_change;
1.14 tedu 467: }
1.1 nicm 468:
469: return (out);
470:
471: no_change:
472: out = strdup(filename1);
473: if (out == NULL)
1.4 nicm 474: cu_err(1, "strdup");
1.1 nicm 475: return (out);
1.29 krw 476: }
477:
478: char *
479: get_ucomnames(void)
480: {
481: char *names;
482: int mib[2];
483: size_t size;
484:
485: mib[0] = CTL_HW;
486: mib[1] = HW_UCOMNAMES;
487: names = NULL;
488: size = 0;
489: for (;;) {
490: if (sysctl(mib, 2, NULL, &size, NULL, 0) == -1 || size == 0)
1.31 ! deraadt 491: return NULL;
1.29 krw 492: if ((names = realloc(names, size)) == NULL)
493: err(1, NULL);
494: if (sysctl(mib, 2, names, &size, NULL, 0) != -1)
495: break;
496: if (errno != ENOMEM)
1.31 ! deraadt 497: return NULL;
1.29 krw 498: }
499: return names;
500: }
501:
502: char *
503: find_ucom(const char *usbid, char *names)
504: {
505: char *cua, *id, *ucom;
506:
507: if (names == NULL)
508: return NULL;
509:
1.30 jca 510: /* names is a comma separated list of "ucom<unit#>:<usb id>". */
1.29 krw 511: cua = NULL;
512: for (ucom = strsep(&names, ","); ucom; ucom = strsep(&names, ",")) {
513: if (*ucom == '\0' || strncasecmp(ucom, "ucom", 4))
514: continue;
515: ucom += 4;
516: id = strchr(ucom, ':');
517: if (id == NULL)
518: continue;
519: *id++ = '\0';
520: if (strcasecmp(id, usbid) == 0) {
521: if (asprintf(&cua, "cuaU%s", ucom) == -1)
522: err(1, NULL);
523: break;
524: }
525: }
526: return cua;
1.1 nicm 527: }