xref: /freebsd/crypto/openssh/sshpty.c (revision af12a3e74a283727a2aa141f2b4c68ff10fe8dc6)
11e8db6e2SBrian Feldman /*
21e8db6e2SBrian Feldman  * Author: Tatu Ylonen <ylo@cs.hut.fi>
31e8db6e2SBrian Feldman  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
41e8db6e2SBrian Feldman  *                    All rights reserved
51e8db6e2SBrian Feldman  * Allocating a pseudo-terminal, and making it the controlling tty.
61e8db6e2SBrian Feldman  *
71e8db6e2SBrian Feldman  * As far as I am concerned, the code I have written for this software
81e8db6e2SBrian Feldman  * can be used freely for any purpose.  Any derived versions of this
91e8db6e2SBrian Feldman  * software must be clearly marked as such, and if the derived work is
101e8db6e2SBrian Feldman  * incompatible with the protocol description in the RFC file, it must be
111e8db6e2SBrian Feldman  * called by a name other than "ssh" or "Secure Shell".
121e8db6e2SBrian Feldman  */
131e8db6e2SBrian Feldman 
141e8db6e2SBrian Feldman #include "includes.h"
15af12a3e7SDag-Erling Smørgrav RCSID("$OpenBSD: sshpty.c,v 1.4 2001/12/19 07:18:56 deraadt Exp $");
16ca3176e7SBrian Feldman RCSID("$FreeBSD$");
171e8db6e2SBrian Feldman 
18ca3176e7SBrian Feldman #include <libutil.h>
191e8db6e2SBrian Feldman #include "sshpty.h"
201e8db6e2SBrian Feldman #include "log.h"
211e8db6e2SBrian Feldman 
221e8db6e2SBrian Feldman /* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */
231e8db6e2SBrian Feldman #if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY)
241e8db6e2SBrian Feldman #undef HAVE_DEV_PTMX
251e8db6e2SBrian Feldman #endif
261e8db6e2SBrian Feldman 
271e8db6e2SBrian Feldman #ifndef O_NOCTTY
281e8db6e2SBrian Feldman #define O_NOCTTY 0
291e8db6e2SBrian Feldman #endif
301e8db6e2SBrian Feldman 
311e8db6e2SBrian Feldman /*
321e8db6e2SBrian Feldman  * Allocates and opens a pty.  Returns 0 if no pty could be allocated, or
331e8db6e2SBrian Feldman  * nonzero if a pty was successfully allocated.  On success, open file
341e8db6e2SBrian Feldman  * descriptors for the pty and tty sides and the name of the tty side are
351e8db6e2SBrian Feldman  * returned (the buffer must be able to hold at least 64 characters).
361e8db6e2SBrian Feldman  */
371e8db6e2SBrian Feldman 
381e8db6e2SBrian Feldman int
391e8db6e2SBrian Feldman pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen)
401e8db6e2SBrian Feldman {
411e8db6e2SBrian Feldman #if defined(HAVE_OPENPTY) || defined(BSD4_4)
421e8db6e2SBrian Feldman 	/* openpty(3) exists in OSF/1 and some other os'es */
431e8db6e2SBrian Feldman 	char buf[64];
441e8db6e2SBrian Feldman 	int i;
451e8db6e2SBrian Feldman 
461e8db6e2SBrian Feldman 	i = openpty(ptyfd, ttyfd, buf, NULL, NULL);
471e8db6e2SBrian Feldman 	if (i < 0) {
481e8db6e2SBrian Feldman 		error("openpty: %.100s", strerror(errno));
491e8db6e2SBrian Feldman 		return 0;
501e8db6e2SBrian Feldman 	}
511e8db6e2SBrian Feldman 	strlcpy(namebuf, buf, namebuflen);	/* possible truncation */
521e8db6e2SBrian Feldman 	return 1;
531e8db6e2SBrian Feldman #else /* HAVE_OPENPTY */
541e8db6e2SBrian Feldman #ifdef HAVE__GETPTY
551e8db6e2SBrian Feldman 	/*
561e8db6e2SBrian Feldman 	 * _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more
571e8db6e2SBrian Feldman 	 * pty's automagically when needed
581e8db6e2SBrian Feldman 	 */
591e8db6e2SBrian Feldman 	char *slave;
601e8db6e2SBrian Feldman 
611e8db6e2SBrian Feldman 	slave = _getpty(ptyfd, O_RDWR, 0622, 0);
621e8db6e2SBrian Feldman 	if (slave == NULL) {
631e8db6e2SBrian Feldman 		error("_getpty: %.100s", strerror(errno));
641e8db6e2SBrian Feldman 		return 0;
651e8db6e2SBrian Feldman 	}
661e8db6e2SBrian Feldman 	strlcpy(namebuf, slave, namebuflen);
671e8db6e2SBrian Feldman 	/* Open the slave side. */
681e8db6e2SBrian Feldman 	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
691e8db6e2SBrian Feldman 	if (*ttyfd < 0) {
701e8db6e2SBrian Feldman 		error("%.200s: %.100s", namebuf, strerror(errno));
711e8db6e2SBrian Feldman 		close(*ptyfd);
721e8db6e2SBrian Feldman 		return 0;
731e8db6e2SBrian Feldman 	}
741e8db6e2SBrian Feldman 	return 1;
751e8db6e2SBrian Feldman #else /* HAVE__GETPTY */
761e8db6e2SBrian Feldman #ifdef HAVE_DEV_PTMX
771e8db6e2SBrian Feldman 	/*
781e8db6e2SBrian Feldman 	 * This code is used e.g. on Solaris 2.x.  (Note that Solaris 2.3
791e8db6e2SBrian Feldman 	 * also has bsd-style ptys, but they simply do not work.)
801e8db6e2SBrian Feldman 	 */
811e8db6e2SBrian Feldman 	int ptm;
821e8db6e2SBrian Feldman 	char *pts;
831e8db6e2SBrian Feldman 
841e8db6e2SBrian Feldman 	ptm = open("/dev/ptmx", O_RDWR | O_NOCTTY);
851e8db6e2SBrian Feldman 	if (ptm < 0) {
861e8db6e2SBrian Feldman 		error("/dev/ptmx: %.100s", strerror(errno));
871e8db6e2SBrian Feldman 		return 0;
881e8db6e2SBrian Feldman 	}
891e8db6e2SBrian Feldman 	if (grantpt(ptm) < 0) {
901e8db6e2SBrian Feldman 		error("grantpt: %.100s", strerror(errno));
911e8db6e2SBrian Feldman 		return 0;
921e8db6e2SBrian Feldman 	}
931e8db6e2SBrian Feldman 	if (unlockpt(ptm) < 0) {
941e8db6e2SBrian Feldman 		error("unlockpt: %.100s", strerror(errno));
951e8db6e2SBrian Feldman 		return 0;
961e8db6e2SBrian Feldman 	}
971e8db6e2SBrian Feldman 	pts = ptsname(ptm);
981e8db6e2SBrian Feldman 	if (pts == NULL)
991e8db6e2SBrian Feldman 		error("Slave pty side name could not be obtained.");
1001e8db6e2SBrian Feldman 	strlcpy(namebuf, pts, namebuflen);
1011e8db6e2SBrian Feldman 	*ptyfd = ptm;
1021e8db6e2SBrian Feldman 
1031e8db6e2SBrian Feldman 	/* Open the slave side. */
1041e8db6e2SBrian Feldman 	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
1051e8db6e2SBrian Feldman 	if (*ttyfd < 0) {
1061e8db6e2SBrian Feldman 		error("%.100s: %.100s", namebuf, strerror(errno));
1071e8db6e2SBrian Feldman 		close(*ptyfd);
1081e8db6e2SBrian Feldman 		return 0;
1091e8db6e2SBrian Feldman 	}
1101e8db6e2SBrian Feldman 	/* Push the appropriate streams modules, as described in Solaris pts(7). */
1111e8db6e2SBrian Feldman 	if (ioctl(*ttyfd, I_PUSH, "ptem") < 0)
1121e8db6e2SBrian Feldman 		error("ioctl I_PUSH ptem: %.100s", strerror(errno));
1131e8db6e2SBrian Feldman 	if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0)
1141e8db6e2SBrian Feldman 		error("ioctl I_PUSH ldterm: %.100s", strerror(errno));
1151e8db6e2SBrian Feldman 	if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0)
1161e8db6e2SBrian Feldman 		error("ioctl I_PUSH ttcompat: %.100s", strerror(errno));
1171e8db6e2SBrian Feldman 	return 1;
1181e8db6e2SBrian Feldman #else /* HAVE_DEV_PTMX */
1191e8db6e2SBrian Feldman #ifdef HAVE_DEV_PTS_AND_PTC
1201e8db6e2SBrian Feldman 	/* AIX-style pty code. */
1211e8db6e2SBrian Feldman 	const char *name;
1221e8db6e2SBrian Feldman 
1231e8db6e2SBrian Feldman 	*ptyfd = open("/dev/ptc", O_RDWR | O_NOCTTY);
1241e8db6e2SBrian Feldman 	if (*ptyfd < 0) {
1251e8db6e2SBrian Feldman 		error("Could not open /dev/ptc: %.100s", strerror(errno));
1261e8db6e2SBrian Feldman 		return 0;
1271e8db6e2SBrian Feldman 	}
1281e8db6e2SBrian Feldman 	name = ttyname(*ptyfd);
1291e8db6e2SBrian Feldman 	if (!name)
1301e8db6e2SBrian Feldman 		fatal("Open of /dev/ptc returns device for which ttyname fails.");
1311e8db6e2SBrian Feldman 	strlcpy(namebuf, name, namebuflen);
1321e8db6e2SBrian Feldman 	*ttyfd = open(name, O_RDWR | O_NOCTTY);
1331e8db6e2SBrian Feldman 	if (*ttyfd < 0) {
1341e8db6e2SBrian Feldman 		error("Could not open pty slave side %.100s: %.100s",
1351e8db6e2SBrian Feldman 		    name, strerror(errno));
1361e8db6e2SBrian Feldman 		close(*ptyfd);
1371e8db6e2SBrian Feldman 		return 0;
1381e8db6e2SBrian Feldman 	}
1391e8db6e2SBrian Feldman 	return 1;
1401e8db6e2SBrian Feldman #else /* HAVE_DEV_PTS_AND_PTC */
1411e8db6e2SBrian Feldman 	/* BSD-style pty code. */
1421e8db6e2SBrian Feldman 	char buf[64];
1431e8db6e2SBrian Feldman 	int i;
1441e8db6e2SBrian Feldman 	const char *ptymajors = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ";
1451e8db6e2SBrian Feldman 	const char *ptyminors = "0123456789abcdef";
1461e8db6e2SBrian Feldman 	int num_minors = strlen(ptyminors);
1471e8db6e2SBrian Feldman 	int num_ptys = strlen(ptymajors) * num_minors;
1481e8db6e2SBrian Feldman 
1491e8db6e2SBrian Feldman 	for (i = 0; i < num_ptys; i++) {
1501e8db6e2SBrian Feldman 		snprintf(buf, sizeof buf, "/dev/pty%c%c", ptymajors[i / num_minors],
1511e8db6e2SBrian Feldman 			 ptyminors[i % num_minors]);
1521e8db6e2SBrian Feldman 		*ptyfd = open(buf, O_RDWR | O_NOCTTY);
1531e8db6e2SBrian Feldman 		if (*ptyfd < 0)
1541e8db6e2SBrian Feldman 			continue;
1551e8db6e2SBrian Feldman 		snprintf(namebuf, namebuflen, "/dev/tty%c%c",
1561e8db6e2SBrian Feldman 		    ptymajors[i / num_minors], ptyminors[i % num_minors]);
1571e8db6e2SBrian Feldman 
1581e8db6e2SBrian Feldman 		/* Open the slave side. */
1591e8db6e2SBrian Feldman 		*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
1601e8db6e2SBrian Feldman 		if (*ttyfd < 0) {
1611e8db6e2SBrian Feldman 			error("%.100s: %.100s", namebuf, strerror(errno));
1621e8db6e2SBrian Feldman 			close(*ptyfd);
1631e8db6e2SBrian Feldman 			return 0;
1641e8db6e2SBrian Feldman 		}
1651e8db6e2SBrian Feldman 		return 1;
1661e8db6e2SBrian Feldman 	}
1671e8db6e2SBrian Feldman 	return 0;
1681e8db6e2SBrian Feldman #endif /* HAVE_DEV_PTS_AND_PTC */
1691e8db6e2SBrian Feldman #endif /* HAVE_DEV_PTMX */
1701e8db6e2SBrian Feldman #endif /* HAVE__GETPTY */
1711e8db6e2SBrian Feldman #endif /* HAVE_OPENPTY */
1721e8db6e2SBrian Feldman }
1731e8db6e2SBrian Feldman 
1741e8db6e2SBrian Feldman /* Releases the tty.  Its ownership is returned to root, and permissions to 0666. */
1751e8db6e2SBrian Feldman 
1761e8db6e2SBrian Feldman void
1771e8db6e2SBrian Feldman pty_release(const char *ttyname)
1781e8db6e2SBrian Feldman {
1791e8db6e2SBrian Feldman 	if (chown(ttyname, (uid_t) 0, (gid_t) 0) < 0)
1801e8db6e2SBrian Feldman 		error("chown %.100s 0 0 failed: %.100s", ttyname, strerror(errno));
1811e8db6e2SBrian Feldman 	if (chmod(ttyname, (mode_t) 0666) < 0)
1821e8db6e2SBrian Feldman 		error("chmod %.100s 0666 failed: %.100s", ttyname, strerror(errno));
1831e8db6e2SBrian Feldman }
1841e8db6e2SBrian Feldman 
1851e8db6e2SBrian Feldman /* Makes the tty the processes controlling tty and sets it to sane modes. */
1861e8db6e2SBrian Feldman 
1871e8db6e2SBrian Feldman void
1881e8db6e2SBrian Feldman pty_make_controlling_tty(int *ttyfd, const char *ttyname)
1891e8db6e2SBrian Feldman {
1901e8db6e2SBrian Feldman 	int fd;
1911e8db6e2SBrian Feldman 
1921e8db6e2SBrian Feldman 	/* First disconnect from the old controlling tty. */
1931e8db6e2SBrian Feldman #ifdef TIOCNOTTY
1941e8db6e2SBrian Feldman 	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
1951e8db6e2SBrian Feldman 	if (fd >= 0) {
1961e8db6e2SBrian Feldman 		(void) ioctl(fd, TIOCNOTTY, NULL);
1971e8db6e2SBrian Feldman 		close(fd);
1981e8db6e2SBrian Feldman 	}
1991e8db6e2SBrian Feldman #endif /* TIOCNOTTY */
2001e8db6e2SBrian Feldman 	if (setsid() < 0)
2011e8db6e2SBrian Feldman 		error("setsid: %.100s", strerror(errno));
2021e8db6e2SBrian Feldman 
2031e8db6e2SBrian Feldman 	/*
2041e8db6e2SBrian Feldman 	 * Verify that we are successfully disconnected from the controlling
2051e8db6e2SBrian Feldman 	 * tty.
2061e8db6e2SBrian Feldman 	 */
2071e8db6e2SBrian Feldman 	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
2081e8db6e2SBrian Feldman 	if (fd >= 0) {
2091e8db6e2SBrian Feldman 		error("Failed to disconnect from controlling tty.");
2101e8db6e2SBrian Feldman 		close(fd);
2111e8db6e2SBrian Feldman 	}
2121e8db6e2SBrian Feldman 	/* Make it our controlling tty. */
2131e8db6e2SBrian Feldman #ifdef TIOCSCTTY
2141e8db6e2SBrian Feldman 	debug("Setting controlling tty using TIOCSCTTY.");
2151e8db6e2SBrian Feldman 	if (ioctl(*ttyfd, TIOCSCTTY, NULL) < 0)
2161e8db6e2SBrian Feldman 		error("ioctl(TIOCSCTTY): %.100s", strerror(errno));
2171e8db6e2SBrian Feldman #endif /* TIOCSCTTY */
2181e8db6e2SBrian Feldman 	fd = open(ttyname, O_RDWR);
2191e8db6e2SBrian Feldman 	if (fd < 0)
2201e8db6e2SBrian Feldman 		error("%.100s: %.100s", ttyname, strerror(errno));
2211e8db6e2SBrian Feldman 	else
2221e8db6e2SBrian Feldman 		close(fd);
2231e8db6e2SBrian Feldman 
2241e8db6e2SBrian Feldman 	/* Verify that we now have a controlling tty. */
2251e8db6e2SBrian Feldman 	fd = open(_PATH_TTY, O_WRONLY);
2261e8db6e2SBrian Feldman 	if (fd < 0)
2271e8db6e2SBrian Feldman 		error("open /dev/tty failed - could not set controlling tty: %.100s",
2281e8db6e2SBrian Feldman 		    strerror(errno));
2291e8db6e2SBrian Feldman 	else {
2301e8db6e2SBrian Feldman 		close(fd);
2311e8db6e2SBrian Feldman 	}
2321e8db6e2SBrian Feldman }
2331e8db6e2SBrian Feldman 
2341e8db6e2SBrian Feldman /* Changes the window size associated with the pty. */
2351e8db6e2SBrian Feldman 
2361e8db6e2SBrian Feldman void
2371e8db6e2SBrian Feldman pty_change_window_size(int ptyfd, int row, int col,
2381e8db6e2SBrian Feldman 	int xpixel, int ypixel)
2391e8db6e2SBrian Feldman {
2401e8db6e2SBrian Feldman 	struct winsize w;
2411e8db6e2SBrian Feldman 	w.ws_row = row;
2421e8db6e2SBrian Feldman 	w.ws_col = col;
2431e8db6e2SBrian Feldman 	w.ws_xpixel = xpixel;
2441e8db6e2SBrian Feldman 	w.ws_ypixel = ypixel;
2451e8db6e2SBrian Feldman 	(void) ioctl(ptyfd, TIOCSWINSZ, &w);
2461e8db6e2SBrian Feldman }
2471e8db6e2SBrian Feldman 
2481e8db6e2SBrian Feldman void
2491e8db6e2SBrian Feldman pty_setowner(struct passwd *pw, const char *ttyname)
2501e8db6e2SBrian Feldman {
2511e8db6e2SBrian Feldman 	struct group *grp;
2521e8db6e2SBrian Feldman 	gid_t gid;
2531e8db6e2SBrian Feldman 	mode_t mode;
2541e8db6e2SBrian Feldman 	struct stat st;
2551e8db6e2SBrian Feldman 
2561e8db6e2SBrian Feldman 	/* Determine the group to make the owner of the tty. */
2571e8db6e2SBrian Feldman 	grp = getgrnam("tty");
2581e8db6e2SBrian Feldman 	if (grp) {
2591e8db6e2SBrian Feldman 		gid = grp->gr_gid;
2601e8db6e2SBrian Feldman 		mode = S_IRUSR | S_IWUSR | S_IWGRP;
2611e8db6e2SBrian Feldman 	} else {
2621e8db6e2SBrian Feldman 		gid = pw->pw_gid;
2631e8db6e2SBrian Feldman 		mode = S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH;
2641e8db6e2SBrian Feldman 	}
2651e8db6e2SBrian Feldman 
2661e8db6e2SBrian Feldman 	/*
2671e8db6e2SBrian Feldman 	 * Change owner and mode of the tty as required.
268af12a3e7SDag-Erling Smørgrav 	 * Warn but continue if filesystem is read-only and the uids match/
269af12a3e7SDag-Erling Smørgrav 	 * tty is owned by root.
2701e8db6e2SBrian Feldman 	 */
2711e8db6e2SBrian Feldman 	if (stat(ttyname, &st))
2721e8db6e2SBrian Feldman 		fatal("stat(%.100s) failed: %.100s", ttyname,
2731e8db6e2SBrian Feldman 		    strerror(errno));
2741e8db6e2SBrian Feldman 
2751e8db6e2SBrian Feldman 	if (st.st_uid != pw->pw_uid || st.st_gid != gid) {
2761e8db6e2SBrian Feldman 		if (chown(ttyname, pw->pw_uid, gid) < 0) {
277af12a3e7SDag-Erling Smørgrav 			if (errno == EROFS &&
278af12a3e7SDag-Erling Smørgrav 			   (st.st_uid == pw->pw_uid || st.st_uid == 0))
2791e8db6e2SBrian Feldman 				error("chown(%.100s, %d, %d) failed: %.100s",
2801e8db6e2SBrian Feldman 				    ttyname, pw->pw_uid, gid,
2811e8db6e2SBrian Feldman 				    strerror(errno));
2821e8db6e2SBrian Feldman 			else
2831e8db6e2SBrian Feldman 				fatal("chown(%.100s, %d, %d) failed: %.100s",
2841e8db6e2SBrian Feldman 				    ttyname, pw->pw_uid, gid,
2851e8db6e2SBrian Feldman 				    strerror(errno));
2861e8db6e2SBrian Feldman 		}
2871e8db6e2SBrian Feldman 	}
2881e8db6e2SBrian Feldman 
2891e8db6e2SBrian Feldman 	if ((st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != mode) {
2901e8db6e2SBrian Feldman 		if (chmod(ttyname, mode) < 0) {
2911e8db6e2SBrian Feldman 			if (errno == EROFS &&
2921e8db6e2SBrian Feldman 			    (st.st_mode & (S_IRGRP | S_IROTH)) == 0)
2931e8db6e2SBrian Feldman 				error("chmod(%.100s, 0%o) failed: %.100s",
2941e8db6e2SBrian Feldman 				    ttyname, mode, strerror(errno));
2951e8db6e2SBrian Feldman 			else
2961e8db6e2SBrian Feldman 				fatal("chmod(%.100s, 0%o) failed: %.100s",
2971e8db6e2SBrian Feldman 				    ttyname, mode, strerror(errno));
2981e8db6e2SBrian Feldman 		}
2991e8db6e2SBrian Feldman 	}
3001e8db6e2SBrian Feldman }
301