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