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