1/*	$OpenBSD: ldattach.c,v 1.20 2023/04/19 12:58:15 jsg Exp $	*/
2
3/*
4 * Copyright (c) 2007, 2008 Marc Balmer <mbalmer@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 USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19/*
20 * Attach a line disciplines to a tty(4) device either from the commandline
21 * or from init(8) (using entries in /etc/ttys).  Optionally pass the data
22 * received on the tty(4) device to the master device of a pty(4) pair.
23 */
24
25#include <sys/types.h>
26#include <sys/ioctl.h>
27#include <sys/limits.h>
28#include <sys/socket.h>
29#include <sys/stat.h>
30
31#include <err.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <paths.h>
35#include <poll.h>
36#include <signal.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <syslog.h>
41#include <termios.h>
42#include <unistd.h>
43#include <util.h>
44
45#include "atomicio.h"
46
47__dead void	usage(void);
48void		relay(int, int);
49void		coroner(int);
50
51volatile sig_atomic_t dying = 0;
52
53__dead void
54usage(void)
55{
56	extern char *__progname;
57
58	fprintf(stderr, "usage: %s [-27dehmop] [-s baudrate] "
59	    "[-t cond] discipline device\n", __progname);
60	exit(1);
61}
62
63/* relay data between two file descriptors */
64void
65relay(int device, int pty)
66{
67	struct pollfd pfd[2];
68	int nfds, n, nread;
69	char buf[128];
70
71	pfd[0].fd = device;
72	pfd[1].fd = pty;
73
74	while (!dying) {
75		pfd[0].events = POLLRDNORM;
76		pfd[1].events = POLLRDNORM;
77		nfds = poll(pfd, 2, INFTIM);
78		if (nfds == -1) {
79			syslog(LOG_ERR, "polling error");
80			exit(1);
81		}
82		if (nfds == 0)	/* should not happen */
83			continue;
84
85		if (pfd[1].revents & POLLHUP) {	/* slave device not connected */
86			sleep(1);
87			continue;
88		}
89
90		for (n = 0; n < 2; n++) {
91			if (!(pfd[n].revents & POLLRDNORM))
92				continue;
93
94			nread = read(pfd[n].fd, buf, sizeof(buf));
95			if (nread == -1) {
96				syslog(LOG_ERR, "error reading from %s: %m",
97				    n ? "pty" : "device");
98				exit(1);
99			}
100			if (nread == 0) {
101				syslog(LOG_ERR, "eof during read from %s: %m",
102				     n ? "pty" : "device");
103				exit(1);
104			}
105			atomicio(vwrite, pfd[1 - n].fd, buf, nread);
106		}
107	}
108}
109
110int
111main(int argc, char *argv[])
112{
113	struct termios tty;
114	struct tstamps tstamps;
115	const char *errstr;
116	sigset_t sigset;
117	pid_t ppid;
118	int ch, fd, master = -1, slave, pty = 0, ldisc, nodaemon = 0;
119	int bits = 0, parity = 0, stop = 0, flowcl = 0, hupcl = 1;
120	speed_t speed = 0;
121	char devn[32], ptyn[32], *dev, *disc;
122
123	tstamps.ts_set = tstamps.ts_clr = 0;
124
125	if ((ppid = getppid()) == 1)
126		nodaemon = 1;
127
128	while ((ch = getopt(argc, argv, "27dehmops:t:")) != -1) {
129		switch (ch) {
130		case '2':
131			stop = 2;
132			break;
133		case '7':
134			bits = 7;
135			break;
136		case 'd':
137			nodaemon = 1;
138			break;
139		case 'e':
140			parity = 'e';
141			break;
142		case 'h':
143			flowcl = 1;
144			break;
145		case 'm':
146			hupcl = 0;
147			break;
148		case 'o':
149			parity = 'o';
150			break;
151		case 'p':
152			pty = 1;
153			break;
154		case 's':
155			speed = (speed_t)strtonum(optarg, 0, UINT_MAX, &errstr);
156			if (errstr) {
157				if (ppid != 1)
158					errx(1,  "speed is %s: %s", errstr,
159					    optarg);
160				else
161					goto bail_out;
162			}
163			break;
164		case 't':
165			if (!strcasecmp(optarg, "dcd"))
166				tstamps.ts_set |= TIOCM_CAR;
167			else if (!strcasecmp(optarg, "!dcd"))
168				tstamps.ts_clr |= TIOCM_CAR;
169			else if (!strcasecmp(optarg, "cts"))
170				tstamps.ts_set |= TIOCM_CTS;
171			else if (!strcasecmp(optarg, "!cts"))
172				tstamps.ts_clr |= TIOCM_CTS;
173			else {
174				if (ppid != 1)
175					errx(1, "'%s' not supported for "
176					    "timestamping", optarg);
177				else
178					goto bail_out;
179			}
180			break;
181		default:
182			if (ppid != -1)
183				usage();
184		}
185	}
186	argc -= optind;
187	argv += optind;
188
189	if (ppid != 1 && argc != 2)
190		usage();
191
192	disc = *argv++;
193	dev = *argv;
194	if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) {
195		(void)snprintf(devn, sizeof(devn), "%s%s", _PATH_DEV, dev);
196		dev = devn;
197	}
198
199	if (!strcmp(disc, "nmea")) {
200		ldisc = NMEADISC;
201		if (speed == 0)
202			speed = B4800;	/* default is 4800 baud for nmea */
203	} else if (!strcmp(disc, "msts")) {
204		ldisc = MSTSDISC;
205	} else if (!strcmp(disc, "endrun")) {
206		ldisc = ENDRUNDISC;
207	} else {
208		syslog(LOG_ERR, "unknown line discipline %s", disc);
209		goto bail_out;
210	}
211
212	if ((fd = open(dev, O_RDWR)) == -1) {
213		syslog(LOG_ERR, "can't open %s", dev);
214		goto bail_out;
215	}
216
217	/*
218	 * Get the current line attributes, modify only values that are
219	 * either requested on the command line or that are needed by
220	 * the line discipline (e.g. nmea has a default baudrate of
221	 * 4800 instead of 9600).
222	 */
223	if (tcgetattr(fd, &tty) == -1) {
224		if (ppid != 1)
225			warnx("tcgetattr");
226		goto bail_out;
227	}
228
229
230	if (bits == 7) {
231		tty.c_cflag &= ~CS8;
232		tty.c_cflag |= CS7;
233	} else if (bits == 8) {
234		tty.c_cflag &= ~CS7;
235		tty.c_cflag |= CS8;
236	}
237
238	if (parity != 0)
239		tty.c_cflag |= PARENB;
240	if (parity == 'o')
241		tty.c_cflag |= PARODD;
242	else
243		tty.c_cflag &= ~PARODD;
244
245	if (stop == 2)
246		tty.c_cflag |= CSTOPB;
247	else
248		tty.c_cflag &= ~CSTOPB;
249
250	if (flowcl)
251		tty.c_cflag |= CRTSCTS;
252
253	if (hupcl == 0)
254		tty.c_cflag &= ~HUPCL;
255
256	if (speed != 0)
257		cfsetspeed(&tty, speed);
258
259	/* setup common to all line disciplines */
260	if (ioctl(fd, TIOCSDTR, 0) == -1)
261		warn("TIOCSDTR");
262	if (ioctl(fd, TIOCSETD, &ldisc) == -1) {
263		syslog(LOG_ERR, "can't attach %s line discipline on %s", disc,
264		    dev);
265		goto bail_out;
266	}
267
268	/* line discpline specific setup */
269	switch (ldisc) {
270	case NMEADISC:
271	case MSTSDISC:
272	case ENDRUNDISC:
273		if (ioctl(fd, TIOCSTSTAMP, &tstamps) == -1) {
274			warnx("TIOCSTSTAMP");
275			goto bail_out;
276		}
277		tty.c_cflag |= CLOCAL;
278		tty.c_iflag = 0;
279		tty.c_lflag = 0;
280		tty.c_oflag = 0;
281		tty.c_cc[VMIN] = 1;
282		tty.c_cc[VTIME] = 0;
283		break;
284	}
285
286	/* finally set the line attributes */
287	if (tcsetattr(fd, TCSADRAIN, &tty) == -1) {
288		if (ppid != 1)
289			warnx("tcsetattr");
290		goto bail_out;
291	}
292
293	/*
294	 * open a pty(4) pair to pass the data if the -p option has been
295	 * given on the commandline.
296	 */
297	if (pty) {
298		if (openpty(&master, &slave, ptyn, NULL, NULL))
299			errx(1, "can't open a pty");
300		close(slave);
301		printf("%s\n", ptyn);
302		fflush(stdout);
303	}
304	if (nodaemon)
305		openlog("ldattach", LOG_PID | LOG_CONS | LOG_PERROR,
306		    LOG_DAEMON);
307	else {
308		openlog("ldattach", LOG_PID | LOG_CONS, LOG_DAEMON);
309		if (daemon(0, 0))
310			errx(1, "can't daemonize");
311	}
312
313	syslog(LOG_INFO, "attach %s on %s", disc, dev);
314	signal(SIGHUP, coroner);
315	signal(SIGTERM, coroner);
316
317	if (master != -1) {
318		syslog(LOG_INFO, "passing data to %s", ptyn);
319		relay(fd, master);
320	} else {
321		sigemptyset(&sigset);
322
323		while (!dying)
324			sigsuspend(&sigset);
325	}
326
327bail_out:
328	if (ppid == 1)
329		sleep(30);	/* delay restart when called from init */
330
331	return 0;
332}
333
334void
335coroner(int useless)
336{
337	dying = 1;
338}
339