]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
pkgconfig: define variables relative to ${prefix}/${rootprefix}/${sysconfdir}
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <grp.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6
7 #include "alloc-util.h"
8 #include "def.h"
9 #include "errno.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "mkdir.h"
13 #include "nspawn-setuid.h"
14 #include "process-util.h"
15 #include "signal-util.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "user-util.h"
19 #include "util.h"
20
21 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
22 int pipe_fds[2], r;
23 pid_t pid;
24
25 assert(database);
26 assert(key);
27 assert(rpid);
28
29 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
30 return log_error_errno(errno, "Failed to allocate pipe: %m");
31
32 r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
33 if (r < 0) {
34 safe_close_pair(pipe_fds);
35 return r;
36 }
37 if (r == 0) {
38 char *empty_env = NULL;
39
40 safe_close(pipe_fds[0]);
41
42 if (rearrange_stdio(-1, pipe_fds[1], -1) < 0)
43 _exit(EXIT_FAILURE);
44
45 close_all_fds(NULL, 0);
46
47 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
48 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
49 _exit(EXIT_FAILURE);
50 }
51
52 pipe_fds[1] = safe_close(pipe_fds[1]);
53
54 *rpid = pid;
55
56 return pipe_fds[0];
57 }
58
59 int change_uid_gid(const char *user, char **_home) {
60 char *x, *u, *g, *h;
61 const char *word, *state;
62 _cleanup_free_ uid_t *uids = NULL;
63 _cleanup_free_ char *home = NULL, *line = NULL;
64 _cleanup_fclose_ FILE *f = NULL;
65 _cleanup_close_ int fd = -1;
66 unsigned n_uids = 0;
67 size_t sz = 0, l;
68 uid_t uid;
69 gid_t gid;
70 pid_t pid;
71 int r;
72
73 assert(_home);
74
75 if (!user || STR_IN_SET(user, "root", "0")) {
76 /* Reset everything fully to 0, just in case */
77
78 r = reset_uid_gid();
79 if (r < 0)
80 return log_error_errno(r, "Failed to become root: %m");
81
82 *_home = NULL;
83 return 0;
84 }
85
86 /* First, get user credentials */
87 fd = spawn_getent("passwd", user, &pid);
88 if (fd < 0)
89 return fd;
90
91 f = fdopen(fd, "re");
92 if (!f)
93 return log_oom();
94 fd = -1;
95
96 r = read_line(f, LONG_LINE_MAX, &line);
97 if (r == 0) {
98 log_error("Failed to resolve user %s.", user);
99 return -ESRCH;
100 }
101 if (r < 0)
102 return log_error_errno(r, "Failed to read from getent: %m");
103
104 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
105
106 x = strchr(line, ':');
107 if (!x) {
108 log_error("/etc/passwd entry has invalid user field.");
109 return -EIO;
110 }
111
112 u = strchr(x+1, ':');
113 if (!u) {
114 log_error("/etc/passwd entry has invalid password field.");
115 return -EIO;
116 }
117
118 u++;
119 g = strchr(u, ':');
120 if (!g) {
121 log_error("/etc/passwd entry has invalid UID field.");
122 return -EIO;
123 }
124
125 *g = 0;
126 g++;
127 x = strchr(g, ':');
128 if (!x) {
129 log_error("/etc/passwd entry has invalid GID field.");
130 return -EIO;
131 }
132
133 *x = 0;
134 h = strchr(x+1, ':');
135 if (!h) {
136 log_error("/etc/passwd entry has invalid GECOS field.");
137 return -EIO;
138 }
139
140 h++;
141 x = strchr(h, ':');
142 if (!x) {
143 log_error("/etc/passwd entry has invalid home directory field.");
144 return -EIO;
145 }
146
147 *x = 0;
148
149 r = parse_uid(u, &uid);
150 if (r < 0) {
151 log_error("Failed to parse UID of user.");
152 return -EIO;
153 }
154
155 r = parse_gid(g, &gid);
156 if (r < 0) {
157 log_error("Failed to parse GID of user.");
158 return -EIO;
159 }
160
161 home = strdup(h);
162 if (!home)
163 return log_oom();
164
165 f = safe_fclose(f);
166 line = mfree(line);
167
168 /* Second, get group memberships */
169 fd = spawn_getent("initgroups", user, &pid);
170 if (fd < 0)
171 return fd;
172
173 f = fdopen(fd, "re");
174 if (!f)
175 return log_oom();
176 fd = -1;
177
178 r = read_line(f, LONG_LINE_MAX, &line);
179 if (r == 0) {
180 log_error("Failed to resolve user %s.", user);
181 return -ESRCH;
182 }
183 if (r < 0)
184 return log_error_errno(r, "Failed to read from getent: %m");
185
186 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
187
188 /* Skip over the username and subsequent separator whitespace */
189 x = line;
190 x += strcspn(x, WHITESPACE);
191 x += strspn(x, WHITESPACE);
192
193 FOREACH_WORD(word, l, x, state) {
194 char c[l+1];
195
196 memcpy(c, word, l);
197 c[l] = 0;
198
199 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
200 return log_oom();
201
202 r = parse_uid(c, &uids[n_uids++]);
203 if (r < 0)
204 return log_error_errno(r, "Failed to parse group data from getent: %m");
205 }
206
207 r = mkdir_parents(home, 0775);
208 if (r < 0)
209 return log_error_errno(r, "Failed to make home root directory: %m");
210
211 r = mkdir_safe(home, 0755, uid, gid, 0);
212 if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
213 return log_error_errno(r, "Failed to make home directory: %m");
214
215 (void) fchown(STDIN_FILENO, uid, gid);
216 (void) fchown(STDOUT_FILENO, uid, gid);
217 (void) fchown(STDERR_FILENO, uid, gid);
218
219 if (setgroups(n_uids, uids) < 0)
220 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
221
222 if (setresgid(gid, gid, gid) < 0)
223 return log_error_errno(errno, "setresgid() failed: %m");
224
225 if (setresuid(uid, uid, uid) < 0)
226 return log_error_errno(errno, "setresuid() failed: %m");
227
228 if (_home)
229 *_home = TAKE_PTR(home);
230
231 return 0;
232 }