sshpty.c revision 76260
1/*
2 * Author: Tatu Ylonen <ylo@cs.hut.fi>
3 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
4 *                    All rights reserved
5 * Allocating a pseudo-terminal, and making it the controlling tty.
6 *
7 * As far as I am concerned, the code I have written for this software
8 * can be used freely for any purpose.  Any derived versions of this
9 * software must be clearly marked as such, and if the derived work is
10 * incompatible with the protocol description in the RFC file, it must be
11 * called by a name other than "ssh" or "Secure Shell".
12 */
13
14#include "includes.h"
15RCSID("$OpenBSD: sshpty.c,v 1.1 2001/03/04 01:46:30 djm Exp $");
16
17#include <util.h>
18#include "sshpty.h"
19#include "log.h"
20
21/* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */
22#if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY)
23#undef HAVE_DEV_PTMX
24#endif
25
26#ifndef O_NOCTTY
27#define O_NOCTTY 0
28#endif
29
30/*
31 * Allocates and opens a pty.  Returns 0 if no pty could be allocated, or
32 * nonzero if a pty was successfully allocated.  On success, open file
33 * descriptors for the pty and tty sides and the name of the tty side are
34 * returned (the buffer must be able to hold at least 64 characters).
35 */
36
37int
38pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen)
39{
40#if defined(HAVE_OPENPTY) || defined(BSD4_4)
41	/* openpty(3) exists in OSF/1 and some other os'es */
42	char buf[64];
43	int i;
44
45	i = openpty(ptyfd, ttyfd, buf, NULL, NULL);
46	if (i < 0) {
47		error("openpty: %.100s", strerror(errno));
48		return 0;
49	}
50	strlcpy(namebuf, buf, namebuflen);	/* possible truncation */
51	return 1;
52#else /* HAVE_OPENPTY */
53#ifdef HAVE__GETPTY
54	/*
55	 * _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more
56	 * pty's automagically when needed
57	 */
58	char *slave;
59
60	slave = _getpty(ptyfd, O_RDWR, 0622, 0);
61	if (slave == NULL) {
62		error("_getpty: %.100s", strerror(errno));
63		return 0;
64	}
65	strlcpy(namebuf, slave, namebuflen);
66	/* Open the slave side. */
67	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
68	if (*ttyfd < 0) {
69		error("%.200s: %.100s", namebuf, strerror(errno));
70		close(*ptyfd);
71		return 0;
72	}
73	return 1;
74#else /* HAVE__GETPTY */
75#ifdef HAVE_DEV_PTMX
76	/*
77	 * This code is used e.g. on Solaris 2.x.  (Note that Solaris 2.3
78	 * also has bsd-style ptys, but they simply do not work.)
79	 */
80	int ptm;
81	char *pts;
82
83	ptm = open("/dev/ptmx", O_RDWR | O_NOCTTY);
84	if (ptm < 0) {
85		error("/dev/ptmx: %.100s", strerror(errno));
86		return 0;
87	}
88	if (grantpt(ptm) < 0) {
89		error("grantpt: %.100s", strerror(errno));
90		return 0;
91	}
92	if (unlockpt(ptm) < 0) {
93		error("unlockpt: %.100s", strerror(errno));
94		return 0;
95	}
96	pts = ptsname(ptm);
97	if (pts == NULL)
98		error("Slave pty side name could not be obtained.");
99	strlcpy(namebuf, pts, namebuflen);
100	*ptyfd = ptm;
101
102	/* Open the slave side. */
103	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
104	if (*ttyfd < 0) {
105		error("%.100s: %.100s", namebuf, strerror(errno));
106		close(*ptyfd);
107		return 0;
108	}
109	/* Push the appropriate streams modules, as described in Solaris pts(7). */
110	if (ioctl(*ttyfd, I_PUSH, "ptem") < 0)
111		error("ioctl I_PUSH ptem: %.100s", strerror(errno));
112	if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0)
113		error("ioctl I_PUSH ldterm: %.100s", strerror(errno));
114	if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0)
115		error("ioctl I_PUSH ttcompat: %.100s", strerror(errno));
116	return 1;
117#else /* HAVE_DEV_PTMX */
118#ifdef HAVE_DEV_PTS_AND_PTC
119	/* AIX-style pty code. */
120	const char *name;
121
122	*ptyfd = open("/dev/ptc", O_RDWR | O_NOCTTY);
123	if (*ptyfd < 0) {
124		error("Could not open /dev/ptc: %.100s", strerror(errno));
125		return 0;
126	}
127	name = ttyname(*ptyfd);
128	if (!name)
129		fatal("Open of /dev/ptc returns device for which ttyname fails.");
130	strlcpy(namebuf, name, namebuflen);
131	*ttyfd = open(name, O_RDWR | O_NOCTTY);
132	if (*ttyfd < 0) {
133		error("Could not open pty slave side %.100s: %.100s",
134		      name, strerror(errno));
135		close(*ptyfd);
136		return 0;
137	}
138	return 1;
139#else /* HAVE_DEV_PTS_AND_PTC */
140	/* BSD-style pty code. */
141	char buf[64];
142	int i;
143	const char *ptymajors = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ";
144	const char *ptyminors = "0123456789abcdef";
145	int num_minors = strlen(ptyminors);
146	int num_ptys = strlen(ptymajors) * num_minors;
147
148	for (i = 0; i < num_ptys; i++) {
149		snprintf(buf, sizeof buf, "/dev/pty%c%c", ptymajors[i / num_minors],
150			 ptyminors[i % num_minors]);
151		*ptyfd = open(buf, O_RDWR | O_NOCTTY);
152		if (*ptyfd < 0)
153			continue;
154		snprintf(namebuf, namebuflen, "/dev/tty%c%c",
155		    ptymajors[i / num_minors], ptyminors[i % num_minors]);
156
157		/* Open the slave side. */
158		*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
159		if (*ttyfd < 0) {
160			error("%.100s: %.100s", namebuf, strerror(errno));
161			close(*ptyfd);
162			return 0;
163		}
164		return 1;
165	}
166	return 0;
167#endif /* HAVE_DEV_PTS_AND_PTC */
168#endif /* HAVE_DEV_PTMX */
169#endif /* HAVE__GETPTY */
170#endif /* HAVE_OPENPTY */
171}
172
173/* Releases the tty.  Its ownership is returned to root, and permissions to 0666. */
174
175void
176pty_release(const char *ttyname)
177{
178	if (chown(ttyname, (uid_t) 0, (gid_t) 0) < 0)
179		error("chown %.100s 0 0 failed: %.100s", ttyname, strerror(errno));
180	if (chmod(ttyname, (mode_t) 0666) < 0)
181		error("chmod %.100s 0666 failed: %.100s", ttyname, strerror(errno));
182}
183
184/* Makes the tty the processes controlling tty and sets it to sane modes. */
185
186void
187pty_make_controlling_tty(int *ttyfd, const char *ttyname)
188{
189	int fd;
190
191	/* First disconnect from the old controlling tty. */
192#ifdef TIOCNOTTY
193	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
194	if (fd >= 0) {
195		(void) ioctl(fd, TIOCNOTTY, NULL);
196		close(fd);
197	}
198#endif /* TIOCNOTTY */
199	if (setsid() < 0)
200		error("setsid: %.100s", strerror(errno));
201
202	/*
203	 * Verify that we are successfully disconnected from the controlling
204	 * tty.
205	 */
206	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
207	if (fd >= 0) {
208		error("Failed to disconnect from controlling tty.");
209		close(fd);
210	}
211	/* Make it our controlling tty. */
212#ifdef TIOCSCTTY
213	debug("Setting controlling tty using TIOCSCTTY.");
214	if (ioctl(*ttyfd, TIOCSCTTY, NULL) < 0)
215		error("ioctl(TIOCSCTTY): %.100s", strerror(errno));
216#endif /* TIOCSCTTY */
217	fd = open(ttyname, O_RDWR);
218	if (fd < 0)
219		error("%.100s: %.100s", ttyname, strerror(errno));
220	else
221		close(fd);
222
223	/* Verify that we now have a controlling tty. */
224	fd = open(_PATH_TTY, O_WRONLY);
225	if (fd < 0)
226		error("open /dev/tty failed - could not set controlling tty: %.100s",
227		      strerror(errno));
228	else {
229		close(fd);
230	}
231}
232
233/* Changes the window size associated with the pty. */
234
235void
236pty_change_window_size(int ptyfd, int row, int col,
237		       int xpixel, int ypixel)
238{
239	struct winsize w;
240	w.ws_row = row;
241	w.ws_col = col;
242	w.ws_xpixel = xpixel;
243	w.ws_ypixel = ypixel;
244	(void) ioctl(ptyfd, TIOCSWINSZ, &w);
245}
246
247void
248pty_setowner(struct passwd *pw, const char *ttyname)
249{
250	struct group *grp;
251	gid_t gid;
252	mode_t mode;
253	struct stat st;
254
255	/* Determine the group to make the owner of the tty. */
256	grp = getgrnam("tty");
257	if (grp) {
258		gid = grp->gr_gid;
259		mode = S_IRUSR | S_IWUSR | S_IWGRP;
260	} else {
261		gid = pw->pw_gid;
262		mode = S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH;
263	}
264
265	/*
266	 * Change owner and mode of the tty as required.
267	 * Warn but continue if filesystem is read-only and the uids match.
268	 */
269	if (stat(ttyname, &st))
270		fatal("stat(%.100s) failed: %.100s", ttyname,
271		    strerror(errno));
272
273	if (st.st_uid != pw->pw_uid || st.st_gid != gid) {
274		if (chown(ttyname, pw->pw_uid, gid) < 0) {
275			if (errno == EROFS && st.st_uid == pw->pw_uid)
276				error("chown(%.100s, %d, %d) failed: %.100s",
277				      ttyname, pw->pw_uid, gid,
278				      strerror(errno));
279			else
280				fatal("chown(%.100s, %d, %d) failed: %.100s",
281				      ttyname, pw->pw_uid, gid,
282				      strerror(errno));
283		}
284	}
285
286	if ((st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != mode) {
287		if (chmod(ttyname, mode) < 0) {
288			if (errno == EROFS &&
289			    (st.st_mode & (S_IRGRP | S_IROTH)) == 0)
290				error("chmod(%.100s, 0%o) failed: %.100s",
291				      ttyname, mode, strerror(errno));
292			else
293				fatal("chmod(%.100s, 0%o) failed: %.100s",
294				      ttyname, mode, strerror(errno));
295		}
296	}
297}
298