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