xref: /openbsd/usr.bin/cu/cu.c (revision 6ddaf43a)
1*6ddaf43aSnicm /* $OpenBSD: cu.c,v 1.7 2012/07/10 13:00:09 nicm Exp $ */
2f391cec5Snicm 
3f391cec5Snicm /*
4f391cec5Snicm  * Copyright (c) 2012 Nicholas Marriott <nicm@openbsd.org>
5f391cec5Snicm  *
6f391cec5Snicm  * Permission to use, copy, modify, and distribute this software for any
7f391cec5Snicm  * purpose with or without fee is hereby granted, provided that the above
8f391cec5Snicm  * copyright notice and this permission notice appear in all copies.
9f391cec5Snicm  *
10f391cec5Snicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11f391cec5Snicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12f391cec5Snicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13f391cec5Snicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14f391cec5Snicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15f391cec5Snicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16f391cec5Snicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17f391cec5Snicm  */
18f391cec5Snicm 
19f391cec5Snicm #include <sys/param.h>
20f391cec5Snicm #include <sys/ioctl.h>
21f391cec5Snicm 
22f391cec5Snicm #include <err.h>
23f391cec5Snicm #include <event.h>
24f391cec5Snicm #include <fcntl.h>
25f391cec5Snicm #include <getopt.h>
26f391cec5Snicm #include <paths.h>
27f391cec5Snicm #include <pwd.h>
28f391cec5Snicm #include <signal.h>
29f391cec5Snicm #include <stdio.h>
30f391cec5Snicm #include <stdlib.h>
31f391cec5Snicm #include <string.h>
32f391cec5Snicm #include <termios.h>
33f391cec5Snicm #include <unistd.h>
34f391cec5Snicm 
35f391cec5Snicm #include "cu.h"
36f391cec5Snicm 
37f391cec5Snicm extern char		*__progname;
38f391cec5Snicm 
3966d5e211Snicm FILE			*record_file;
40f391cec5Snicm struct termios		 saved_tio;
41f391cec5Snicm struct bufferevent	*input_ev;
42f391cec5Snicm struct bufferevent	*output_ev;
43f391cec5Snicm int			 line_fd;
44f391cec5Snicm struct bufferevent	*line_ev;
45f391cec5Snicm struct event		 sigterm_ev;
46f391cec5Snicm enum {
47f391cec5Snicm 	STATE_NONE,
48f391cec5Snicm 	STATE_NEWLINE,
49f391cec5Snicm 	STATE_TILDE
50f391cec5Snicm } last_state = STATE_NEWLINE;
51f391cec5Snicm 
52f391cec5Snicm __dead void	usage(void);
53f391cec5Snicm void		signal_event(int, short, void *);
54f391cec5Snicm void		stream_read(struct bufferevent *, void *);
55f391cec5Snicm void		stream_error(struct bufferevent *, short, void *);
56f391cec5Snicm void		line_read(struct bufferevent *, void *);
57f391cec5Snicm void		line_error(struct bufferevent *, short, void *);
58f391cec5Snicm 
59f391cec5Snicm __dead void
60f391cec5Snicm usage(void)
61f391cec5Snicm {
62*6ddaf43aSnicm 	fprintf(stderr, "usage: %s [-l line] [-s speed | -speed]\n",
63*6ddaf43aSnicm 	    __progname);
64f391cec5Snicm 	exit(1);
65f391cec5Snicm }
66f391cec5Snicm 
67f391cec5Snicm int
68f391cec5Snicm main(int argc, char **argv)
69f391cec5Snicm {
70f391cec5Snicm 	const char	*line, *errstr;
71f391cec5Snicm 	char		*tmp;
72f391cec5Snicm 	int		 opt, speed, i, ch;
73f391cec5Snicm 	static char	 sbuf[12];
74f391cec5Snicm 
75f391cec5Snicm 	line = "/dev/cua00";
76f391cec5Snicm 	speed = 9600;
77f391cec5Snicm 
78f391cec5Snicm 	/*
79f391cec5Snicm 	 * Convert obsolescent -### speed to modern -s### syntax which getopt()
80f391cec5Snicm 	 * can handle.
81f391cec5Snicm 	 */
82f391cec5Snicm 	for (i = 1; i < argc; i++) {
83f391cec5Snicm 		if (argv[i][0] == '-') {
84f391cec5Snicm 			switch (argv[i][1]) {
85f391cec5Snicm 			case '0': case '1': case '2': case '3': case '4':
86f391cec5Snicm 			case '5': case '6': case '7': case '8': case '9':
87f391cec5Snicm 				ch = snprintf(sbuf, sizeof(sbuf), "-s%s",
88f391cec5Snicm 				    &argv[i][1]);
89f391cec5Snicm 				if (ch <= 0 || ch >= (int)sizeof(sbuf)) {
90f391cec5Snicm 					errx(1, "invalid speed: %s",
91f391cec5Snicm 					    &argv[i][1]);
92f391cec5Snicm 				}
93f391cec5Snicm 				argv[i] = sbuf;
94f391cec5Snicm 				break;
95f391cec5Snicm 			case '-':
96f391cec5Snicm 				/* if we get "--" stop processing args */
97f391cec5Snicm 				if (argv[i][2] == '\0')
98f391cec5Snicm 					goto getopt;
99f391cec5Snicm 				break;
100f391cec5Snicm 			}
101f391cec5Snicm 		}
102f391cec5Snicm 	}
103f391cec5Snicm 
104f391cec5Snicm getopt:
105f391cec5Snicm 	while ((opt = getopt(argc, argv, "l:s:")) != -1) {
106f391cec5Snicm 		switch (opt) {
107f391cec5Snicm 		case 'l':
108f391cec5Snicm 			line = optarg;
109f391cec5Snicm 			break;
110f391cec5Snicm 		case 's':
111f391cec5Snicm 			speed = strtonum(optarg, 0, UINT_MAX, &errstr);
112f391cec5Snicm 			if (errstr != NULL)
113f391cec5Snicm 				errx(1, "speed is %s: %s", errstr, optarg);
114f391cec5Snicm 			break;
115f391cec5Snicm 		default:
116f391cec5Snicm 			usage();
117f391cec5Snicm 		}
118f391cec5Snicm 	}
119f391cec5Snicm 	argc -= optind;
120f391cec5Snicm 	argv += optind;
121f391cec5Snicm 	if (argc != 0)
122f391cec5Snicm 		usage();
123f391cec5Snicm 
124f391cec5Snicm 	if (strchr(line, '/') == NULL) {
125f391cec5Snicm 		if (asprintf(&tmp, "%s%s", _PATH_DEV, line) == -1)
126f391cec5Snicm 			err(1, "asprintf");
127f391cec5Snicm 		line = tmp;
128f391cec5Snicm 	}
129f391cec5Snicm 
130f391cec5Snicm 	line_fd = open(line, O_RDWR);
131f391cec5Snicm 	if (line_fd < 0)
132f391cec5Snicm 		err(1, "open(\"%s\")", line);
133f391cec5Snicm 	if (ioctl(line_fd, TIOCEXCL) != 0)
134f391cec5Snicm 		err(1, "ioctl(TIOCEXCL)");
135f391cec5Snicm 
136909d4fcfSnicm 	if (set_line(speed) != 0)
13711403328Snicm 		err(1, "tcsetattr");
138f391cec5Snicm 
139f391cec5Snicm 	if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0)
140f391cec5Snicm 		err(1, "tcgetattr");
141f391cec5Snicm 
142f391cec5Snicm 	event_init();
143ca5f2657Snicm 
144f391cec5Snicm 	signal_set(&sigterm_ev, SIGTERM, signal_event, NULL);
145f391cec5Snicm 	signal_add(&sigterm_ev, NULL);
146ca5f2657Snicm 	if (signal(SIGINT, SIG_IGN) == SIG_ERR)
147ca5f2657Snicm 		err(1, "signal");
148ca5f2657Snicm 	if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
149ca5f2657Snicm 		err(1, "signal");
150f391cec5Snicm 
15111403328Snicm 	set_termios(); /* after this use cu_err and friends */
152f391cec5Snicm 
153f391cec5Snicm 	/* stdin and stdout get separate events */
154f391cec5Snicm 	input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL,
155f391cec5Snicm 	    stream_error, NULL);
156f391cec5Snicm 	bufferevent_enable(input_ev, EV_READ);
157f391cec5Snicm 	output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error,
158f391cec5Snicm 	    NULL);
159f391cec5Snicm 	bufferevent_enable(output_ev, EV_WRITE);
160f391cec5Snicm 
161f391cec5Snicm 	line_ev = bufferevent_new(line_fd, line_read, NULL, line_error,
162f391cec5Snicm 	    NULL);
163f391cec5Snicm 	bufferevent_enable(line_ev, EV_READ|EV_WRITE);
164f391cec5Snicm 
165f391cec5Snicm 	printf("Connected (speed %u)\r\n", speed);
166f391cec5Snicm 	event_dispatch();
167f391cec5Snicm 
168654f0ea4Snicm 	restore_termios();
169f391cec5Snicm 	printf("\r\n[EOT]\n");
170f391cec5Snicm 
171f391cec5Snicm 	exit(0);
172f391cec5Snicm }
173f391cec5Snicm 
174f391cec5Snicm void
175f391cec5Snicm signal_event(int fd, short events, void *data)
176f391cec5Snicm {
177654f0ea4Snicm 	restore_termios();
178f391cec5Snicm 	printf("\r\n[SIG%s]\n", sys_signame[fd]);
179f391cec5Snicm 
180f391cec5Snicm 	exit(0);
181f391cec5Snicm }
182f391cec5Snicm 
183f391cec5Snicm void
184f391cec5Snicm set_termios(void)
185f391cec5Snicm {
186f391cec5Snicm 	struct termios tio;
187f391cec5Snicm 
188f391cec5Snicm 	if (!isatty(STDIN_FILENO))
189f391cec5Snicm 		return;
190f391cec5Snicm 
191f391cec5Snicm 	memcpy(&tio, &saved_tio, sizeof(tio));
192f391cec5Snicm 	tio.c_lflag &= ~(ICANON|IEXTEN|ECHO);
193f391cec5Snicm 	tio.c_iflag &= ~(INPCK|ICRNL);
194f391cec5Snicm 	tio.c_oflag &= ~OPOST;
195f391cec5Snicm 	tio.c_cc[VMIN] = 1;
196f391cec5Snicm 	tio.c_cc[VTIME] = 0;
197f391cec5Snicm 	tio.c_cc[VDISCARD] = _POSIX_VDISABLE;
198f391cec5Snicm 	tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
199f391cec5Snicm 	tio.c_cc[VINTR] = _POSIX_VDISABLE;
200f391cec5Snicm 	tio.c_cc[VLNEXT] = _POSIX_VDISABLE;
201f391cec5Snicm 	tio.c_cc[VQUIT] = _POSIX_VDISABLE;
202f391cec5Snicm 	tio.c_cc[VSUSP] = _POSIX_VDISABLE;
203f391cec5Snicm 	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0)
20411403328Snicm 		cu_err(1, "tcsetattr");
205f391cec5Snicm }
206f391cec5Snicm 
207f391cec5Snicm void
208f391cec5Snicm restore_termios(void)
209f391cec5Snicm {
21011403328Snicm 	if (isatty(STDIN_FILENO))
21111403328Snicm 		tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio);
212f391cec5Snicm }
213f391cec5Snicm 
214909d4fcfSnicm int
215909d4fcfSnicm set_line(int speed)
216909d4fcfSnicm {
217909d4fcfSnicm 	struct termios	 tio;
218909d4fcfSnicm 
219909d4fcfSnicm 	cfmakeraw(&tio);
220909d4fcfSnicm 	tio.c_iflag = 0;
221909d4fcfSnicm 	tio.c_oflag = 0;
222909d4fcfSnicm 	tio.c_lflag = 0;
223909d4fcfSnicm 	tio.c_cflag = CREAD|CS8;
224909d4fcfSnicm 	tio.c_cc[VMIN] = 1;
225909d4fcfSnicm 	tio.c_cc[VTIME] = 0;
226909d4fcfSnicm 	cfsetspeed(&tio, speed);
22711403328Snicm 	if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0)
228909d4fcfSnicm 		return (-1);
229909d4fcfSnicm 	return (0);
230909d4fcfSnicm }
231909d4fcfSnicm 
232f391cec5Snicm void
233f391cec5Snicm stream_read(struct bufferevent *bufev, void *data)
234f391cec5Snicm {
235f391cec5Snicm 	char	*new_data, *ptr;
236f391cec5Snicm 	size_t	 new_size;
237f391cec5Snicm 	int	 state_change;
238f391cec5Snicm 
239f391cec5Snicm 	new_data = EVBUFFER_DATA(input_ev->input);
240f391cec5Snicm 	new_size = EVBUFFER_LENGTH(input_ev->input);
241f391cec5Snicm 	if (new_size == 0)
242f391cec5Snicm 		return;
243f391cec5Snicm 
244f391cec5Snicm 	state_change = isatty(STDIN_FILENO);
245f391cec5Snicm 	for (ptr = new_data; ptr < new_data + new_size; ptr++) {
246f391cec5Snicm 		switch (last_state) {
247f391cec5Snicm 		case STATE_NONE:
248f391cec5Snicm 			if (state_change && *ptr == '\r')
249f391cec5Snicm 				last_state = STATE_NEWLINE;
250f391cec5Snicm 			break;
251f391cec5Snicm 		case STATE_NEWLINE:
252f391cec5Snicm 			if (state_change && *ptr == '~') {
253f391cec5Snicm 				last_state = STATE_TILDE;
254f391cec5Snicm 				continue;
255f391cec5Snicm 			}
256f391cec5Snicm 			if (*ptr != '\r')
257f391cec5Snicm 				last_state = STATE_NONE;
258f391cec5Snicm 			break;
259f391cec5Snicm 		case STATE_TILDE:
260f391cec5Snicm 			do_command(*ptr);
261f391cec5Snicm 			last_state = STATE_NEWLINE;
262f391cec5Snicm 			continue;
263f391cec5Snicm 		}
264f391cec5Snicm 
265f391cec5Snicm 		bufferevent_write(line_ev, ptr, 1);
266f391cec5Snicm 	}
267f391cec5Snicm 
268f391cec5Snicm 	evbuffer_drain(input_ev->input, new_size);
269f391cec5Snicm }
270f391cec5Snicm 
271f391cec5Snicm void
272f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data)
273f391cec5Snicm {
274f391cec5Snicm 	event_loopexit(NULL);
275f391cec5Snicm }
276f391cec5Snicm 
277f391cec5Snicm void
278f391cec5Snicm line_read(struct bufferevent *bufev, void *data)
279f391cec5Snicm {
280f391cec5Snicm 	char	*new_data;
281f391cec5Snicm 	size_t	 new_size;
282f391cec5Snicm 
283f391cec5Snicm 	new_data = EVBUFFER_DATA(line_ev->input);
284f391cec5Snicm 	new_size = EVBUFFER_LENGTH(line_ev->input);
285f391cec5Snicm 	if (new_size == 0)
286f391cec5Snicm 		return;
287f391cec5Snicm 
28866d5e211Snicm 	if (record_file != NULL)
28966d5e211Snicm 		fwrite(new_data, 1, new_size, record_file);
290f391cec5Snicm 	bufferevent_write(output_ev, new_data, new_size);
291f391cec5Snicm 
292f391cec5Snicm 	evbuffer_drain(line_ev->input, new_size);
293f391cec5Snicm }
294f391cec5Snicm 
295f391cec5Snicm void
296f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data)
297f391cec5Snicm {
298f391cec5Snicm 	event_loopexit(NULL);
299f391cec5Snicm }
300f391cec5Snicm 
301f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */
302f391cec5Snicm char *
303f391cec5Snicm tilde_expand(const char *filename1)
304f391cec5Snicm {
305f391cec5Snicm 	const char	*filename, *path;
306f391cec5Snicm 	char		 user[128], ret[MAXPATHLEN], *out;
307f391cec5Snicm 	struct passwd	*pw;
308f391cec5Snicm 	u_int		 len, slash;
309f391cec5Snicm 
310f391cec5Snicm 	if (*filename1 != '~')
311f391cec5Snicm 		goto no_change;
312f391cec5Snicm 	filename = filename1 + 1;
313f391cec5Snicm 
314f391cec5Snicm 	path = strchr(filename, '/');
315f391cec5Snicm 	if (path != NULL && path > filename) {		/* ~user/path */
316f391cec5Snicm 		slash = path - filename;
317f391cec5Snicm 		if (slash > sizeof(user) - 1)
318f391cec5Snicm 			goto no_change;
319f391cec5Snicm 		memcpy(user, filename, slash);
320f391cec5Snicm 		user[slash] = '\0';
321f391cec5Snicm 		if ((pw = getpwnam(user)) == NULL)
322f391cec5Snicm 			goto no_change;
323f391cec5Snicm 	} else if ((pw = getpwuid(getuid())) == NULL)	/* ~/path */
324f391cec5Snicm 		goto no_change;
325f391cec5Snicm 
326f391cec5Snicm 	if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret))
327f391cec5Snicm 		goto no_change;
328f391cec5Snicm 
329f391cec5Snicm 	/* Make sure directory has a trailing '/' */
330f391cec5Snicm 	len = strlen(pw->pw_dir);
331f391cec5Snicm 	if ((len == 0 || pw->pw_dir[len - 1] != '/') &&
332f391cec5Snicm 	    strlcat(ret, "/", sizeof(ret)) >= sizeof(ret))
333f391cec5Snicm 		goto no_change;
334f391cec5Snicm 
335f391cec5Snicm 	/* Skip leading '/' from specified path */
336f391cec5Snicm 	if (path != NULL)
337f391cec5Snicm 		filename = path + 1;
338f391cec5Snicm 	if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret))
339f391cec5Snicm 		goto no_change;
340f391cec5Snicm 
341f391cec5Snicm 	out = strdup(ret);
342f391cec5Snicm 	if (out == NULL)
34311403328Snicm 		cu_err(1, "strdup");
344f391cec5Snicm 	return (out);
345f391cec5Snicm 
346f391cec5Snicm no_change:
347f391cec5Snicm 	out = strdup(filename1);
348f391cec5Snicm 	if (out == NULL)
34911403328Snicm 		cu_err(1, "strdup");
350f391cec5Snicm 	return (out);
351f391cec5Snicm }
352