]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/nspawn/nspawn-setuid.c
util-lib: split our string related calls from util.[ch] into its own file string...
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
CommitLineData
ee645080
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
07630cea 22#include <grp.h>
ee645080
LP
23#include <sys/types.h>
24#include <unistd.h>
ee645080 25
ee645080
LP
26#include "mkdir.h"
27#include "process-util.h"
07630cea
LP
28#include "signal-util.h"
29#include "string-util.h"
30#include "util.h"
ee645080
LP
31#include "nspawn-setuid.h"
32
33static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
34 int pipe_fds[2];
35 pid_t pid;
36
37 assert(database);
38 assert(key);
39 assert(rpid);
40
41 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
42 return log_error_errno(errno, "Failed to allocate pipe: %m");
43
44 pid = fork();
45 if (pid < 0)
46 return log_error_errno(errno, "Failed to fork getent child: %m");
47 else if (pid == 0) {
48 int nullfd;
49 char *empty_env = NULL;
50
51 if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0)
52 _exit(EXIT_FAILURE);
53
54 if (pipe_fds[0] > 2)
55 safe_close(pipe_fds[0]);
56 if (pipe_fds[1] > 2)
57 safe_close(pipe_fds[1]);
58
59 nullfd = open("/dev/null", O_RDWR);
60 if (nullfd < 0)
61 _exit(EXIT_FAILURE);
62
63 if (dup3(nullfd, STDIN_FILENO, 0) < 0)
64 _exit(EXIT_FAILURE);
65
66 if (dup3(nullfd, STDERR_FILENO, 0) < 0)
67 _exit(EXIT_FAILURE);
68
69 if (nullfd > 2)
70 safe_close(nullfd);
71
72 (void) reset_all_signal_handlers();
73 (void) reset_signal_mask();
74 close_all_fds(NULL, 0);
75
76 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
77 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
78 _exit(EXIT_FAILURE);
79 }
80
81 pipe_fds[1] = safe_close(pipe_fds[1]);
82
83 *rpid = pid;
84
85 return pipe_fds[0];
86}
87
88int change_uid_gid(const char *user, char **_home) {
89 char line[LINE_MAX], *x, *u, *g, *h;
90 const char *word, *state;
91 _cleanup_free_ uid_t *uids = NULL;
92 _cleanup_free_ char *home = NULL;
93 _cleanup_fclose_ FILE *f = NULL;
94 _cleanup_close_ int fd = -1;
95 unsigned n_uids = 0;
96 size_t sz = 0, l;
97 uid_t uid;
98 gid_t gid;
99 pid_t pid;
100 int r;
101
102 assert(_home);
103
104 if (!user || streq(user, "root") || streq(user, "0")) {
105 /* Reset everything fully to 0, just in case */
106
107 r = reset_uid_gid();
108 if (r < 0)
109 return log_error_errno(r, "Failed to become root: %m");
110
111 *_home = NULL;
112 return 0;
113 }
114
115 /* First, get user credentials */
116 fd = spawn_getent("passwd", user, &pid);
117 if (fd < 0)
118 return fd;
119
120 f = fdopen(fd, "r");
121 if (!f)
122 return log_oom();
123 fd = -1;
124
125 if (!fgets(line, sizeof(line), f)) {
126
127 if (!ferror(f)) {
128 log_error("Failed to resolve user %s.", user);
129 return -ESRCH;
130 }
131
132 log_error_errno(errno, "Failed to read from getent: %m");
133 return -errno;
134 }
135
136 truncate_nl(line);
137
138 wait_for_terminate_and_warn("getent passwd", pid, true);
139
140 x = strchr(line, ':');
141 if (!x) {
142 log_error("/etc/passwd entry has invalid user field.");
143 return -EIO;
144 }
145
146 u = strchr(x+1, ':');
147 if (!u) {
148 log_error("/etc/passwd entry has invalid password field.");
149 return -EIO;
150 }
151
152 u++;
153 g = strchr(u, ':');
154 if (!g) {
155 log_error("/etc/passwd entry has invalid UID field.");
156 return -EIO;
157 }
158
159 *g = 0;
160 g++;
161 x = strchr(g, ':');
162 if (!x) {
163 log_error("/etc/passwd entry has invalid GID field.");
164 return -EIO;
165 }
166
167 *x = 0;
168 h = strchr(x+1, ':');
169 if (!h) {
170 log_error("/etc/passwd entry has invalid GECOS field.");
171 return -EIO;
172 }
173
174 h++;
175 x = strchr(h, ':');
176 if (!x) {
177 log_error("/etc/passwd entry has invalid home directory field.");
178 return -EIO;
179 }
180
181 *x = 0;
182
183 r = parse_uid(u, &uid);
184 if (r < 0) {
185 log_error("Failed to parse UID of user.");
186 return -EIO;
187 }
188
189 r = parse_gid(g, &gid);
190 if (r < 0) {
191 log_error("Failed to parse GID of user.");
192 return -EIO;
193 }
194
195 home = strdup(h);
196 if (!home)
197 return log_oom();
198
199 /* Second, get group memberships */
200 fd = spawn_getent("initgroups", user, &pid);
201 if (fd < 0)
202 return fd;
203
204 fclose(f);
205 f = fdopen(fd, "r");
206 if (!f)
207 return log_oom();
208 fd = -1;
209
210 if (!fgets(line, sizeof(line), f)) {
211 if (!ferror(f)) {
212 log_error("Failed to resolve user %s.", user);
213 return -ESRCH;
214 }
215
216 log_error_errno(errno, "Failed to read from getent: %m");
217 return -errno;
218 }
219
220 truncate_nl(line);
221
222 wait_for_terminate_and_warn("getent initgroups", pid, true);
223
224 /* Skip over the username and subsequent separator whitespace */
225 x = line;
226 x += strcspn(x, WHITESPACE);
227 x += strspn(x, WHITESPACE);
228
229 FOREACH_WORD(word, l, x, state) {
230 char c[l+1];
231
232 memcpy(c, word, l);
233 c[l] = 0;
234
235 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
236 return log_oom();
237
238 r = parse_uid(c, &uids[n_uids++]);
239 if (r < 0) {
240 log_error("Failed to parse group data from getent.");
241 return -EIO;
242 }
243 }
244
245 r = mkdir_parents(home, 0775);
246 if (r < 0)
247 return log_error_errno(r, "Failed to make home root directory: %m");
248
249 r = mkdir_safe(home, 0755, uid, gid);
250 if (r < 0 && r != -EEXIST)
251 return log_error_errno(r, "Failed to make home directory: %m");
252
253 (void) fchown(STDIN_FILENO, uid, gid);
254 (void) fchown(STDOUT_FILENO, uid, gid);
255 (void) fchown(STDERR_FILENO, uid, gid);
256
257 if (setgroups(n_uids, uids) < 0)
258 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
259
260 if (setresgid(gid, gid, gid) < 0)
261 return log_error_errno(errno, "setregid() failed: %m");
262
263 if (setresuid(uid, uid, uid) < 0)
264 return log_error_errno(errno, "setreuid() failed: %m");
265
266 if (_home) {
267 *_home = home;
268 home = NULL;
269 }
270
271 return 0;
272}