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