1 /* $OpenBSD: cu.c,v 1.32 2024/10/24 22:42:08 krw 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/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/sysctl.h>
22
23 #include <ctype.h>
24 #include <err.h>
25 #include <errno.h>
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>
37 #include <limits.h>
38
39 #include "cu.h"
40
41 extern char *__progname;
42
43 FILE *record_file;
44 struct termios saved_tio;
45 struct bufferevent *input_ev;
46 struct bufferevent *output_ev;
47 int escape_char = '~';
48 int is_direct = -1;
49 int restricted = 0;
50 const char *line_path = NULL;
51 int line_speed = -1;
52 int line_fd;
53 struct termios line_tio;
54 struct bufferevent *line_ev;
55 struct event sigterm_ev;
56 struct event sighup_ev;
57 enum {
58 STATE_NONE,
59 STATE_NEWLINE,
60 STATE_ESCAPE
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 *);
69 void try_remote(const char *, const char *, const char *);
70 char *get_ucomnames(void);
71 char *find_ucom(const char *, char *);
72
73 __dead void
usage(void)74 usage(void)
75 {
76 fprintf(stderr, "usage: %s [-dr] [-E escape_char] [-l line] "
77 "[-s speed | -speed]\n", __progname);
78 fprintf(stderr, " %s [host]\n", __progname);
79 exit(1);
80 }
81
82 int
main(int argc,char ** argv)83 main(int argc, char **argv)
84 {
85 const char *errstr;
86 char *tmp, *s, *host, *ucomnames;
87 int opt, i, flags;
88
89 ucomnames = get_ucomnames();
90
91 if (pledge("stdio rpath wpath cpath getpw proc exec tty",
92 NULL) == -1)
93 err(1, "pledge");
94
95 if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0)
96 err(1, "tcgetattr");
97
98 /*
99 * Convert obsolescent -### speed to modern -s### syntax which getopt()
100 * can handle.
101 */
102 for (i = 1; i < argc; i++) {
103 if (strcmp("--", argv[i]) == 0)
104 break;
105 if (argv[i][0] != '-' || !isdigit((u_char)argv[i][1]))
106 continue;
107
108 if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1)
109 errx(1, "speed asprintf");
110 }
111
112 while ((opt = getopt(argc, argv, "drE:l:s:")) != -1) {
113 switch (opt) {
114 case 'd':
115 is_direct = 1;
116 break;
117 case 'r':
118 if (pledge("stdio rpath wpath tty", NULL) == -1)
119 err(1, "pledge");
120 restricted = 1;
121 break;
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;
131 case 'l':
132 line_path = optarg;
133 break;
134 case 's':
135 line_speed = strtonum(optarg, 0, INT_MAX, &errstr);
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;
145 if (argc != 0 && argc != 1)
146 usage();
147
148 if (line_path != NULL || line_speed != -1 || is_direct != -1) {
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 == '/')
158 line_path = host;
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 }
166 }
167 }
168
169 if (line_path == NULL)
170 line_path = "/dev/cua00";
171 if (line_speed == -1)
172 line_speed = 9600;
173 if (is_direct == -1)
174 is_direct = 0;
175
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 }
182 if (strchr(line_path, '/') == NULL) {
183 if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1)
184 err(1, "asprintf");
185 line_path = tmp;
186 }
187
188 flags = O_RDWR;
189 if (is_direct)
190 flags |= O_NONBLOCK;
191 line_fd = open(line_path, flags);
192 if (line_fd == -1)
193 err(1, "open(\"%s\")", line_path);
194 if (restricted && pledge("stdio tty", NULL) == -1)
195 err(1, "pledge");
196 if (!isatty(line_fd))
197 err(1, "%s", line_path);
198 if (ioctl(line_fd, TIOCEXCL) != 0)
199 err(1, "ioctl(TIOCEXCL)");
200 if (tcgetattr(line_fd, &line_tio) != 0)
201 err(1, "tcgetattr");
202 if (set_line(line_speed) != 0)
203 err(1, "tcsetattr");
204
205 event_init();
206
207 signal_set(&sigterm_ev, SIGTERM, signal_event, NULL);
208 signal_add(&sigterm_ev, NULL);
209 signal_set(&sighup_ev, SIGHUP, signal_event, NULL);
210 signal_add(&sighup_ev, NULL);
211 if (signal(SIGINT, SIG_IGN) == SIG_ERR)
212 err(1, "signal");
213 if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
214 err(1, "signal");
215
216 set_termios(); /* after this use cu_err and friends */
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
226 set_blocking(line_fd, 0);
227 line_ev = bufferevent_new(line_fd, line_read, NULL, line_error,
228 NULL);
229 bufferevent_enable(line_ev, EV_READ|EV_WRITE);
230
231 printf("Connected to %s (speed %d)\r\n", line_path, line_speed);
232 event_dispatch();
233
234 restore_termios();
235 printf("\r\n[EOT]\n");
236
237 exit(0);
238 }
239
240 void
signal_event(int fd,short events,void * data)241 signal_event(int fd, short events, void *data)
242 {
243 restore_termios();
244 printf("\r\n[SIG%s]\n", sys_signame[fd]);
245
246 exit(0);
247 }
248
249 void
set_blocking(int fd,int state)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
set_termios(void)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)
285 cu_err(1, "tcsetattr");
286 }
287
288 void
restore_termios(void)289 restore_termios(void)
290 {
291 if (isatty(STDIN_FILENO))
292 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio);
293 }
294
295 int
set_line(int speed)296 set_line(int speed)
297 {
298 struct termios tio;
299
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;
306 tio.c_cc[VMIN] = 1;
307 tio.c_cc[VTIME] = 0;
308 cfsetspeed(&tio, speed);
309 if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0)
310 return (-1);
311 return (0);
312 }
313
314 void
stream_read(struct bufferevent * bufev,void * data)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:
334 if (state_change && (u_char)*ptr == escape_char) {
335 last_state = STATE_ESCAPE;
336 continue;
337 }
338 if (*ptr != '\r')
339 last_state = STATE_NONE;
340 break;
341 case STATE_ESCAPE:
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
stream_error(struct bufferevent * bufev,short what,void * data)354 stream_error(struct bufferevent *bufev, short what, void *data)
355 {
356 event_loopexit(NULL);
357 }
358
359 void
line_read(struct bufferevent * bufev,void * data)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
370 if (record_file != NULL)
371 fwrite(new_data, 1, new_size, record_file);
372 bufferevent_write(output_ev, new_data, new_size);
373
374 evbuffer_drain(line_ev->input, new_size);
375 }
376
377 void
line_error(struct bufferevent * bufev,short what,void * data)378 line_error(struct bufferevent *bufev, short what, void *data)
379 {
380 event_loopexit(NULL);
381 }
382
383 void
try_remote(const char * host,const char * path,const char * entry)384 try_remote(const char *host, const char *path, const char *entry)
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
396 if (entry != NULL && cgetset(entry) != 0)
397 cu_errx(1, "cgetset failed");
398 error = cgetent(&cp, (char **)paths, (char *)host);
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 }
411
412 if (is_direct == -1 && cgetcap(cp, "dc", ':') != NULL)
413 is_direct = 1;
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 }
423 }
424
425 /* Expands tildes in the file name. Based on code from ssh/misc.c. */
426 char *
tilde_expand(const char * filename1)427 tilde_expand(const char *filename1)
428 {
429 const char *filename, *path, *sep;
430 char user[128], *out;
431 struct passwd *pw;
432 u_int len, slash;
433 int rv;
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);
453 if (len == 0 || pw->pw_dir[len - 1] != '/')
454 sep = "/";
455 else
456 sep = "";
457
458 /* Skip leading '/' from specified path */
459 if (path != NULL)
460 filename = path + 1;
461
462 if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1)
463 cu_err(1, "asprintf");
464 if (rv >= PATH_MAX) {
465 free(out);
466 goto no_change;
467 }
468
469 return (out);
470
471 no_change:
472 out = strdup(filename1);
473 if (out == NULL)
474 cu_err(1, "strdup");
475 return (out);
476 }
477
478 char *
get_ucomnames(void)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)
491 return NULL;
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)
497 return NULL;
498 }
499 return names;
500 }
501
502 char *
find_ucom(const char * usbid,char * names)503 find_ucom(const char *usbid, char *names)
504 {
505 const char *errstr;
506 const char *U;
507 char *cua, *id, *ucom;
508 uint32_t unit;
509
510 if (names == NULL)
511 return NULL;
512
513 /* The mapping of ucom[NN] to cuaU[C] is defined in MAKEDEV. */
514 U ="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
515
516 /* names is a comma separated list of "ucom<unit#>:<usb id>". */
517 cua = NULL;
518 for (ucom = strsep(&names, ","); ucom; ucom = strsep(&names, ",")) {
519 if (*ucom == '\0' || strncasecmp(ucom, "ucom", 4))
520 continue;
521 ucom += 4;
522 id = strchr(ucom, ':');
523 if (id == NULL)
524 continue;
525 *id++ = '\0';
526 if (strcasecmp(id, usbid) == 0) {
527 unit = strtonum(ucom, 0, strlen(U) - 1, &errstr);
528 if (errstr != NULL)
529 continue;
530 if (asprintf(&cua, "cuaU%c", U[unit]) == -1)
531 err(1, NULL);
532 break;
533 }
534 }
535 return cua;
536 }
537