xref: /openbsd/usr.bin/cu/cu.c (revision 89ee0e62)
1*89ee0e62Snicm /* $OpenBSD: cu.c,v 1.12 2013/01/17 11:15:22 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 
22023045f1Shalex #include <ctype.h>
23f391cec5Snicm #include <err.h>
24f391cec5Snicm #include <event.h>
25f391cec5Snicm #include <fcntl.h>
26f391cec5Snicm #include <getopt.h>
27f391cec5Snicm #include <paths.h>
28f391cec5Snicm #include <pwd.h>
29f391cec5Snicm #include <signal.h>
30f391cec5Snicm #include <stdio.h>
31f391cec5Snicm #include <stdlib.h>
32f391cec5Snicm #include <string.h>
33f391cec5Snicm #include <termios.h>
34f391cec5Snicm #include <unistd.h>
35f391cec5Snicm 
36f391cec5Snicm #include "cu.h"
37f391cec5Snicm 
38f391cec5Snicm extern char		*__progname;
39f391cec5Snicm 
4066d5e211Snicm FILE			*record_file;
41f391cec5Snicm struct termios		 saved_tio;
42f391cec5Snicm struct bufferevent	*input_ev;
43f391cec5Snicm struct bufferevent	*output_ev;
44f391cec5Snicm int			 line_fd;
45*89ee0e62Snicm struct termios		 line_tio;
46f391cec5Snicm struct bufferevent	*line_ev;
47f391cec5Snicm struct event		 sigterm_ev;
48c30d1c6aSnicm struct event		 sighup_ev;
49f391cec5Snicm enum {
50f391cec5Snicm 	STATE_NONE,
51f391cec5Snicm 	STATE_NEWLINE,
52f391cec5Snicm 	STATE_TILDE
53f391cec5Snicm } last_state = STATE_NEWLINE;
54f391cec5Snicm 
55f391cec5Snicm __dead void	usage(void);
56f391cec5Snicm void		signal_event(int, short, void *);
57f391cec5Snicm void		stream_read(struct bufferevent *, void *);
58f391cec5Snicm void		stream_error(struct bufferevent *, short, void *);
59f391cec5Snicm void		line_read(struct bufferevent *, void *);
60f391cec5Snicm void		line_error(struct bufferevent *, short, void *);
61f391cec5Snicm 
62f391cec5Snicm __dead void
63f391cec5Snicm usage(void)
64f391cec5Snicm {
656ddaf43aSnicm 	fprintf(stderr, "usage: %s [-l line] [-s speed | -speed]\n",
666ddaf43aSnicm 	    __progname);
67f391cec5Snicm 	exit(1);
68f391cec5Snicm }
69f391cec5Snicm 
70f391cec5Snicm int
71f391cec5Snicm main(int argc, char **argv)
72f391cec5Snicm {
73f391cec5Snicm 	const char	*line, *errstr;
74f391cec5Snicm 	char		*tmp;
75023045f1Shalex 	int		 opt, speed, i;
76f391cec5Snicm 
77f391cec5Snicm 	line = "/dev/cua00";
78f391cec5Snicm 	speed = 9600;
79f391cec5Snicm 
80f391cec5Snicm 	/*
81f391cec5Snicm 	 * Convert obsolescent -### speed to modern -s### syntax which getopt()
82f391cec5Snicm 	 * can handle.
83f391cec5Snicm 	 */
84f391cec5Snicm 	for (i = 1; i < argc; i++) {
850630af0bSdlg 		if (strcmp("--", argv[i]) == 0)
86f391cec5Snicm 			break;
870630af0bSdlg 		if (argv[i][0] != '-' || !isdigit(argv[i][1]))
880630af0bSdlg 			continue;
890630af0bSdlg 
900630af0bSdlg 		if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1)
910630af0bSdlg 			errx(1, "speed asprintf");
92f391cec5Snicm 	}
93f391cec5Snicm 
94f391cec5Snicm 	while ((opt = getopt(argc, argv, "l:s:")) != -1) {
95f391cec5Snicm 		switch (opt) {
96f391cec5Snicm 		case 'l':
97f391cec5Snicm 			line = optarg;
98f391cec5Snicm 			break;
99f391cec5Snicm 		case 's':
100f391cec5Snicm 			speed = strtonum(optarg, 0, UINT_MAX, &errstr);
101f391cec5Snicm 			if (errstr != NULL)
102f391cec5Snicm 				errx(1, "speed is %s: %s", errstr, optarg);
103f391cec5Snicm 			break;
104f391cec5Snicm 		default:
105f391cec5Snicm 			usage();
106f391cec5Snicm 		}
107f391cec5Snicm 	}
108f391cec5Snicm 	argc -= optind;
109f391cec5Snicm 	argv += optind;
110f391cec5Snicm 	if (argc != 0)
111f391cec5Snicm 		usage();
112f391cec5Snicm 
113f391cec5Snicm 	if (strchr(line, '/') == NULL) {
114f391cec5Snicm 		if (asprintf(&tmp, "%s%s", _PATH_DEV, line) == -1)
115f391cec5Snicm 			err(1, "asprintf");
116f391cec5Snicm 		line = tmp;
117f391cec5Snicm 	}
118f391cec5Snicm 
119f391cec5Snicm 	line_fd = open(line, O_RDWR);
120f391cec5Snicm 	if (line_fd < 0)
121f391cec5Snicm 		err(1, "open(\"%s\")", line);
122f391cec5Snicm 	if (ioctl(line_fd, TIOCEXCL) != 0)
123f391cec5Snicm 		err(1, "ioctl(TIOCEXCL)");
124*89ee0e62Snicm 	if (tcgetattr(line_fd, &line_tio) != 0)
125*89ee0e62Snicm 		err(1, "tcgetattr");
126909d4fcfSnicm 	if (set_line(speed) != 0)
12711403328Snicm 		err(1, "tcsetattr");
128f391cec5Snicm 
129f391cec5Snicm 	if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0)
130f391cec5Snicm 		err(1, "tcgetattr");
131f391cec5Snicm 
132f391cec5Snicm 	event_init();
133ca5f2657Snicm 
134f391cec5Snicm 	signal_set(&sigterm_ev, SIGTERM, signal_event, NULL);
135f391cec5Snicm 	signal_add(&sigterm_ev, NULL);
136c30d1c6aSnicm 	signal_set(&sighup_ev, SIGHUP, signal_event, NULL);
137c30d1c6aSnicm 	signal_add(&sighup_ev, NULL);
138ca5f2657Snicm 	if (signal(SIGINT, SIG_IGN) == SIG_ERR)
139ca5f2657Snicm 		err(1, "signal");
140ca5f2657Snicm 	if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
141ca5f2657Snicm 		err(1, "signal");
142f391cec5Snicm 
14311403328Snicm 	set_termios(); /* after this use cu_err and friends */
144f391cec5Snicm 
145f391cec5Snicm 	/* stdin and stdout get separate events */
146f391cec5Snicm 	input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL,
147f391cec5Snicm 	    stream_error, NULL);
148f391cec5Snicm 	bufferevent_enable(input_ev, EV_READ);
149f391cec5Snicm 	output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error,
150f391cec5Snicm 	    NULL);
151f391cec5Snicm 	bufferevent_enable(output_ev, EV_WRITE);
152f391cec5Snicm 
153f391cec5Snicm 	line_ev = bufferevent_new(line_fd, line_read, NULL, line_error,
154f391cec5Snicm 	    NULL);
155f391cec5Snicm 	bufferevent_enable(line_ev, EV_READ|EV_WRITE);
156f391cec5Snicm 
157f391cec5Snicm 	printf("Connected (speed %u)\r\n", speed);
158f391cec5Snicm 	event_dispatch();
159f391cec5Snicm 
160654f0ea4Snicm 	restore_termios();
161f391cec5Snicm 	printf("\r\n[EOT]\n");
162f391cec5Snicm 
163f391cec5Snicm 	exit(0);
164f391cec5Snicm }
165f391cec5Snicm 
166f391cec5Snicm void
167f391cec5Snicm signal_event(int fd, short events, void *data)
168f391cec5Snicm {
169654f0ea4Snicm 	restore_termios();
170f391cec5Snicm 	printf("\r\n[SIG%s]\n", sys_signame[fd]);
171f391cec5Snicm 
172f391cec5Snicm 	exit(0);
173f391cec5Snicm }
174f391cec5Snicm 
175f391cec5Snicm void
176f391cec5Snicm set_termios(void)
177f391cec5Snicm {
178f391cec5Snicm 	struct termios tio;
179f391cec5Snicm 
180f391cec5Snicm 	if (!isatty(STDIN_FILENO))
181f391cec5Snicm 		return;
182f391cec5Snicm 
183f391cec5Snicm 	memcpy(&tio, &saved_tio, sizeof(tio));
184f391cec5Snicm 	tio.c_lflag &= ~(ICANON|IEXTEN|ECHO);
185f391cec5Snicm 	tio.c_iflag &= ~(INPCK|ICRNL);
186f391cec5Snicm 	tio.c_oflag &= ~OPOST;
187f391cec5Snicm 	tio.c_cc[VMIN] = 1;
188f391cec5Snicm 	tio.c_cc[VTIME] = 0;
189f391cec5Snicm 	tio.c_cc[VDISCARD] = _POSIX_VDISABLE;
190f391cec5Snicm 	tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
191f391cec5Snicm 	tio.c_cc[VINTR] = _POSIX_VDISABLE;
192f391cec5Snicm 	tio.c_cc[VLNEXT] = _POSIX_VDISABLE;
193f391cec5Snicm 	tio.c_cc[VQUIT] = _POSIX_VDISABLE;
194f391cec5Snicm 	tio.c_cc[VSUSP] = _POSIX_VDISABLE;
195f391cec5Snicm 	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0)
19611403328Snicm 		cu_err(1, "tcsetattr");
197f391cec5Snicm }
198f391cec5Snicm 
199f391cec5Snicm void
200f391cec5Snicm restore_termios(void)
201f391cec5Snicm {
20211403328Snicm 	if (isatty(STDIN_FILENO))
20311403328Snicm 		tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio);
204f391cec5Snicm }
205f391cec5Snicm 
206909d4fcfSnicm int
207909d4fcfSnicm set_line(int speed)
208909d4fcfSnicm {
209909d4fcfSnicm 	struct termios	 tio;
210909d4fcfSnicm 
211*89ee0e62Snicm 	memcpy(&tio, &line_tio, sizeof(tio));
212*89ee0e62Snicm 	tio.c_iflag &= ~(ISTRIP|ICRNL);
213*89ee0e62Snicm 	tio.c_oflag &= ~OPOST;
214*89ee0e62Snicm 	tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
215*89ee0e62Snicm 	tio.c_cflag &= ~(CSIZE|PARENB);
216*89ee0e62Snicm 	tio.c_cflag |= CREAD|CS8|CLOCAL;
217909d4fcfSnicm 	tio.c_cc[VMIN] = 1;
218909d4fcfSnicm 	tio.c_cc[VTIME] = 0;
219909d4fcfSnicm 	cfsetspeed(&tio, speed);
22011403328Snicm 	if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0)
221909d4fcfSnicm 		return (-1);
222909d4fcfSnicm 	return (0);
223909d4fcfSnicm }
224909d4fcfSnicm 
225f391cec5Snicm void
226f391cec5Snicm stream_read(struct bufferevent *bufev, void *data)
227f391cec5Snicm {
228f391cec5Snicm 	char	*new_data, *ptr;
229f391cec5Snicm 	size_t	 new_size;
230f391cec5Snicm 	int	 state_change;
231f391cec5Snicm 
232f391cec5Snicm 	new_data = EVBUFFER_DATA(input_ev->input);
233f391cec5Snicm 	new_size = EVBUFFER_LENGTH(input_ev->input);
234f391cec5Snicm 	if (new_size == 0)
235f391cec5Snicm 		return;
236f391cec5Snicm 
237f391cec5Snicm 	state_change = isatty(STDIN_FILENO);
238f391cec5Snicm 	for (ptr = new_data; ptr < new_data + new_size; ptr++) {
239f391cec5Snicm 		switch (last_state) {
240f391cec5Snicm 		case STATE_NONE:
241f391cec5Snicm 			if (state_change && *ptr == '\r')
242f391cec5Snicm 				last_state = STATE_NEWLINE;
243f391cec5Snicm 			break;
244f391cec5Snicm 		case STATE_NEWLINE:
245f391cec5Snicm 			if (state_change && *ptr == '~') {
246f391cec5Snicm 				last_state = STATE_TILDE;
247f391cec5Snicm 				continue;
248f391cec5Snicm 			}
249f391cec5Snicm 			if (*ptr != '\r')
250f391cec5Snicm 				last_state = STATE_NONE;
251f391cec5Snicm 			break;
252f391cec5Snicm 		case STATE_TILDE:
253f391cec5Snicm 			do_command(*ptr);
254f391cec5Snicm 			last_state = STATE_NEWLINE;
255f391cec5Snicm 			continue;
256f391cec5Snicm 		}
257f391cec5Snicm 
258f391cec5Snicm 		bufferevent_write(line_ev, ptr, 1);
259f391cec5Snicm 	}
260f391cec5Snicm 
261f391cec5Snicm 	evbuffer_drain(input_ev->input, new_size);
262f391cec5Snicm }
263f391cec5Snicm 
264f391cec5Snicm void
265f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data)
266f391cec5Snicm {
267f391cec5Snicm 	event_loopexit(NULL);
268f391cec5Snicm }
269f391cec5Snicm 
270f391cec5Snicm void
271f391cec5Snicm line_read(struct bufferevent *bufev, void *data)
272f391cec5Snicm {
273f391cec5Snicm 	char	*new_data;
274f391cec5Snicm 	size_t	 new_size;
275f391cec5Snicm 
276f391cec5Snicm 	new_data = EVBUFFER_DATA(line_ev->input);
277f391cec5Snicm 	new_size = EVBUFFER_LENGTH(line_ev->input);
278f391cec5Snicm 	if (new_size == 0)
279f391cec5Snicm 		return;
280f391cec5Snicm 
28166d5e211Snicm 	if (record_file != NULL)
28266d5e211Snicm 		fwrite(new_data, 1, new_size, record_file);
283f391cec5Snicm 	bufferevent_write(output_ev, new_data, new_size);
284f391cec5Snicm 
285f391cec5Snicm 	evbuffer_drain(line_ev->input, new_size);
286f391cec5Snicm }
287f391cec5Snicm 
288f391cec5Snicm void
289f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data)
290f391cec5Snicm {
291f391cec5Snicm 	event_loopexit(NULL);
292f391cec5Snicm }
293f391cec5Snicm 
294f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */
295f391cec5Snicm char *
296f391cec5Snicm tilde_expand(const char *filename1)
297f391cec5Snicm {
298f391cec5Snicm 	const char	*filename, *path;
299f391cec5Snicm 	char		 user[128], ret[MAXPATHLEN], *out;
300f391cec5Snicm 	struct passwd	*pw;
301f391cec5Snicm 	u_int		 len, slash;
302f391cec5Snicm 
303f391cec5Snicm 	if (*filename1 != '~')
304f391cec5Snicm 		goto no_change;
305f391cec5Snicm 	filename = filename1 + 1;
306f391cec5Snicm 
307f391cec5Snicm 	path = strchr(filename, '/');
308f391cec5Snicm 	if (path != NULL && path > filename) {		/* ~user/path */
309f391cec5Snicm 		slash = path - filename;
310f391cec5Snicm 		if (slash > sizeof(user) - 1)
311f391cec5Snicm 			goto no_change;
312f391cec5Snicm 		memcpy(user, filename, slash);
313f391cec5Snicm 		user[slash] = '\0';
314f391cec5Snicm 		if ((pw = getpwnam(user)) == NULL)
315f391cec5Snicm 			goto no_change;
316f391cec5Snicm 	} else if ((pw = getpwuid(getuid())) == NULL)	/* ~/path */
317f391cec5Snicm 		goto no_change;
318f391cec5Snicm 
319f391cec5Snicm 	if (strlcpy(ret, pw->pw_dir, sizeof(ret)) >= sizeof(ret))
320f391cec5Snicm 		goto no_change;
321f391cec5Snicm 
322f391cec5Snicm 	/* Make sure directory has a trailing '/' */
323f391cec5Snicm 	len = strlen(pw->pw_dir);
324f391cec5Snicm 	if ((len == 0 || pw->pw_dir[len - 1] != '/') &&
325f391cec5Snicm 	    strlcat(ret, "/", sizeof(ret)) >= sizeof(ret))
326f391cec5Snicm 		goto no_change;
327f391cec5Snicm 
328f391cec5Snicm 	/* Skip leading '/' from specified path */
329f391cec5Snicm 	if (path != NULL)
330f391cec5Snicm 		filename = path + 1;
331f391cec5Snicm 	if (strlcat(ret, filename, sizeof(ret)) >= sizeof(ret))
332f391cec5Snicm 		goto no_change;
333f391cec5Snicm 
334f391cec5Snicm 	out = strdup(ret);
335f391cec5Snicm 	if (out == NULL)
33611403328Snicm 		cu_err(1, "strdup");
337f391cec5Snicm 	return (out);
338f391cec5Snicm 
339f391cec5Snicm no_change:
340f391cec5Snicm 	out = strdup(filename1);
341f391cec5Snicm 	if (out == NULL)
34211403328Snicm 		cu_err(1, "strdup");
343f391cec5Snicm 	return (out);
344f391cec5Snicm }
345