xref: /openbsd/usr.bin/cu/cu.c (revision 439243a3)
1*439243a3Snicm /* $OpenBSD: cu.c,v 1.16 2014/03/26 13:00:50 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;
44*439243a3Snicm const char		*line_path = NULL;
45*439243a3Snicm int			 line_speed = -1;
46f391cec5Snicm int			 line_fd;
4789ee0e62Snicm struct termios		 line_tio;
48f391cec5Snicm struct bufferevent	*line_ev;
49f391cec5Snicm struct event		 sigterm_ev;
50c30d1c6aSnicm struct event		 sighup_ev;
51f391cec5Snicm enum {
52f391cec5Snicm 	STATE_NONE,
53f391cec5Snicm 	STATE_NEWLINE,
54f391cec5Snicm 	STATE_TILDE
55f391cec5Snicm } last_state = STATE_NEWLINE;
56f391cec5Snicm 
57f391cec5Snicm __dead void	usage(void);
58f391cec5Snicm void		signal_event(int, short, void *);
59f391cec5Snicm void		stream_read(struct bufferevent *, void *);
60f391cec5Snicm void		stream_error(struct bufferevent *, short, void *);
61f391cec5Snicm void		line_read(struct bufferevent *, void *);
62f391cec5Snicm void		line_error(struct bufferevent *, short, void *);
63*439243a3Snicm void		try_remote(const char *, const char *);
64f391cec5Snicm 
65f391cec5Snicm __dead void
66f391cec5Snicm usage(void)
67f391cec5Snicm {
68*439243a3Snicm 	fprintf(stderr, "usage: %s [-l line] [-s speed | -speed] [host]\n",
696ddaf43aSnicm 	    __progname);
70f391cec5Snicm 	exit(1);
71f391cec5Snicm }
72f391cec5Snicm 
73f391cec5Snicm int
74f391cec5Snicm main(int argc, char **argv)
75f391cec5Snicm {
76*439243a3Snicm 	const char	*errstr;
77*439243a3Snicm 	char		*tmp, *s;
78*439243a3Snicm 	int		 opt, i;
79f391cec5Snicm 
80*439243a3Snicm 	if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &saved_tio) != 0)
81*439243a3Snicm 		err(1, "tcgetattr");
82f391cec5Snicm 
83f391cec5Snicm 	/*
84f391cec5Snicm 	 * Convert obsolescent -### speed to modern -s### syntax which getopt()
85f391cec5Snicm 	 * can handle.
86f391cec5Snicm 	 */
87f391cec5Snicm 	for (i = 1; i < argc; i++) {
880630af0bSdlg 		if (strcmp("--", argv[i]) == 0)
89f391cec5Snicm 			break;
90e889c1f5Sderaadt 		if (argv[i][0] != '-' || !isdigit((unsigned char)argv[i][1]))
910630af0bSdlg 			continue;
920630af0bSdlg 
930630af0bSdlg 		if (asprintf(&argv[i], "-s%s", &argv[i][1]) == -1)
940630af0bSdlg 			errx(1, "speed asprintf");
95f391cec5Snicm 	}
96f391cec5Snicm 
97f391cec5Snicm 	while ((opt = getopt(argc, argv, "l:s:")) != -1) {
98f391cec5Snicm 		switch (opt) {
99f391cec5Snicm 		case 'l':
100*439243a3Snicm 			line_path = optarg;
101f391cec5Snicm 			break;
102f391cec5Snicm 		case 's':
103*439243a3Snicm 			line_speed = strtonum(optarg, 0, INT_MAX, &errstr);
104f391cec5Snicm 			if (errstr != NULL)
105f391cec5Snicm 				errx(1, "speed is %s: %s", errstr, optarg);
106f391cec5Snicm 			break;
107f391cec5Snicm 		default:
108f391cec5Snicm 			usage();
109f391cec5Snicm 		}
110f391cec5Snicm 	}
111f391cec5Snicm 	argc -= optind;
112f391cec5Snicm 	argv += optind;
113*439243a3Snicm 	if (argc != 0 && argc != 1)
114f391cec5Snicm 		usage();
115f391cec5Snicm 
116*439243a3Snicm 	s = getenv("REMOTE");
117*439243a3Snicm 	if (argc == 1) {
118*439243a3Snicm 		if (s != NULL && *s == '/')
119*439243a3Snicm 			try_remote(argv[0], s);
120*439243a3Snicm 		else
121*439243a3Snicm 			try_remote(argv[0], NULL);
122*439243a3Snicm 	} else if (s != NULL && *s != '/')
123*439243a3Snicm 		try_remote(s, NULL);
124*439243a3Snicm 
125*439243a3Snicm 	if (line_path == NULL)
126*439243a3Snicm 		line_path = "/dev/cua00";
127*439243a3Snicm 	if (line_speed == -1)
128*439243a3Snicm 		line_speed = 9600;
129*439243a3Snicm 
130*439243a3Snicm 	if (strchr(line_path, '/') == NULL) {
131*439243a3Snicm 		if (asprintf(&tmp, "%s%s", _PATH_DEV, line_path) == -1)
132f391cec5Snicm 			err(1, "asprintf");
133*439243a3Snicm 		line_path = tmp;
134f391cec5Snicm 	}
135f391cec5Snicm 
136*439243a3Snicm 	line_fd = open(line_path, O_RDWR);
137f391cec5Snicm 	if (line_fd < 0)
138*439243a3Snicm 		err(1, "open(\"%s\")", line_path);
139f391cec5Snicm 	if (ioctl(line_fd, TIOCEXCL) != 0)
140f391cec5Snicm 		err(1, "ioctl(TIOCEXCL)");
14189ee0e62Snicm 	if (tcgetattr(line_fd, &line_tio) != 0)
14289ee0e62Snicm 		err(1, "tcgetattr");
143*439243a3Snicm 	if (set_line(line_speed) != 0)
14411403328Snicm 		err(1, "tcsetattr");
145f391cec5Snicm 
146f391cec5Snicm 	event_init();
147ca5f2657Snicm 
148f391cec5Snicm 	signal_set(&sigterm_ev, SIGTERM, signal_event, NULL);
149f391cec5Snicm 	signal_add(&sigterm_ev, NULL);
150c30d1c6aSnicm 	signal_set(&sighup_ev, SIGHUP, signal_event, NULL);
151c30d1c6aSnicm 	signal_add(&sighup_ev, NULL);
152ca5f2657Snicm 	if (signal(SIGINT, SIG_IGN) == SIG_ERR)
153ca5f2657Snicm 		err(1, "signal");
154ca5f2657Snicm 	if (signal(SIGQUIT, SIG_IGN) == SIG_ERR)
155ca5f2657Snicm 		err(1, "signal");
156f391cec5Snicm 
15711403328Snicm 	set_termios(); /* after this use cu_err and friends */
158f391cec5Snicm 
159f391cec5Snicm 	/* stdin and stdout get separate events */
160f391cec5Snicm 	input_ev = bufferevent_new(STDIN_FILENO, stream_read, NULL,
161f391cec5Snicm 	    stream_error, NULL);
162f391cec5Snicm 	bufferevent_enable(input_ev, EV_READ);
163f391cec5Snicm 	output_ev = bufferevent_new(STDOUT_FILENO, NULL, NULL, stream_error,
164f391cec5Snicm 	    NULL);
165f391cec5Snicm 	bufferevent_enable(output_ev, EV_WRITE);
166f391cec5Snicm 
167f391cec5Snicm 	line_ev = bufferevent_new(line_fd, line_read, NULL, line_error,
168f391cec5Snicm 	    NULL);
169f391cec5Snicm 	bufferevent_enable(line_ev, EV_READ|EV_WRITE);
170f391cec5Snicm 
171*439243a3Snicm 	printf("Connected to %s (speed %d)\r\n", line_path, line_speed);
172f391cec5Snicm 	event_dispatch();
173f391cec5Snicm 
174654f0ea4Snicm 	restore_termios();
175f391cec5Snicm 	printf("\r\n[EOT]\n");
176f391cec5Snicm 
177f391cec5Snicm 	exit(0);
178f391cec5Snicm }
179f391cec5Snicm 
180f391cec5Snicm void
181f391cec5Snicm signal_event(int fd, short events, void *data)
182f391cec5Snicm {
183654f0ea4Snicm 	restore_termios();
184f391cec5Snicm 	printf("\r\n[SIG%s]\n", sys_signame[fd]);
185f391cec5Snicm 
186f391cec5Snicm 	exit(0);
187f391cec5Snicm }
188f391cec5Snicm 
189f391cec5Snicm void
190f391cec5Snicm set_termios(void)
191f391cec5Snicm {
192f391cec5Snicm 	struct termios tio;
193f391cec5Snicm 
194f391cec5Snicm 	if (!isatty(STDIN_FILENO))
195f391cec5Snicm 		return;
196f391cec5Snicm 
197f391cec5Snicm 	memcpy(&tio, &saved_tio, sizeof(tio));
198f391cec5Snicm 	tio.c_lflag &= ~(ICANON|IEXTEN|ECHO);
199f391cec5Snicm 	tio.c_iflag &= ~(INPCK|ICRNL);
200f391cec5Snicm 	tio.c_oflag &= ~OPOST;
201f391cec5Snicm 	tio.c_cc[VMIN] = 1;
202f391cec5Snicm 	tio.c_cc[VTIME] = 0;
203f391cec5Snicm 	tio.c_cc[VDISCARD] = _POSIX_VDISABLE;
204f391cec5Snicm 	tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
205f391cec5Snicm 	tio.c_cc[VINTR] = _POSIX_VDISABLE;
206f391cec5Snicm 	tio.c_cc[VLNEXT] = _POSIX_VDISABLE;
207f391cec5Snicm 	tio.c_cc[VQUIT] = _POSIX_VDISABLE;
208f391cec5Snicm 	tio.c_cc[VSUSP] = _POSIX_VDISABLE;
209f391cec5Snicm 	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0)
21011403328Snicm 		cu_err(1, "tcsetattr");
211f391cec5Snicm }
212f391cec5Snicm 
213f391cec5Snicm void
214f391cec5Snicm restore_termios(void)
215f391cec5Snicm {
21611403328Snicm 	if (isatty(STDIN_FILENO))
21711403328Snicm 		tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_tio);
218f391cec5Snicm }
219f391cec5Snicm 
220909d4fcfSnicm int
221909d4fcfSnicm set_line(int speed)
222909d4fcfSnicm {
223909d4fcfSnicm 	struct termios	 tio;
224909d4fcfSnicm 
22589ee0e62Snicm 	memcpy(&tio, &line_tio, sizeof(tio));
22689ee0e62Snicm 	tio.c_iflag &= ~(ISTRIP|ICRNL);
22789ee0e62Snicm 	tio.c_oflag &= ~OPOST;
22889ee0e62Snicm 	tio.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
22989ee0e62Snicm 	tio.c_cflag &= ~(CSIZE|PARENB);
23089ee0e62Snicm 	tio.c_cflag |= CREAD|CS8|CLOCAL;
231909d4fcfSnicm 	tio.c_cc[VMIN] = 1;
232909d4fcfSnicm 	tio.c_cc[VTIME] = 0;
233909d4fcfSnicm 	cfsetspeed(&tio, speed);
23411403328Snicm 	if (tcsetattr(line_fd, TCSAFLUSH, &tio) != 0)
235909d4fcfSnicm 		return (-1);
236909d4fcfSnicm 	return (0);
237909d4fcfSnicm }
238909d4fcfSnicm 
239f391cec5Snicm void
240f391cec5Snicm stream_read(struct bufferevent *bufev, void *data)
241f391cec5Snicm {
242f391cec5Snicm 	char	*new_data, *ptr;
243f391cec5Snicm 	size_t	 new_size;
244f391cec5Snicm 	int	 state_change;
245f391cec5Snicm 
246f391cec5Snicm 	new_data = EVBUFFER_DATA(input_ev->input);
247f391cec5Snicm 	new_size = EVBUFFER_LENGTH(input_ev->input);
248f391cec5Snicm 	if (new_size == 0)
249f391cec5Snicm 		return;
250f391cec5Snicm 
251f391cec5Snicm 	state_change = isatty(STDIN_FILENO);
252f391cec5Snicm 	for (ptr = new_data; ptr < new_data + new_size; ptr++) {
253f391cec5Snicm 		switch (last_state) {
254f391cec5Snicm 		case STATE_NONE:
255f391cec5Snicm 			if (state_change && *ptr == '\r')
256f391cec5Snicm 				last_state = STATE_NEWLINE;
257f391cec5Snicm 			break;
258f391cec5Snicm 		case STATE_NEWLINE:
259f391cec5Snicm 			if (state_change && *ptr == '~') {
260f391cec5Snicm 				last_state = STATE_TILDE;
261f391cec5Snicm 				continue;
262f391cec5Snicm 			}
263f391cec5Snicm 			if (*ptr != '\r')
264f391cec5Snicm 				last_state = STATE_NONE;
265f391cec5Snicm 			break;
266f391cec5Snicm 		case STATE_TILDE:
267f391cec5Snicm 			do_command(*ptr);
268f391cec5Snicm 			last_state = STATE_NEWLINE;
269f391cec5Snicm 			continue;
270f391cec5Snicm 		}
271f391cec5Snicm 
272f391cec5Snicm 		bufferevent_write(line_ev, ptr, 1);
273f391cec5Snicm 	}
274f391cec5Snicm 
275f391cec5Snicm 	evbuffer_drain(input_ev->input, new_size);
276f391cec5Snicm }
277f391cec5Snicm 
278f391cec5Snicm void
279f391cec5Snicm stream_error(struct bufferevent *bufev, short what, void *data)
280f391cec5Snicm {
281f391cec5Snicm 	event_loopexit(NULL);
282f391cec5Snicm }
283f391cec5Snicm 
284f391cec5Snicm void
285f391cec5Snicm line_read(struct bufferevent *bufev, void *data)
286f391cec5Snicm {
287f391cec5Snicm 	char	*new_data;
288f391cec5Snicm 	size_t	 new_size;
289f391cec5Snicm 
290f391cec5Snicm 	new_data = EVBUFFER_DATA(line_ev->input);
291f391cec5Snicm 	new_size = EVBUFFER_LENGTH(line_ev->input);
292f391cec5Snicm 	if (new_size == 0)
293f391cec5Snicm 		return;
294f391cec5Snicm 
29566d5e211Snicm 	if (record_file != NULL)
29666d5e211Snicm 		fwrite(new_data, 1, new_size, record_file);
297f391cec5Snicm 	bufferevent_write(output_ev, new_data, new_size);
298f391cec5Snicm 
299f391cec5Snicm 	evbuffer_drain(line_ev->input, new_size);
300f391cec5Snicm }
301f391cec5Snicm 
302f391cec5Snicm void
303f391cec5Snicm line_error(struct bufferevent *bufev, short what, void *data)
304f391cec5Snicm {
305f391cec5Snicm 	event_loopexit(NULL);
306f391cec5Snicm }
307f391cec5Snicm 
308*439243a3Snicm void
309*439243a3Snicm try_remote(const char *host, const char *path)
310*439243a3Snicm {
311*439243a3Snicm 	const char	*paths[] = { "/etc/remote", NULL, NULL };
312*439243a3Snicm 	char		*cp, *s;
313*439243a3Snicm 	long		 l;
314*439243a3Snicm 	int		 error;
315*439243a3Snicm 
316*439243a3Snicm 	if (path != NULL) {
317*439243a3Snicm 		paths[0] = path;
318*439243a3Snicm 		paths[1] = "/etc/remote";
319*439243a3Snicm 	}
320*439243a3Snicm 
321*439243a3Snicm 	error = cgetent(&cp, (char**)paths, (char*)host);
322*439243a3Snicm 	if (error < 0) {
323*439243a3Snicm 		switch (error) {
324*439243a3Snicm 		case -1:
325*439243a3Snicm 			cu_errx(1, "unknown host %s", host);
326*439243a3Snicm 		case -2:
327*439243a3Snicm 			cu_errx(1, "can't open remote file");
328*439243a3Snicm 		case -3:
329*439243a3Snicm 			cu_errx(1, "loop in remote file");
330*439243a3Snicm 		default:
331*439243a3Snicm 			cu_errx(1, "unknown error in remote file");
332*439243a3Snicm 		}
333*439243a3Snicm 	}
334*439243a3Snicm 
335*439243a3Snicm 	if (line_path == NULL && cgetstr(cp, "dv", &s) >= 0)
336*439243a3Snicm 		line_path = s;
337*439243a3Snicm 
338*439243a3Snicm 	if (line_speed == -1 && cgetnum(cp, "br", &l) >= 0) {
339*439243a3Snicm 		if (l < 0 || l > INT_MAX)
340*439243a3Snicm 			cu_errx(1, "speed out of range");
341*439243a3Snicm 		line_speed = l;
342*439243a3Snicm 	}
343*439243a3Snicm }
344*439243a3Snicm 
345f391cec5Snicm /* Expands tildes in the file name. Based on code from ssh/misc.c. */
346f391cec5Snicm char *
347f391cec5Snicm tilde_expand(const char *filename1)
348f391cec5Snicm {
34934574ca2Stedu 	const char	*filename, *path, *sep;
35034574ca2Stedu 	char		 user[128], *out;
351f391cec5Snicm 	struct passwd	*pw;
352f391cec5Snicm 	u_int		 len, slash;
35334574ca2Stedu 	int		 rv;
354f391cec5Snicm 
355f391cec5Snicm 	if (*filename1 != '~')
356f391cec5Snicm 		goto no_change;
357f391cec5Snicm 	filename = filename1 + 1;
358f391cec5Snicm 
359f391cec5Snicm 	path = strchr(filename, '/');
360f391cec5Snicm 	if (path != NULL && path > filename) {		/* ~user/path */
361f391cec5Snicm 		slash = path - filename;
362f391cec5Snicm 		if (slash > sizeof(user) - 1)
363f391cec5Snicm 			goto no_change;
364f391cec5Snicm 		memcpy(user, filename, slash);
365f391cec5Snicm 		user[slash] = '\0';
366f391cec5Snicm 		if ((pw = getpwnam(user)) == NULL)
367f391cec5Snicm 			goto no_change;
368f391cec5Snicm 	} else if ((pw = getpwuid(getuid())) == NULL)	/* ~/path */
369f391cec5Snicm 		goto no_change;
370f391cec5Snicm 
371f391cec5Snicm 	/* Make sure directory has a trailing '/' */
372f391cec5Snicm 	len = strlen(pw->pw_dir);
37334574ca2Stedu 	if (len == 0 || pw->pw_dir[len - 1] != '/')
37434574ca2Stedu 		sep = "/";
37534574ca2Stedu 	else
37634574ca2Stedu 		sep = "";
377f391cec5Snicm 
378f391cec5Snicm 	/* Skip leading '/' from specified path */
379f391cec5Snicm 	if (path != NULL)
380f391cec5Snicm 		filename = path + 1;
381f391cec5Snicm 
38234574ca2Stedu 	if ((rv = asprintf(&out, "%s%s%s", pw->pw_dir, sep, filename)) == -1)
38334574ca2Stedu 		cu_err(1, "asprintf");
38434574ca2Stedu 	if (rv >= MAXPATHLEN) {
38534574ca2Stedu 		free(out);
38634574ca2Stedu 		goto no_change;
38734574ca2Stedu 	}
38834574ca2Stedu 
389f391cec5Snicm 	return (out);
390f391cec5Snicm 
391f391cec5Snicm no_change:
392f391cec5Snicm 	out = strdup(filename1);
393f391cec5Snicm 	if (out == NULL)
39411403328Snicm 		cu_err(1, "strdup");
395f391cec5Snicm 	return (out);
396f391cec5Snicm }
397