smartmontools SVN Rev 5640
Utility to control and monitor storage systems with "S.M.A.R.T."
popen_as_ugid.cpp
Go to the documentation of this file.
1/*
2 * popen_as_ugid.cpp
3 *
4 * Home page of code is: https://www.smartmontools.org
5 *
6 * Copyright (C) 2021-24 Christian Franke
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11#include "popen_as_ugid.h"
12
13const char * popen_as_ugid_cvsid = "$Id: popen_as_ugid.cpp 5579 2024-01-12 18:43:41Z chrfranke $"
15
16#include <errno.h>
17#include <fcntl.h>
18#include <signal.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22#include <sys/types.h>
23#include <sys/wait.h>
24
25static FILE * s_popen_file /* = 0 */;
26static pid_t s_popen_pid /* = 0 */;
27
28FILE * popen_as_ugid(const char * cmd, const char * mode, uid_t uid, gid_t gid)
29{
30 // Only "r" supported
31 if (*mode != 'r') {
32 errno = EINVAL;
33 return (FILE *)0;
34 }
35
36 // Only one stream supported
37 if (s_popen_file) {
38 errno = EMFILE;
39 return (FILE *)0;
40 }
41
42 int pd[2] = {-1, -1};
43 int sd[2] = {-1, -1};
44 FILE * fp = 0;
45 pid_t pid;
46 errno = 0;
47 if (!(// Create stdout and status pipe ...
48 !pipe(pd) && !pipe(sd) &&
49 // ... connect stdout pipe to FILE ...
50 !!(fp = fdopen(pd[0], "r")) &&
51 // ... and then fork()
52 (pid = fork()) != (pid_t)-1) ) {
53 int err = (errno ? errno : ENOSYS);
54 if (fp) {
55 fclose(fp); close(pd[1]);
56 }
57 else if (pd[0] >= 0) {
58 close(pd[0]); close(pd[1]);
59 }
60 if (sd[0] >= 0) {
61 close(sd[0]); close(sd[1]);
62 }
63 errno = err;
64 return (FILE *)0;
65 }
66
67 if (!pid) { // Child
68 // Do not inherit any unneeded file descriptors
69 fclose(fp);
70 int i;
71 for (i = 0; i <= pd[1] || i <= sd[1]; i++) {
72 if (i == pd[1] || i == sd[1])
73 continue;
74 close(i);
75 }
76 int open_max = sysconf(_SC_OPEN_MAX);
77#ifdef HAVE_CLOSE_RANGE
78 if (close_range(i, open_max - 1, 0))
79#endif
80 {
81 // Limit number of unneeded close() calls under the assumption that
82 // there are no large gaps between open FDs
83 for (int failed = 0; i < open_max && failed < 1024; i++)
84 failed = (!close(i) ? 0 : failed + 1);
85 }
86
87 FILE * fc = 0;
88 int err = errno = 0;
89 if (!(// Connect stdio to /dev/null ...
90 open("/dev/null", O_RDWR) == 0 &&
91 dup(0) == 1 && dup(0) == 2 &&
92 // ... don't inherit pipes ...
93 !fcntl(pd[1], F_SETFD, FD_CLOEXEC) &&
94 !fcntl(sd[1], F_SETFD, FD_CLOEXEC) &&
95 // ... set group and user (assumes we are root) ...
96 (!gid || (!setgid(gid) && !setgroups(1, &gid))) &&
97 (!uid || !setuid(uid)) &&
98 // ... and then call popen() from std library
99 !!(fc = popen(cmd, mode)) )) {
100 err = (errno ? errno : ENOSYS);
101 }
102
103 // Send setup result to parent
104 if (write(sd[1], &err, sizeof(err)) != (int)sizeof(err))
105 err = EIO;
106 close(sd[1]);
107 if (!fc)
108 _exit(127);
109
110 // Send popen's FILE stream to parent's FILE
111 int c;
112 while (!err && (c = getc(fc)) != EOF) {
113 char cb = (char)c;
114 if (write(pd[1], &cb, 1) != 1)
115 err = EIO;
116 }
117
118 // Return status or re-throw signal
119 int status = pclose(fc);
120 if (WIFSIGNALED(status))
121 kill(getpid(), WTERMSIG(status));
122 _exit(WIFEXITED(status) ? WEXITSTATUS(status) : 127);
123 }
124
125 // Parent
126 close(pd[1]); close(sd[1]);
127
128 // Get setup result from child
129 int err = 0;
130 if (read(sd[0], &err, sizeof(err)) != (int)sizeof(err))
131 err = EIO;
132 close(sd[0]);
133 if (err) {
134 fclose(fp);
135 errno = err;
136 return (FILE *)0;
137 }
138
139 // Save for pclose_as_ugid()
140 s_popen_file = fp;
141 s_popen_pid = pid;
142 return fp;
143}
144
145int pclose_as_ugid(FILE * f)
146{
147 if (f != s_popen_file) {
148 errno = EBADF;
149 return -1;
150 }
151
152 fclose(f);
153 s_popen_file = 0;
154
155 pid_t pid; int status;
156 do
157 pid = waitpid(s_popen_pid, &status, 0);
158 while (pid == (pid_t)-1 && errno == EINTR);
159 s_popen_pid = 0;
160
161 if (pid == (pid_t)-1)
162 return -1;
163 return status;
164}
165
166const char * parse_ugid(const char * s, uid_t & uid, gid_t & gid,
167 std::string & uname, std::string & gname )
168{
169 // Split USER:GROUP
170 int len = strlen(s), n1 = -1, n2 = -1;
171 char un[64+1] = "", gn[64+1] = "";
172 if (!( sscanf(s, "%64[^ :]%n:%64[^ :]%n", un, &n1, gn, &n2) >= 1
173 && (n1 == len || n2 == len) )) {
174 return "Syntax error";
175 }
176
177 // Lookup user
178 const struct passwd * pwd;
179 unsigned u = 0;
180 if (sscanf(un, "%u%n", &u, (n1 = -1, &n1)) == 1 && n1 == (int)strlen(un)) {
181 uid = (uid_t)u;
182 pwd = getpwuid(uid);
183 }
184 else {
185 pwd = getpwnam(un);
186 if (!pwd)
187 return "Unknown user name";
188 uid = pwd->pw_uid;
189 }
190 if (pwd)
191 uname = pwd->pw_name;
192
193 const struct group * grp;
194 if (gn[0]) {
195 // Lookup group
196 unsigned g = 0;
197 if (sscanf(gn, "%u%n", &g, (n1 = -1, &n1)) == 1 && n1 == (int)strlen(gn)) {
198 gid = (gid_t)g;
199 grp = getgrgid(gid);
200 }
201 else {
202 grp = getgrnam(gn);
203 if (!grp)
204 return "Unknown group name";
205 gid = grp->gr_gid;
206 }
207 }
208 else {
209 // Use default group
210 if (!pwd)
211 return "Unknown default group";
212 gid = pwd->pw_gid;
213 grp = getgrgid(gid);
214 }
215 if (grp)
216 gname = grp->gr_name;
217
218 return (const char *)0;
219}
220
221// Test program
222#ifdef TEST
223
224int main(int argc, char **argv)
225{
226 const char * user_group, * cmd;
227 switch (argc) {
228 case 2: user_group = 0; cmd = argv[1]; break;
229 case 3: user_group = argv[1]; cmd = argv[2]; break;
230 default:
231 printf("Usage: %s [USER[:GROUP]] \"COMMAND ARG...\"\n", argv[0]);
232 return 1;
233 }
234
235 int leak1 = open("/dev/null", O_RDONLY), leak2 = dup2(leak1, 1000);
236
237 FILE * f;
238 if (user_group) {
239 uid_t uid; gid_t gid;
240 std::string uname = "unknown", gname = "unknown";
241 const char * err = parse_ugid(user_group, uid, gid, uname, gname);
242 if (err) {
243 fprintf(stderr, "Error: %s\n", err);
244 return 1;
245 }
246 printf("popen_as_ugid(\"%s\", \"r\", %u(%s), %u(%s)):\n", cmd,
247 (unsigned)uid, uname.c_str(), (unsigned)gid, gname.c_str());
248 f = popen_as_ugid(cmd, "r", uid, gid);
249 }
250 else {
251 printf("popen(\"%s\", \"r\"):\n", cmd);
252 f = popen(cmd, "r");
253 }
254 fflush(stdout);
255 close(leak1); close(leak2);
256
257 if (!f) {
258 perror("popen");
259 return 1;
260 }
261
262 int cnt, c;
263 for (cnt = 0; (c = getc(f)) != EOF; cnt++)
264 putchar(c);
265 printf("[EOF]\nread %d bytes\n", cnt);
266
267 int status;
268 if (user_group)
269 status = pclose_as_ugid(f);
270 else
271 status = pclose(f);
272
273 if (status == -1) {
274 perror("pclose");
275 return 1;
276 }
277 printf("pclose() = 0x%04x (exit = %d, sig = %d)\n",
278 status, WEXITSTATUS(status), WTERMSIG(status));
279 return 0;
280}
281
282#endif
u8 cmd
Definition: megaraid.h:1
u16 s[6]
Definition: megaraid.h:18
static pid_t s_popen_pid
FILE * popen_as_ugid(const char *cmd, const char *mode, uid_t uid, gid_t gid)
const char * popen_as_ugid_cvsid
static FILE * s_popen_file
int pclose_as_ugid(FILE *f)
const char * parse_ugid(const char *s, uid_t &uid, gid_t &gid, std::string &uname, std::string &gname)
#define POPEN_AS_UGID_H_CVSID
Definition: popen_as_ugid.h:12
int main(int argc, char **argv)
Definition: smartctl.cpp:1686