1/* Copyright (C) 1998-2018 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Zack Weinberg <zack@rabi.phys.columbia.edu>, 1998.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <http://www.gnu.org/licenses/>. */
18
19#include <assert.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <grp.h>
23#include <limits.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/resource.h>
27#include <sys/stat.h>
28#include <sys/types.h>
29#include <sys/wait.h>
30#include <unistd.h>
31
32#include "pty-private.h"
33
34
35/* Return the result of ptsname_r in the buffer pointed to by PTS,
36 which should be of length BUF_LEN. If it is too long to fit in
37 this buffer, a sufficiently long buffer is allocated using malloc,
38 and returned in PTS. 0 is returned upon success, -1 otherwise. */
39static int
40pts_name (int fd, char **pts, size_t buf_len, struct stat64 *stp)
41{
42 int rv;
43 char *buf = *pts;
44
45 for (;;)
46 {
47 char *new_buf;
48
49 if (buf_len)
50 {
51 rv = __ptsname_internal (fd, buf, buf_len, stp);
52 if (rv != 0)
53 {
54 if (rv == ENOTTY)
55 /* ptsname_r returns with ENOTTY to indicate
56 a descriptor not referring to a pty master.
57 For this condition, grantpt must return EINVAL. */
58 rv = EINVAL;
59 errno = rv; /* Not necessarily set by __ptsname_r. */
60 break;
61 }
62
63 if (memchr (buf, '\0', buf_len))
64 /* We succeeded and the returned name fit in the buffer. */
65 break;
66
67 /* Try again with a longer buffer. */
68 buf_len += buf_len; /* Double it */
69 }
70 else
71 /* No initial buffer; start out by mallocing one. */
72 buf_len = 128; /* First time guess. */
73
74 if (buf != *pts)
75 /* We've already malloced another buffer at least once. */
76 new_buf = (char *) realloc (buf, buf_len);
77 else
78 new_buf = (char *) malloc (buf_len);
79 if (! new_buf)
80 {
81 rv = -1;
82 __set_errno (ENOMEM);
83 break;
84 }
85 buf = new_buf;
86 }
87
88 if (rv == 0)
89 *pts = buf; /* Return buffer to the user. */
90 else if (buf != *pts)
91 free (buf); /* Free what we malloced when returning an error. */
92
93 return rv;
94}
95
96/* Change the ownership and access permission of the slave pseudo
97 terminal associated with the master pseudo terminal specified
98 by FD. */
99int
100grantpt (int fd)
101{
102 int retval = -1;
103#ifdef PATH_MAX
104 char _buf[PATH_MAX];
105#else
106 char _buf[512];
107#endif
108 char *buf = _buf;
109 struct stat64 st;
110
111 if (__glibc_unlikely (pts_name (fd, &buf, sizeof (_buf), &st)))
112 {
113 int save_errno = errno;
114
115 /* Check, if the file descriptor is valid. pts_name returns the
116 wrong errno number, so we cannot use that. */
117 if (__libc_fcntl (fd, F_GETFD) == -1 && errno == EBADF)
118 return -1;
119
120 /* If the filedescriptor is no TTY, grantpt has to set errno
121 to EINVAL. */
122 if (save_errno == ENOTTY)
123 __set_errno (EINVAL);
124 else
125 __set_errno (save_errno);
126
127 return -1;
128 }
129
130 /* Make sure that we own the device. */
131 uid_t uid = __getuid ();
132 if (st.st_uid != uid)
133 {
134 if (__chown (buf, uid, st.st_gid) < 0)
135 goto helper;
136 }
137
138 static int tty_gid = -1;
139 if (__glibc_unlikely (tty_gid == -1))
140 {
141 char *grtmpbuf;
142 struct group grbuf;
143 size_t grbuflen = __sysconf (_SC_GETGR_R_SIZE_MAX);
144 struct group *p;
145
146 /* Get the group ID of the special `tty' group. */
147 if (grbuflen == (size_t) -1L)
148 /* `sysconf' does not support _SC_GETGR_R_SIZE_MAX.
149 Try a moderate value. */
150 grbuflen = 1024;
151 grtmpbuf = (char *) __alloca (grbuflen);
152 __getgrnam_r (TTY_GROUP, &grbuf, grtmpbuf, grbuflen, &p);
153 if (p != NULL)
154 tty_gid = p->gr_gid;
155 }
156 gid_t gid = tty_gid == -1 ? __getgid () : tty_gid;
157
158#if HAVE_PT_CHOWN
159 /* Make sure the group of the device is that special group. */
160 if (st.st_gid != gid)
161 {
162 if (__chown (buf, uid, gid) < 0)
163 goto helper;
164 }
165
166 /* Make sure the permission mode is set to readable and writable by
167 the owner, and writable by the group. */
168 mode_t mode = S_IRUSR|S_IWUSR|S_IWGRP;
169#else
170 /* When built without pt_chown, we have delegated the creation of the
171 pty node with the right group and permission mode to the kernel, and
172 non-root users are unlikely to be able to change it. Therefore let's
173 consider that POSIX enforcement is the responsibility of the whole
174 system and not only the GNU libc. Thus accept different group or
175 permission mode. */
176
177 /* Make sure the permission is set to readable and writable by the
178 owner. For security reasons, make it writable by the group only
179 when originally writable and when the group of the device is that
180 special group. */
181 mode_t mode = S_IRUSR|S_IWUSR|
182 ((st.st_gid == gid) ? (st.st_mode & S_IWGRP) : 0);
183#endif
184
185 if ((st.st_mode & ACCESSPERMS) != mode)
186 {
187 if (__chmod (buf, mode) < 0)
188 goto helper;
189 }
190
191 retval = 0;
192 goto cleanup;
193
194 /* We have to use the helper program if it is available. */
195 helper:;
196
197#if HAVE_PT_CHOWN
198 pid_t pid = __fork ();
199 if (pid == -1)
200 goto cleanup;
201 else if (pid == 0)
202 {
203 /* Disable core dumps. */
204 struct rlimit rl = { 0, 0 };
205 __setrlimit (RLIMIT_CORE, &rl);
206
207 /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */
208 if (fd != PTY_FILENO)
209 if (__dup2 (fd, PTY_FILENO) < 0)
210 _exit (FAIL_EBADF);
211
212# ifdef CLOSE_ALL_FDS
213 CLOSE_ALL_FDS ();
214# endif
215
216 execle (_PATH_PT_CHOWN, __basename (_PATH_PT_CHOWN), NULL, NULL);
217 _exit (FAIL_EXEC);
218 }
219 else
220 {
221 int w;
222
223 if (__waitpid (pid, &w, 0) == -1)
224 goto cleanup;
225 if (!WIFEXITED (w))
226 __set_errno (ENOEXEC);
227 else
228 switch (WEXITSTATUS (w))
229 {
230 case 0:
231 retval = 0;
232 break;
233 case FAIL_EBADF:
234 __set_errno (EBADF);
235 break;
236 case FAIL_EINVAL:
237 __set_errno (EINVAL);
238 break;
239 case FAIL_EACCES:
240 __set_errno (EACCES);
241 break;
242 case FAIL_EXEC:
243 __set_errno (ENOEXEC);
244 break;
245 case FAIL_ENOMEM:
246 __set_errno (ENOMEM);
247 break;
248
249 default:
250 assert(! "grantpt: internal error: invalid exit code from pt_chown");
251 }
252 }
253#endif
254
255 cleanup:
256 if (buf != _buf)
257 free (buf);
258
259 return retval;
260}
261