xref: /openbsd-src/sbin/ldattach/ldattach.c (revision 6bae335dd015f9e023db26fea05e06c52cf1d0d7)
1*6bae335dSjsg /*	$OpenBSD: ldattach.c,v 1.20 2023/04/19 12:58:15 jsg Exp $	*/
26e9f490bSmbalmer 
36e9f490bSmbalmer /*
497189cdcSmbalmer  * Copyright (c) 2007, 2008 Marc Balmer <mbalmer@openbsd.org>
56e9f490bSmbalmer  *
66e9f490bSmbalmer  * Permission to use, copy, modify, and distribute this software for any
76e9f490bSmbalmer  * purpose with or without fee is hereby granted, provided that the above
86e9f490bSmbalmer  * copyright notice and this permission notice appear in all copies.
96e9f490bSmbalmer  *
106e9f490bSmbalmer  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
116e9f490bSmbalmer  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
126e9f490bSmbalmer  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
136e9f490bSmbalmer  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
146e9f490bSmbalmer  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
156e9f490bSmbalmer  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
166e9f490bSmbalmer  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
176e9f490bSmbalmer  */
186e9f490bSmbalmer 
196e9f490bSmbalmer /*
20a2129d99Smbalmer  * Attach a line disciplines to a tty(4) device either from the commandline
21a2129d99Smbalmer  * or from init(8) (using entries in /etc/ttys).  Optionally pass the data
22a2129d99Smbalmer  * received on the tty(4) device to the master device of a pty(4) pair.
236e9f490bSmbalmer  */
246e9f490bSmbalmer 
25330f45acSmbalmer #include <sys/types.h>
266e9f490bSmbalmer #include <sys/ioctl.h>
27a8fe7cfbSmbalmer #include <sys/limits.h>
28330f45acSmbalmer #include <sys/socket.h>
29330f45acSmbalmer #include <sys/stat.h>
306e9f490bSmbalmer 
316e9f490bSmbalmer #include <err.h>
326e9f490bSmbalmer #include <errno.h>
336e9f490bSmbalmer #include <fcntl.h>
346e9f490bSmbalmer #include <paths.h>
35330f45acSmbalmer #include <poll.h>
366e9f490bSmbalmer #include <signal.h>
376e9f490bSmbalmer #include <stdio.h>
386e9f490bSmbalmer #include <stdlib.h>
396e9f490bSmbalmer #include <string.h>
406e9f490bSmbalmer #include <syslog.h>
416e9f490bSmbalmer #include <termios.h>
426e9f490bSmbalmer #include <unistd.h>
43330f45acSmbalmer #include <util.h>
44330f45acSmbalmer 
45330f45acSmbalmer #include "atomicio.h"
466e9f490bSmbalmer 
476e9f490bSmbalmer __dead void	usage(void);
48330f45acSmbalmer void		relay(int, int);
496e9f490bSmbalmer void		coroner(int);
506e9f490bSmbalmer 
516e9f490bSmbalmer volatile sig_atomic_t dying = 0;
526e9f490bSmbalmer 
536e9f490bSmbalmer __dead void
usage(void)546e9f490bSmbalmer usage(void)
556e9f490bSmbalmer {
566e9f490bSmbalmer 	extern char *__progname;
576e9f490bSmbalmer 
58330f45acSmbalmer 	fprintf(stderr, "usage: %s [-27dehmop] [-s baudrate] "
596e9f490bSmbalmer 	    "[-t cond] discipline device\n", __progname);
606e9f490bSmbalmer 	exit(1);
616e9f490bSmbalmer }
626e9f490bSmbalmer 
63330f45acSmbalmer /* relay data between two file descriptors */
64330f45acSmbalmer void
relay(int device,int pty)65330f45acSmbalmer relay(int device, int pty)
66330f45acSmbalmer {
67330f45acSmbalmer 	struct pollfd pfd[2];
68330f45acSmbalmer 	int nfds, n, nread;
69330f45acSmbalmer 	char buf[128];
70330f45acSmbalmer 
71330f45acSmbalmer 	pfd[0].fd = device;
72330f45acSmbalmer 	pfd[1].fd = pty;
73330f45acSmbalmer 
74330f45acSmbalmer 	while (!dying) {
758e305884Sckuethe 		pfd[0].events = POLLRDNORM;
768e305884Sckuethe 		pfd[1].events = POLLRDNORM;
77330f45acSmbalmer 		nfds = poll(pfd, 2, INFTIM);
78330f45acSmbalmer 		if (nfds == -1) {
79330f45acSmbalmer 			syslog(LOG_ERR, "polling error");
80330f45acSmbalmer 			exit(1);
81330f45acSmbalmer 		}
82661c4f77Smbalmer 		if (nfds == 0)	/* should not happen */
83330f45acSmbalmer 			continue;
84330f45acSmbalmer 
85661c4f77Smbalmer 		if (pfd[1].revents & POLLHUP) {	/* slave device not connected */
86661c4f77Smbalmer 			sleep(1);
87661c4f77Smbalmer 			continue;
88661c4f77Smbalmer 		}
89661c4f77Smbalmer 
90330f45acSmbalmer 		for (n = 0; n < 2; n++) {
91330f45acSmbalmer 			if (!(pfd[n].revents & POLLRDNORM))
92330f45acSmbalmer 				continue;
93330f45acSmbalmer 
94330f45acSmbalmer 			nread = read(pfd[n].fd, buf, sizeof(buf));
95330f45acSmbalmer 			if (nread == -1) {
96330f45acSmbalmer 				syslog(LOG_ERR, "error reading from %s: %m",
97330f45acSmbalmer 				    n ? "pty" : "device");
98330f45acSmbalmer 				exit(1);
99330f45acSmbalmer 			}
100330f45acSmbalmer 			if (nread == 0) {
101330f45acSmbalmer 				syslog(LOG_ERR, "eof during read from %s: %m",
102330f45acSmbalmer 				     n ? "pty" : "device");
103330f45acSmbalmer 				exit(1);
104330f45acSmbalmer 			}
105330f45acSmbalmer 			atomicio(vwrite, pfd[1 - n].fd, buf, nread);
106330f45acSmbalmer 		}
107330f45acSmbalmer 	}
108330f45acSmbalmer }
109330f45acSmbalmer 
1106e9f490bSmbalmer int
main(int argc,char * argv[])1116e9f490bSmbalmer main(int argc, char *argv[])
1126e9f490bSmbalmer {
1136e9f490bSmbalmer 	struct termios tty;
1146e9f490bSmbalmer 	struct tstamps tstamps;
1156e9f490bSmbalmer 	const char *errstr;
1166e9f490bSmbalmer 	sigset_t sigset;
1176e9f490bSmbalmer 	pid_t ppid;
118330f45acSmbalmer 	int ch, fd, master = -1, slave, pty = 0, ldisc, nodaemon = 0;
11997189cdcSmbalmer 	int bits = 0, parity = 0, stop = 0, flowcl = 0, hupcl = 1;
120a8fe7cfbSmbalmer 	speed_t speed = 0;
121330f45acSmbalmer 	char devn[32], ptyn[32], *dev, *disc;
1226e9f490bSmbalmer 
1236e9f490bSmbalmer 	tstamps.ts_set = tstamps.ts_clr = 0;
1246e9f490bSmbalmer 
1256e9f490bSmbalmer 	if ((ppid = getppid()) == 1)
1266e9f490bSmbalmer 		nodaemon = 1;
1276e9f490bSmbalmer 
128330f45acSmbalmer 	while ((ch = getopt(argc, argv, "27dehmops:t:")) != -1) {
1296e9f490bSmbalmer 		switch (ch) {
13097189cdcSmbalmer 		case '2':
13197189cdcSmbalmer 			stop = 2;
13297189cdcSmbalmer 			break;
1336e9f490bSmbalmer 		case '7':
134a8fe7cfbSmbalmer 			bits = 7;
1356e9f490bSmbalmer 			break;
1366e9f490bSmbalmer 		case 'd':
1376e9f490bSmbalmer 			nodaemon = 1;
1386e9f490bSmbalmer 			break;
1396e9f490bSmbalmer 		case 'e':
140a8fe7cfbSmbalmer 			parity = 'e';
1416e9f490bSmbalmer 			break;
1426e9f490bSmbalmer 		case 'h':
143a8fe7cfbSmbalmer 			flowcl = 1;
1446e9f490bSmbalmer 			break;
1456e9f490bSmbalmer 		case 'm':
146a8fe7cfbSmbalmer 			hupcl = 0;
1476e9f490bSmbalmer 			break;
1486e9f490bSmbalmer 		case 'o':
149a8fe7cfbSmbalmer 			parity = 'o';
1506e9f490bSmbalmer 			break;
151330f45acSmbalmer 		case 'p':
152330f45acSmbalmer 			pty = 1;
153330f45acSmbalmer 			break;
1546e9f490bSmbalmer 		case 's':
155a8fe7cfbSmbalmer 			speed = (speed_t)strtonum(optarg, 0, UINT_MAX, &errstr);
1566e9f490bSmbalmer 			if (errstr) {
1576e9f490bSmbalmer 				if (ppid != 1)
1586e9f490bSmbalmer 					errx(1,  "speed is %s: %s", errstr,
1596e9f490bSmbalmer 					    optarg);
1606e9f490bSmbalmer 				else
1616e9f490bSmbalmer 					goto bail_out;
1626e9f490bSmbalmer 			}
1636e9f490bSmbalmer 			break;
1646e9f490bSmbalmer 		case 't':
1656e9f490bSmbalmer 			if (!strcasecmp(optarg, "dcd"))
1666e9f490bSmbalmer 				tstamps.ts_set |= TIOCM_CAR;
1676e9f490bSmbalmer 			else if (!strcasecmp(optarg, "!dcd"))
1686e9f490bSmbalmer 				tstamps.ts_clr |= TIOCM_CAR;
1696e9f490bSmbalmer 			else if (!strcasecmp(optarg, "cts"))
1706e9f490bSmbalmer 				tstamps.ts_set |= TIOCM_CTS;
1716e9f490bSmbalmer 			else if (!strcasecmp(optarg, "!cts"))
1726e9f490bSmbalmer 				tstamps.ts_clr |= TIOCM_CTS;
1736e9f490bSmbalmer 			else {
1746e9f490bSmbalmer 				if (ppid != 1)
1756e9f490bSmbalmer 					errx(1, "'%s' not supported for "
1766e9f490bSmbalmer 					    "timestamping", optarg);
1776e9f490bSmbalmer 				else
1786e9f490bSmbalmer 					goto bail_out;
1796e9f490bSmbalmer 			}
1806e9f490bSmbalmer 			break;
1816e9f490bSmbalmer 		default:
1826e9f490bSmbalmer 			if (ppid != -1)
1836e9f490bSmbalmer 				usage();
1846e9f490bSmbalmer 		}
1856e9f490bSmbalmer 	}
1866e9f490bSmbalmer 	argc -= optind;
1876e9f490bSmbalmer 	argv += optind;
1886e9f490bSmbalmer 
1896e9f490bSmbalmer 	if (ppid != 1 && argc != 2)
1906e9f490bSmbalmer 		usage();
1916e9f490bSmbalmer 
1926e9f490bSmbalmer 	disc = *argv++;
1936e9f490bSmbalmer 	dev = *argv;
1946e9f490bSmbalmer 	if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) {
195330f45acSmbalmer 		(void)snprintf(devn, sizeof(devn), "%s%s", _PATH_DEV, dev);
196c0868ea5Sderaadt 		dev = devn;
1976e9f490bSmbalmer 	}
1986e9f490bSmbalmer 
1995a184b84Smpi 	if (!strcmp(disc, "nmea")) {
2006e9f490bSmbalmer 		ldisc = NMEADISC;
201a8fe7cfbSmbalmer 		if (speed == 0)
202a8fe7cfbSmbalmer 			speed = B4800;	/* default is 4800 baud for nmea */
20397189cdcSmbalmer 	} else if (!strcmp(disc, "msts")) {
20497189cdcSmbalmer 		ldisc = MSTSDISC;
20561406af6Sstevesk 	} else if (!strcmp(disc, "endrun")) {
20661406af6Sstevesk 		ldisc = ENDRUNDISC;
207a8fe7cfbSmbalmer 	} else {
2086e9f490bSmbalmer 		syslog(LOG_ERR, "unknown line discipline %s", disc);
2096e9f490bSmbalmer 		goto bail_out;
2106e9f490bSmbalmer 	}
2116e9f490bSmbalmer 
212df69c215Sderaadt 	if ((fd = open(dev, O_RDWR)) == -1) {
21311bb8c88Smbalmer 		syslog(LOG_ERR, "can't open %s", dev);
214a8fe7cfbSmbalmer 		goto bail_out;
21511bb8c88Smbalmer 	}
2166e9f490bSmbalmer 
217a8fe7cfbSmbalmer 	/*
218a8fe7cfbSmbalmer 	 * Get the current line attributes, modify only values that are
219a8fe7cfbSmbalmer 	 * either requested on the command line or that are needed by
220a8fe7cfbSmbalmer 	 * the line discipline (e.g. nmea has a default baudrate of
221a8fe7cfbSmbalmer 	 * 4800 instead of 9600).
222a8fe7cfbSmbalmer 	 */
223df69c215Sderaadt 	if (tcgetattr(fd, &tty) == -1) {
2246e9f490bSmbalmer 		if (ppid != 1)
225a8fe7cfbSmbalmer 			warnx("tcgetattr");
2266e9f490bSmbalmer 		goto bail_out;
2276e9f490bSmbalmer 	}
2286e9f490bSmbalmer 
229a8fe7cfbSmbalmer 
230a8fe7cfbSmbalmer 	if (bits == 7) {
231a8fe7cfbSmbalmer 		tty.c_cflag &= ~CS8;
232a8fe7cfbSmbalmer 		tty.c_cflag |= CS7;
233a8fe7cfbSmbalmer 	} else if (bits == 8) {
234a8fe7cfbSmbalmer 		tty.c_cflag &= ~CS7;
235a8fe7cfbSmbalmer 		tty.c_cflag |= CS8;
236a8fe7cfbSmbalmer 	}
237a8fe7cfbSmbalmer 
238a8fe7cfbSmbalmer 	if (parity != 0)
239a8fe7cfbSmbalmer 		tty.c_cflag |= PARENB;
240a8fe7cfbSmbalmer 	if (parity == 'o')
241a8fe7cfbSmbalmer 		tty.c_cflag |= PARODD;
242a8fe7cfbSmbalmer 	else
243a8fe7cfbSmbalmer 		tty.c_cflag &= ~PARODD;
244a8fe7cfbSmbalmer 
24597189cdcSmbalmer 	if (stop == 2)
24697189cdcSmbalmer 		tty.c_cflag |= CSTOPB;
24797189cdcSmbalmer 	else
24897189cdcSmbalmer 		tty.c_cflag &= ~CSTOPB;
24997189cdcSmbalmer 
250a8fe7cfbSmbalmer 	if (flowcl)
251a8fe7cfbSmbalmer 		tty.c_cflag |= CRTSCTS;
252a8fe7cfbSmbalmer 
253a8fe7cfbSmbalmer 	if (hupcl == 0)
254a8fe7cfbSmbalmer 		tty.c_cflag &= ~HUPCL;
255a8fe7cfbSmbalmer 
256a8fe7cfbSmbalmer 	if (speed != 0)
257a8fe7cfbSmbalmer 		cfsetspeed(&tty, speed);
258a8fe7cfbSmbalmer 
2596e9f490bSmbalmer 	/* setup common to all line disciplines */
260df69c215Sderaadt 	if (ioctl(fd, TIOCSDTR, 0) == -1)
2616e9f490bSmbalmer 		warn("TIOCSDTR");
262df69c215Sderaadt 	if (ioctl(fd, TIOCSETD, &ldisc) == -1) {
263330f45acSmbalmer 		syslog(LOG_ERR, "can't attach %s line discipline on %s", disc,
2646e9f490bSmbalmer 		    dev);
2656e9f490bSmbalmer 		goto bail_out;
2666e9f490bSmbalmer 	}
2676e9f490bSmbalmer 
2686e9f490bSmbalmer 	/* line discpline specific setup */
2696e9f490bSmbalmer 	switch (ldisc) {
2706e9f490bSmbalmer 	case NMEADISC:
271560889b3Sguenther 	case MSTSDISC:
27261406af6Sstevesk 	case ENDRUNDISC:
273df69c215Sderaadt 		if (ioctl(fd, TIOCSTSTAMP, &tstamps) == -1) {
2746e9f490bSmbalmer 			warnx("TIOCSTSTAMP");
2756e9f490bSmbalmer 			goto bail_out;
2766e9f490bSmbalmer 		}
277aabbc470Sckuethe 		tty.c_cflag |= CLOCAL;
278a8fe7cfbSmbalmer 		tty.c_iflag = 0;
279a8fe7cfbSmbalmer 		tty.c_lflag = 0;
280a8fe7cfbSmbalmer 		tty.c_oflag = 0;
281a8fe7cfbSmbalmer 		tty.c_cc[VMIN] = 1;
282a8fe7cfbSmbalmer 		tty.c_cc[VTIME] = 0;
283a8fe7cfbSmbalmer 		break;
284a8fe7cfbSmbalmer 	}
285a8fe7cfbSmbalmer 
286a8fe7cfbSmbalmer 	/* finally set the line attributes */
287df69c215Sderaadt 	if (tcsetattr(fd, TCSADRAIN, &tty) == -1) {
288a8fe7cfbSmbalmer 		if (ppid != 1)
289a8fe7cfbSmbalmer 			warnx("tcsetattr");
290a8fe7cfbSmbalmer 		goto bail_out;
2916e9f490bSmbalmer 	}
2926e9f490bSmbalmer 
293330f45acSmbalmer 	/*
294330f45acSmbalmer 	 * open a pty(4) pair to pass the data if the -p option has been
295330f45acSmbalmer 	 * given on the commandline.
296330f45acSmbalmer 	 */
297330f45acSmbalmer 	if (pty) {
298330f45acSmbalmer 		if (openpty(&master, &slave, ptyn, NULL, NULL))
299330f45acSmbalmer 			errx(1, "can't open a pty");
300330f45acSmbalmer 		close(slave);
301330f45acSmbalmer 		printf("%s\n", ptyn);
302a6a6c57eSmbalmer 		fflush(stdout);
303330f45acSmbalmer 	}
304330f45acSmbalmer 	if (nodaemon)
305330f45acSmbalmer 		openlog("ldattach", LOG_PID | LOG_CONS | LOG_PERROR,
306330f45acSmbalmer 		    LOG_DAEMON);
307330f45acSmbalmer 	else {
308330f45acSmbalmer 		openlog("ldattach", LOG_PID | LOG_CONS, LOG_DAEMON);
309330f45acSmbalmer 		if (daemon(0, 0))
3106e9f490bSmbalmer 			errx(1, "can't daemonize");
311330f45acSmbalmer 	}
3126e9f490bSmbalmer 
31311bb8c88Smbalmer 	syslog(LOG_INFO, "attach %s on %s", disc, dev);
3146e9f490bSmbalmer 	signal(SIGHUP, coroner);
3156e9f490bSmbalmer 	signal(SIGTERM, coroner);
3166e9f490bSmbalmer 
317330f45acSmbalmer 	if (master != -1) {
318330f45acSmbalmer 		syslog(LOG_INFO, "passing data to %s", ptyn);
319330f45acSmbalmer 		relay(fd, master);
320330f45acSmbalmer 	} else {
3216e9f490bSmbalmer 		sigemptyset(&sigset);
322330f45acSmbalmer 
3236e9f490bSmbalmer 		while (!dying)
3246e9f490bSmbalmer 			sigsuspend(&sigset);
325330f45acSmbalmer 	}
3266e9f490bSmbalmer 
3276e9f490bSmbalmer bail_out:
3286e9f490bSmbalmer 	if (ppid == 1)
3296e9f490bSmbalmer 		sleep(30);	/* delay restart when called from init */
3306e9f490bSmbalmer 
3316e9f490bSmbalmer 	return 0;
3326e9f490bSmbalmer }
3336e9f490bSmbalmer 
3346e9f490bSmbalmer void
coroner(int useless)3356e9f490bSmbalmer coroner(int useless)
3366e9f490bSmbalmer {
3376e9f490bSmbalmer 	dying = 1;
3386e9f490bSmbalmer }
339