1 | /* pt_chmod - helper program for `grantpt'. |
2 | Copyright (C) 1998-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
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 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <argp.h> |
20 | #include <errno.h> |
21 | #include <error.h> |
22 | #include <grp.h> |
23 | #include <libintl.h> |
24 | #include <locale.h> |
25 | #include <signal.h> |
26 | #include <stdio.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | #include <sys/stat.h> |
30 | #include <unistd.h> |
31 | #ifdef HAVE_LIBCAP |
32 | # include <sys/capability.h> |
33 | # include <sys/prctl.h> |
34 | #endif |
35 | |
36 | #include "pty-private.h" |
37 | |
38 | /* Get libc version number. */ |
39 | #include "../version.h" |
40 | |
41 | #define PACKAGE _libc_intl_domainname |
42 | |
43 | /* Name and version of program. */ |
44 | static void print_version (FILE *stream, struct argp_state *state); |
45 | void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; |
46 | |
47 | /* Function to print some extra text in the help message. */ |
48 | static char *more_help (int key, const char *text, void *input); |
49 | |
50 | /* Data structure to communicate with argp functions. */ |
51 | static struct argp argp = |
52 | { |
53 | NULL, NULL, NULL, NULL, NULL, more_help |
54 | }; |
55 | |
56 | |
57 | /* Print the version information. */ |
58 | static void |
59 | print_version (FILE *stream, struct argp_state *state) |
60 | { |
61 | fprintf (stream: stream, format: "pt_chown %s%s\n" , PKGVERSION, VERSION); |
62 | fprintf (stream: stream, gettext ("\ |
63 | Copyright (C) %s Free Software Foundation, Inc.\n\ |
64 | This is free software; see the source for copying conditions. There is NO\n\ |
65 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ |
66 | " ), "2022" ); |
67 | } |
68 | |
69 | static char * |
70 | more_help (int key, const char *text, void *input) |
71 | { |
72 | char *cp; |
73 | char *tp; |
74 | |
75 | switch (key) |
76 | { |
77 | case ARGP_KEY_HELP_PRE_DOC: |
78 | asprintf (ptr: &cp, gettext ("\ |
79 | Set the owner, group and access permission of the slave pseudo\ |
80 | terminal corresponding to the master pseudo terminal passed on\ |
81 | file descriptor `%d'. This is the helper program for the\ |
82 | `grantpt' function. It is not intended to be run directly from\ |
83 | the command line.\n" ), |
84 | PTY_FILENO); |
85 | return cp; |
86 | case ARGP_KEY_HELP_EXTRA: |
87 | /* We print some extra information. */ |
88 | if (asprintf (ptr: &tp, gettext ("\ |
89 | For bug reporting instructions, please see:\n\ |
90 | %s.\n" ), REPORT_BUGS_TO) < 0) |
91 | return NULL; |
92 | if (asprintf (ptr: &cp, gettext ("\ |
93 | The owner is set to the current user, the group is set to `%s',\ |
94 | and the access permission is set to `%o'.\n\n\ |
95 | %s" ), |
96 | TTY_GROUP, S_IRUSR|S_IWUSR|S_IWGRP, tp) < 0) |
97 | { |
98 | free (ptr: tp); |
99 | return NULL; |
100 | } |
101 | return cp; |
102 | default: |
103 | break; |
104 | } |
105 | return (char *) text; |
106 | } |
107 | |
108 | static int |
109 | do_pt_chown (void) |
110 | { |
111 | char *pty; |
112 | struct stat64 st; |
113 | struct group *p; |
114 | gid_t gid; |
115 | |
116 | /* Check that PTY_FILENO is a valid master pseudo terminal. */ |
117 | pty = ptsname (PTY_FILENO); |
118 | if (pty == NULL) |
119 | return errno == EBADF ? FAIL_EBADF : FAIL_EINVAL; |
120 | |
121 | /* Check that the returned slave pseudo terminal is a |
122 | character device. */ |
123 | if (stat64 (file: pty, buf: &st) < 0 || !S_ISCHR (st.st_mode)) |
124 | return FAIL_EINVAL; |
125 | |
126 | /* Get the group ID of the special `tty' group. */ |
127 | p = getgrnam (TTY_GROUP); |
128 | gid = p ? p->gr_gid : getgid (); |
129 | |
130 | /* Set the owner to the real user ID, and the group to that special |
131 | group ID. */ |
132 | if (chown (file: pty, owner: getuid (), group: gid) < 0) |
133 | return FAIL_EACCES; |
134 | |
135 | /* Set the permission mode to readable and writable by the owner, |
136 | and writable by the group. */ |
137 | if ((st.st_mode & ACCESSPERMS) != (S_IRUSR|S_IWUSR|S_IWGRP) |
138 | && chmod (file: pty, S_IRUSR|S_IWUSR|S_IWGRP) < 0) |
139 | return FAIL_EACCES; |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | |
145 | int |
146 | main (int argc, char *argv[]) |
147 | { |
148 | uid_t euid = geteuid (); |
149 | uid_t uid = getuid (); |
150 | int remaining; |
151 | sigset_t signalset; |
152 | |
153 | /* Clear any signal mask from the parent process. */ |
154 | sigemptyset (set: &signalset); |
155 | sigprocmask (SIG_SETMASK, set: &signalset, NULL); |
156 | |
157 | if (argc == 1 && euid == 0) |
158 | { |
159 | #ifdef HAVE_LIBCAP |
160 | /* Drop privileges. */ |
161 | if (uid != euid) |
162 | { |
163 | static const cap_value_t cap_list[] = |
164 | { CAP_CHOWN, CAP_FOWNER }; |
165 | # define ncap_list (sizeof (cap_list) / sizeof (cap_list[0])) |
166 | cap_t caps = cap_init (); |
167 | if (caps == NULL) |
168 | return FAIL_ENOMEM; |
169 | |
170 | /* There is no reason why these should not work. */ |
171 | cap_set_flag (caps, CAP_PERMITTED, ncap_list, cap_list, CAP_SET); |
172 | cap_set_flag (caps, CAP_EFFECTIVE, ncap_list, cap_list, CAP_SET); |
173 | |
174 | int res = cap_set_proc (caps); |
175 | |
176 | cap_free (caps); |
177 | |
178 | if (__glibc_unlikely (res != 0)) |
179 | return FAIL_EXEC; |
180 | } |
181 | #endif |
182 | |
183 | /* Normal invocation of this program is with no arguments and |
184 | with privileges. */ |
185 | return do_pt_chown (); |
186 | } |
187 | |
188 | /* We aren't going to be using privileges, so drop them right now. */ |
189 | setuid (uid); |
190 | |
191 | /* Set locale via LC_ALL. */ |
192 | setlocale (LC_ALL, locale: "" ); |
193 | |
194 | /* Set the text message domain. */ |
195 | textdomain (PACKAGE); |
196 | |
197 | /* parse and process arguments. */ |
198 | argp_parse (argp: &argp, argc: argc, argv: argv, flags: 0, arg_index: &remaining, NULL); |
199 | |
200 | if (remaining < argc) |
201 | { |
202 | /* We should not be called with any non-option parameters. */ |
203 | error (status: 0, errnum: 0, gettext ("too many arguments" )); |
204 | argp_help (argp: &argp, stdout, ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR, |
205 | name: program_invocation_short_name); |
206 | return EXIT_FAILURE; |
207 | } |
208 | |
209 | /* Check if we are properly installed. */ |
210 | if (euid != 0) |
211 | error (status: FAIL_EXEC, errnum: 0, gettext ("needs to be installed setuid `root'" )); |
212 | |
213 | return EXIT_SUCCESS; |
214 | } |
215 | |