Branch data 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 : : }
|