1/*
2 * Dropbear - a SSH2 server
3 *
4 * Copied from OpenSSH-3.5p1 source, modified by Matt Johnston 2003
5 *
6 * Author: Tatu Ylonen <ylo@cs.hut.fi>
7 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8 *                    All rights reserved
9 * Allocating a pseudo-terminal, and making it the controlling tty.
10 *
11 * As far as I am concerned, the code I have written for this software
12 * can be used freely for any purpose.  Any derived versions of this
13 * software must be clearly marked as such, and if the derived work is
14 * incompatible with the protocol description in the RFC file, it must be
15 * called by a name other than "ssh" or "Secure Shell".
16 */
17
18/*RCSID("OpenBSD: sshpty.c,v 1.7 2002/06/24 17:57:20 deraadt Exp ");*/
19
20#include "includes.h"
21#include "dbutil.h"
22#include "errno.h"
23#include "sshpty.h"
24
25/* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */
26#if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY)
27#undef HAVE_DEV_PTMX
28#endif
29
30#ifdef HAVE_PTY_H
31# include <pty.h>
32#endif
33#if defined(USE_DEV_PTMX) && defined(HAVE_STROPTS_H)
34# include <stropts.h>
35#endif
36
37#ifndef O_NOCTTY
38#define O_NOCTTY 0
39#endif
40
41/*
42 * Allocates and opens a pty.  Returns 0 if no pty could be allocated, or
43 * nonzero if a pty was successfully allocated.  On success, open file
44 * descriptors for the pty and tty sides and the name of the tty side are
45 * returned (the buffer must be able to hold at least 64 characters).
46 */
47
48int
49pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen)
50{
51#if defined(HAVE_OPENPTY)
52	/* exists in recent (4.4) BSDs and OSF/1 */
53	char *name;
54	int i;
55
56	i = openpty(ptyfd, ttyfd, NULL, NULL, NULL);
57	if (i < 0) {
58		dropbear_log(LOG_WARNING,
59				"pty_allocate: openpty: %.100s", strerror(errno));
60		return 0;
61	}
62	name = ttyname(*ttyfd);
63	if (!name) {
64		dropbear_exit("ttyname fails for openpty device");
65	}
66
67	strlcpy(namebuf, name, namebuflen);	/* possible truncation */
68	return 1;
69#else /* HAVE_OPENPTY */
70#ifdef HAVE__GETPTY
71	/*
72	 * _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more
73	 * pty's automagically when needed
74	 */
75	char *slave;
76
77	slave = _getpty(ptyfd, O_RDWR, 0622, 0);
78	if (slave == NULL) {
79		dropbear_log(LOG_WARNING,
80				"pty_allocate: _getpty: %.100s", strerror(errno));
81		return 0;
82	}
83	strlcpy(namebuf, slave, namebuflen);
84	/* Open the slave side. */
85	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
86	if (*ttyfd < 0) {
87		dropbear_log(LOG_WARNING,
88				"pty_allocate error: ttyftd open error");
89		close(*ptyfd);
90		return 0;
91	}
92	return 1;
93#else /* HAVE__GETPTY */
94#if defined(USE_DEV_PTMX)
95	/*
96	 * This code is used e.g. on Solaris 2.x.  (Note that Solaris 2.3
97	 * also has bsd-style ptys, but they simply do not work.)
98	 *
99	 * Linux systems may have the /dev/ptmx device, but this code won't work.
100	 */
101	int ptm;
102	char *pts;
103
104	ptm = open("/dev/ptmx", O_RDWR | O_NOCTTY);
105	if (ptm < 0) {
106		dropbear_log(LOG_WARNING,
107				"pty_allocate: /dev/ptmx: %.100s", strerror(errno));
108		return 0;
109	}
110	if (grantpt(ptm) < 0) {
111		dropbear_log(LOG_WARNING,
112				"grantpt: %.100s", strerror(errno));
113		return 0;
114	}
115	if (unlockpt(ptm) < 0) {
116		dropbear_log(LOG_WARNING,
117				"unlockpt: %.100s", strerror(errno));
118		return 0;
119	}
120	pts = ptsname(ptm);
121	if (pts == NULL) {
122		dropbear_log(LOG_WARNING,
123				"Slave pty side name could not be obtained.");
124	}
125	strlcpy(namebuf, pts, namebuflen);
126	*ptyfd = ptm;
127
128	/* Open the slave side. */
129	*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
130	if (*ttyfd < 0) {
131		dropbear_log(LOG_ERR,
132			"error opening pts %.100s: %.100s", namebuf, strerror(errno));
133		close(*ptyfd);
134		return 0;
135	}
136#ifndef HAVE_CYGWIN
137	/*
138	 * Push the appropriate streams modules, as described in Solaris pts(7).
139	 * HP-UX pts(7) doesn't have ttcompat module.
140	 */
141	if (ioctl(*ttyfd, I_PUSH, "ptem") < 0) {
142		dropbear_log(LOG_WARNING,
143				"ioctl I_PUSH ptem: %.100s", strerror(errno));
144	}
145	if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0) {
146		dropbear_log(LOG_WARNING,
147			"ioctl I_PUSH ldterm: %.100s", strerror(errno));
148	}
149#ifndef __hpux
150	if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0) {
151		dropbear_log(LOG_WARNING,
152			"ioctl I_PUSH ttcompat: %.100s", strerror(errno));
153	}
154#endif
155#endif
156	return 1;
157#else /* USE_DEV_PTMX */
158#ifdef HAVE_DEV_PTS_AND_PTC
159	/* AIX-style pty code. */
160	const char *name;
161
162	*ptyfd = open("/dev/ptc", O_RDWR | O_NOCTTY);
163	if (*ptyfd < 0) {
164		dropbear_log(LOG_ERR,
165			"Could not open /dev/ptc: %.100s", strerror(errno));
166		return 0;
167	}
168	name = ttyname(*ptyfd);
169	if (!name) {
170		dropbear_exit("ttyname fails for /dev/ptc device");
171	}
172	strlcpy(namebuf, name, namebuflen);
173	*ttyfd = open(name, O_RDWR | O_NOCTTY);
174	if (*ttyfd < 0) {
175		dropbear_log(LOG_ERR,
176			"Could not open pty slave side %.100s: %.100s",
177		    name, strerror(errno));
178		close(*ptyfd);
179		return 0;
180	}
181	return 1;
182#else /* HAVE_DEV_PTS_AND_PTC */
183
184	/* BSD-style pty code. */
185	char buf[64];
186	int i;
187	const char *ptymajors = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ";
188	const char *ptyminors = "0123456789abcdef";
189	int num_minors = strlen(ptyminors);
190	int num_ptys = strlen(ptymajors) * num_minors;
191	struct termios tio;
192
193	for (i = 0; i < num_ptys; i++) {
194		snprintf(buf, sizeof buf, "/dev/pty%c%c", ptymajors[i / num_minors],
195			 ptyminors[i % num_minors]);
196		snprintf(namebuf, namebuflen, "/dev/tty%c%c",
197		    ptymajors[i / num_minors], ptyminors[i % num_minors]);
198
199		*ptyfd = open(buf, O_RDWR | O_NOCTTY);
200		if (*ptyfd < 0) {
201			/* Try SCO style naming */
202			snprintf(buf, sizeof buf, "/dev/ptyp%d", i);
203			snprintf(namebuf, namebuflen, "/dev/ttyp%d", i);
204			*ptyfd = open(buf, O_RDWR | O_NOCTTY);
205			if (*ptyfd < 0) {
206				continue;
207			}
208		}
209
210		/* Open the slave side. */
211		*ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
212		if (*ttyfd < 0) {
213			dropbear_log(LOG_ERR,
214				"pty_allocate: %.100s: %.100s", namebuf, strerror(errno));
215			close(*ptyfd);
216			return 0;
217		}
218		/* set tty modes to a sane state for broken clients */
219		if (tcgetattr(*ptyfd, &tio) < 0) {
220			dropbear_log(LOG_WARNING,
221				"ptyallocate: tty modes failed: %.100s", strerror(errno));
222		} else {
223			tio.c_lflag |= (ECHO | ISIG | ICANON);
224			tio.c_oflag |= (OPOST | ONLCR);
225			tio.c_iflag |= ICRNL;
226
227			/* Set the new modes for the terminal. */
228			if (tcsetattr(*ptyfd, TCSANOW, &tio) < 0) {
229				dropbear_log(LOG_WARNING,
230					"Setting tty modes for pty failed: %.100s",
231					strerror(errno));
232			}
233		}
234
235		return 1;
236	}
237	dropbear_log(LOG_WARNING, "failed to open any /dev/pty?? devices");
238	return 0;
239#endif /* HAVE_DEV_PTS_AND_PTC */
240#endif /* USE_DEV_PTMX */
241#endif /* HAVE__GETPTY */
242#endif /* HAVE_OPENPTY */
243}
244
245/* Releases the tty.  Its ownership is returned to root, and permissions to 0666. */
246
247void
248pty_release(const char *tty_name)
249{
250	if (chown(tty_name, (uid_t) 0, (gid_t) 0) < 0
251			&& (errno != ENOENT)) {
252		dropbear_log(LOG_ERR,
253				"chown %.100s 0 0 failed: %.100s", tty_name, strerror(errno));
254	}
255	if (chmod(tty_name, (mode_t) 0666) < 0
256			&& (errno != ENOENT)) {
257		dropbear_log(LOG_ERR,
258			"chmod %.100s 0666 failed: %.100s", tty_name, strerror(errno));
259	}
260}
261
262/* Makes the tty the processes controlling tty and sets it to sane modes. */
263
264void
265pty_make_controlling_tty(int *ttyfd, const char *tty_name)
266{
267	int fd;
268#ifdef USE_VHANGUP
269	void *old;
270#endif /* USE_VHANGUP */
271
272	/* Solaris has a problem with TIOCNOTTY for a bg process, so
273	 * we disable the signal which would STOP the process - matt */
274	signal(SIGTTOU, SIG_IGN);
275
276	/* First disconnect from the old controlling tty. */
277#ifdef TIOCNOTTY
278	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
279	if (fd >= 0) {
280		(void) ioctl(fd, TIOCNOTTY, NULL);
281		close(fd);
282	}
283#endif /* TIOCNOTTY */
284	if (setsid() < 0) {
285		dropbear_log(LOG_ERR,
286			"setsid: %.100s", strerror(errno));
287	}
288
289	/*
290	 * Verify that we are successfully disconnected from the controlling
291	 * tty.
292	 */
293	fd = open(_PATH_TTY, O_RDWR | O_NOCTTY);
294	if (fd >= 0) {
295		dropbear_log(LOG_ERR,
296				"Failed to disconnect from controlling tty.\n");
297		close(fd);
298	}
299	/* Make it our controlling tty. */
300#ifdef TIOCSCTTY
301	if (ioctl(*ttyfd, TIOCSCTTY, NULL) < 0) {
302		dropbear_log(LOG_ERR,
303			"ioctl(TIOCSCTTY): %.100s", strerror(errno));
304	}
305#endif /* TIOCSCTTY */
306#ifdef HAVE_NEWS4
307	if (setpgrp(0,0) < 0) {
308		dropbear_log(LOG_ERR,
309			error("SETPGRP %s",strerror(errno)));
310	}
311#endif /* HAVE_NEWS4 */
312#ifdef USE_VHANGUP
313	old = mysignal(SIGHUP, SIG_IGN);
314	vhangup();
315	mysignal(SIGHUP, old);
316#endif /* USE_VHANGUP */
317	fd = open(tty_name, O_RDWR);
318	if (fd < 0) {
319		dropbear_log(LOG_ERR,
320			"%.100s: %.100s", tty_name, strerror(errno));
321	} else {
322#ifdef USE_VHANGUP
323		close(*ttyfd);
324		*ttyfd = fd;
325#else /* USE_VHANGUP */
326		close(fd);
327#endif /* USE_VHANGUP */
328	}
329	/* Verify that we now have a controlling tty. */
330	fd = open(_PATH_TTY, O_WRONLY);
331	if (fd < 0) {
332		dropbear_log(LOG_ERR,
333			"open /dev/tty failed - could not set controlling tty: %.100s",
334		    strerror(errno));
335	} else {
336		close(fd);
337	}
338}
339
340/* Changes the window size associated with the pty. */
341
342void
343pty_change_window_size(int ptyfd, int row, int col,
344	int xpixel, int ypixel)
345{
346	struct winsize w;
347
348	w.ws_row = row;
349	w.ws_col = col;
350	w.ws_xpixel = xpixel;
351	w.ws_ypixel = ypixel;
352	(void) ioctl(ptyfd, TIOCSWINSZ, &w);
353}
354
355void
356pty_setowner(struct passwd *pw, const char *tty_name)
357{
358	struct group *grp;
359	gid_t gid;
360	mode_t mode;
361	struct stat st;
362
363	/* Determine the group to make the owner of the tty. */
364	grp = getgrnam("tty");
365	if (grp) {
366		gid = grp->gr_gid;
367		mode = S_IRUSR | S_IWUSR | S_IWGRP;
368	} else {
369		gid = pw->pw_gid;
370		mode = S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH;
371	}
372
373	/*
374	 * Change owner and mode of the tty as required.
375	 * Warn but continue if filesystem is read-only and the uids match/
376	 * tty is owned by root.
377	 */
378	if (stat(tty_name, &st)) {
379		dropbear_exit("pty_setowner: stat(%.101s) failed: %.100s",
380				tty_name, strerror(errno));
381	}
382
383	if (st.st_uid != pw->pw_uid || st.st_gid != gid) {
384		if (chown(tty_name, pw->pw_uid, gid) < 0) {
385			if (errno == EROFS &&
386			    (st.st_uid == pw->pw_uid || st.st_uid == 0)) {
387				dropbear_log(LOG_ERR,
388					"chown(%.100s, %u, %u) failed: %.100s",
389						tty_name, (unsigned int)pw->pw_uid, (unsigned int)gid,
390						strerror(errno));
391			} else {
392				dropbear_exit("chown(%.100s, %u, %u) failed: %.100s",
393				    tty_name, (unsigned int)pw->pw_uid, (unsigned int)gid,
394				    strerror(errno));
395			}
396		}
397	}
398
399	if ((st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) != mode) {
400		if (chmod(tty_name, mode) < 0) {
401			if (errno == EROFS &&
402			    (st.st_mode & (S_IRGRP | S_IROTH)) == 0) {
403				dropbear_log(LOG_ERR,
404					"chmod(%.100s, 0%o) failed: %.100s",
405					tty_name, mode, strerror(errno));
406			} else {
407				dropbear_exit("chmod(%.100s, 0%o) failed: %.100s",
408				    tty_name, mode, strerror(errno));
409			}
410		}
411	}
412}
413