]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
409093fe LP |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2016 Lennart Poettering | |
6 | ||
7 | systemd is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU Lesser General Public License as published by | |
9 | the Free Software Foundation; either version 2.1 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | systemd is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | Lesser General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU Lesser General Public License | |
18 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
19 | ***/ | |
20 | ||
21 | #include <nss.h> | |
22 | ||
23 | #include "sd-bus.h" | |
24 | ||
fd63e712 | 25 | #include "alloc-util.h" |
409093fe LP |
26 | #include "bus-common-errors.h" |
27 | #include "env-util.h" | |
fd63e712 | 28 | #include "fs-util.h" |
409093fe LP |
29 | #include "macro.h" |
30 | #include "nss-util.h" | |
31 | #include "signal-util.h" | |
fd63e712 | 32 | #include "stdio-util.h" |
2129011e | 33 | #include "string-util.h" |
409093fe LP |
34 | #include "user-util.h" |
35 | #include "util.h" | |
36 | ||
2129011e LP |
37 | static const struct passwd root_passwd = { |
38 | .pw_name = (char*) "root", | |
39 | .pw_passwd = (char*) "x", /* see shadow file */ | |
40 | .pw_uid = 0, | |
41 | .pw_gid = 0, | |
42 | .pw_gecos = (char*) "Super User", | |
43 | .pw_dir = (char*) "/root", | |
44 | .pw_shell = (char*) "/bin/sh", | |
45 | }; | |
46 | ||
47 | static const struct passwd nobody_passwd = { | |
48 | .pw_name = (char*) NOBODY_USER_NAME, | |
49 | .pw_passwd = (char*) "*", /* locked */ | |
3a664727 LP |
50 | .pw_uid = UID_NOBODY, |
51 | .pw_gid = GID_NOBODY, | |
2129011e LP |
52 | .pw_gecos = (char*) "User Nobody", |
53 | .pw_dir = (char*) "/", | |
54 | .pw_shell = (char*) "/sbin/nologin", | |
55 | }; | |
56 | ||
57 | static const struct group root_group = { | |
58 | .gr_name = (char*) "root", | |
59 | .gr_gid = 0, | |
60 | .gr_passwd = (char*) "x", /* see shadow file */ | |
61 | .gr_mem = (char*[]) { NULL }, | |
62 | }; | |
63 | ||
64 | static const struct group nobody_group = { | |
65 | .gr_name = (char*) NOBODY_GROUP_NAME, | |
3a664727 | 66 | .gr_gid = GID_NOBODY, |
2129011e LP |
67 | .gr_passwd = (char*) "*", /* locked */ |
68 | .gr_mem = (char*[]) { NULL }, | |
69 | }; | |
70 | ||
409093fe LP |
71 | NSS_GETPW_PROTOTYPES(systemd); |
72 | NSS_GETGR_PROTOTYPES(systemd); | |
73 | ||
fd63e712 LP |
74 | static int direct_lookup_name(const char *name, uid_t *ret) { |
75 | _cleanup_free_ char *s = NULL; | |
76 | const char *path; | |
77 | int r; | |
78 | ||
79 | assert(name); | |
80 | ||
81 | /* Normally, we go via the bus to resolve names. That has the benefit that it is available from any mount | |
82 | * namespace and subject to proper authentication. However, there's one problem: if our module is called from | |
83 | * dbus-daemon itself we really can't use D-Bus to communicate. In this case, resort to a client-side hack, | |
84 | * and look for the dynamic names directly. This is pretty ugly, but breaks the cyclic dependency. */ | |
85 | ||
86 | path = strjoina("/run/systemd/dynamic-uid/direct:", name); | |
87 | r = readlink_malloc(path, &s); | |
88 | if (r < 0) | |
89 | return r; | |
90 | ||
91 | return parse_uid(s, ret); | |
92 | } | |
93 | ||
94 | static int direct_lookup_uid(uid_t uid, char **ret) { | |
95 | char path[strlen("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1], *s; | |
96 | int r; | |
97 | ||
98 | xsprintf(path, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid); | |
99 | ||
100 | r = readlink_malloc(path, &s); | |
101 | if (r < 0) | |
102 | return r; | |
103 | if (!valid_user_group_name(s)) { /* extra safety check */ | |
104 | free(s); | |
105 | return -EINVAL; | |
106 | } | |
107 | ||
108 | *ret = s; | |
109 | return 0; | |
110 | } | |
111 | ||
409093fe LP |
112 | enum nss_status _nss_systemd_getpwnam_r( |
113 | const char *name, | |
114 | struct passwd *pwd, | |
115 | char *buffer, size_t buflen, | |
116 | int *errnop) { | |
117 | ||
409093fe LP |
118 | uint32_t translated; |
119 | size_t l; | |
689ca202 | 120 | int bypass, r; |
409093fe LP |
121 | |
122 | BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); | |
123 | ||
124 | assert(name); | |
125 | assert(pwd); | |
126 | ||
d6c575e3 LP |
127 | /* If the username is not valid, then we don't know it. Ideally libc would filter these for us anyway. We don't |
128 | * generate EINVAL here, because it isn't really out business to complain about invalid user names. */ | |
129 | if (!valid_user_group_name(name)) | |
130 | goto not_found; | |
2129011e LP |
131 | |
132 | /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ | |
71e0accc | 133 | if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { |
fe102d6a LP |
134 | if (streq(name, root_passwd.pw_name)) { |
135 | *pwd = root_passwd; | |
136 | *errnop = 0; | |
137 | return NSS_STATUS_SUCCESS; | |
138 | } | |
139 | if (streq(name, nobody_passwd.pw_name)) { | |
140 | *pwd = nobody_passwd; | |
141 | *errnop = 0; | |
142 | return NSS_STATUS_SUCCESS; | |
143 | } | |
2129011e LP |
144 | } |
145 | ||
409093fe | 146 | /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */ |
71e0accc | 147 | if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) |
409093fe LP |
148 | goto not_found; |
149 | ||
689ca202 YW |
150 | bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS"); |
151 | if (bypass <= 0) { | |
fd63e712 LP |
152 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
153 | _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; | |
154 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
155 | ||
156 | r = sd_bus_open_system(&bus); | |
689ca202 YW |
157 | if (r < 0) { |
158 | bypass = 1; | |
159 | goto direct_lookup; | |
160 | } | |
fd63e712 LP |
161 | |
162 | r = sd_bus_call_method(bus, | |
163 | "org.freedesktop.systemd1", | |
164 | "/org/freedesktop/systemd1", | |
165 | "org.freedesktop.systemd1.Manager", | |
166 | "LookupDynamicUserByName", | |
167 | &error, | |
168 | &reply, | |
169 | "s", | |
170 | name); | |
171 | if (r < 0) { | |
172 | if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) | |
173 | goto not_found; | |
174 | ||
175 | goto fail; | |
176 | } | |
177 | ||
178 | r = sd_bus_message_read(reply, "u", &translated); | |
179 | if (r < 0) | |
180 | goto fail; | |
409093fe LP |
181 | } |
182 | ||
689ca202 YW |
183 | direct_lookup: |
184 | if (bypass > 0) { | |
185 | /* Access the dynamic UID allocation directly if we are called from dbus-daemon, see above. */ | |
186 | r = direct_lookup_name(name, (uid_t*) &translated); | |
187 | if (r == -ENOENT) | |
188 | goto not_found; | |
189 | if (r < 0) | |
190 | goto fail; | |
191 | } | |
192 | ||
409093fe LP |
193 | l = strlen(name); |
194 | if (buflen < l+1) { | |
cda458a5 | 195 | *errnop = ERANGE; |
409093fe LP |
196 | return NSS_STATUS_TRYAGAIN; |
197 | } | |
198 | ||
199 | memcpy(buffer, name, l+1); | |
200 | ||
201 | pwd->pw_name = buffer; | |
202 | pwd->pw_uid = (uid_t) translated; | |
203 | pwd->pw_gid = (uid_t) translated; | |
204 | pwd->pw_gecos = (char*) "Dynamic User"; | |
205 | pwd->pw_passwd = (char*) "*"; /* locked */ | |
206 | pwd->pw_dir = (char*) "/"; | |
207 | pwd->pw_shell = (char*) "/sbin/nologin"; | |
208 | ||
209 | *errnop = 0; | |
210 | return NSS_STATUS_SUCCESS; | |
211 | ||
212 | not_found: | |
213 | *errnop = 0; | |
214 | return NSS_STATUS_NOTFOUND; | |
215 | ||
216 | fail: | |
217 | *errnop = -r; | |
218 | return NSS_STATUS_UNAVAIL; | |
219 | } | |
220 | ||
221 | enum nss_status _nss_systemd_getpwuid_r( | |
222 | uid_t uid, | |
223 | struct passwd *pwd, | |
224 | char *buffer, size_t buflen, | |
225 | int *errnop) { | |
226 | ||
227 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
228 | _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; | |
229 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
fd63e712 | 230 | _cleanup_free_ char *direct = NULL; |
409093fe LP |
231 | const char *translated; |
232 | size_t l; | |
689ca202 | 233 | int bypass, r; |
409093fe LP |
234 | |
235 | BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); | |
236 | ||
d6c575e3 LP |
237 | if (!uid_is_valid(uid)) |
238 | goto not_found; | |
409093fe | 239 | |
2129011e | 240 | /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */ |
71e0accc | 241 | if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { |
fe102d6a LP |
242 | if (uid == root_passwd.pw_uid) { |
243 | *pwd = root_passwd; | |
244 | *errnop = 0; | |
245 | return NSS_STATUS_SUCCESS; | |
246 | } | |
247 | if (uid == nobody_passwd.pw_uid) { | |
248 | *pwd = nobody_passwd; | |
249 | *errnop = 0; | |
250 | return NSS_STATUS_SUCCESS; | |
251 | } | |
2129011e LP |
252 | } |
253 | ||
83438277 | 254 | if (!uid_is_dynamic(uid)) |
409093fe LP |
255 | goto not_found; |
256 | ||
71e0accc | 257 | if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) |
409093fe LP |
258 | goto not_found; |
259 | ||
689ca202 YW |
260 | bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS"); |
261 | if (bypass <= 0) { | |
fd63e712 | 262 | r = sd_bus_open_system(&bus); |
689ca202 YW |
263 | if (r < 0) { |
264 | bypass = 1; | |
265 | goto direct_lookup; | |
266 | } | |
fd63e712 LP |
267 | |
268 | r = sd_bus_call_method(bus, | |
269 | "org.freedesktop.systemd1", | |
270 | "/org/freedesktop/systemd1", | |
271 | "org.freedesktop.systemd1.Manager", | |
272 | "LookupDynamicUserByUID", | |
273 | &error, | |
274 | &reply, | |
275 | "u", | |
276 | (uint32_t) uid); | |
277 | if (r < 0) { | |
278 | if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) | |
279 | goto not_found; | |
280 | ||
281 | goto fail; | |
282 | } | |
283 | ||
284 | r = sd_bus_message_read(reply, "s", &translated); | |
285 | if (r < 0) | |
286 | goto fail; | |
409093fe LP |
287 | } |
288 | ||
689ca202 YW |
289 | direct_lookup: |
290 | if (bypass > 0) { | |
291 | r = direct_lookup_uid(uid, &direct); | |
292 | if (r == -ENOENT) | |
293 | goto not_found; | |
294 | if (r < 0) | |
295 | goto fail; | |
296 | ||
297 | translated = direct; | |
298 | ||
299 | } | |
300 | ||
409093fe LP |
301 | l = strlen(translated) + 1; |
302 | if (buflen < l) { | |
cda458a5 | 303 | *errnop = ERANGE; |
409093fe LP |
304 | return NSS_STATUS_TRYAGAIN; |
305 | } | |
306 | ||
307 | memcpy(buffer, translated, l); | |
308 | ||
309 | pwd->pw_name = buffer; | |
310 | pwd->pw_uid = uid; | |
311 | pwd->pw_gid = uid; | |
312 | pwd->pw_gecos = (char*) "Dynamic User"; | |
313 | pwd->pw_passwd = (char*) "*"; /* locked */ | |
314 | pwd->pw_dir = (char*) "/"; | |
315 | pwd->pw_shell = (char*) "/sbin/nologin"; | |
316 | ||
317 | *errnop = 0; | |
318 | return NSS_STATUS_SUCCESS; | |
319 | ||
320 | not_found: | |
321 | *errnop = 0; | |
322 | return NSS_STATUS_NOTFOUND; | |
323 | ||
324 | fail: | |
325 | *errnop = -r; | |
326 | return NSS_STATUS_UNAVAIL; | |
327 | } | |
328 | ||
329 | enum nss_status _nss_systemd_getgrnam_r( | |
330 | const char *name, | |
331 | struct group *gr, | |
332 | char *buffer, size_t buflen, | |
333 | int *errnop) { | |
334 | ||
409093fe LP |
335 | uint32_t translated; |
336 | size_t l; | |
689ca202 | 337 | int bypass, r; |
409093fe LP |
338 | |
339 | BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); | |
340 | ||
341 | assert(name); | |
342 | assert(gr); | |
343 | ||
d6c575e3 LP |
344 | if (!valid_user_group_name(name)) |
345 | goto not_found; | |
2129011e LP |
346 | |
347 | /* Synthesize records for root and nobody, in case they are missing form /etc/group */ | |
71e0accc | 348 | if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { |
fe102d6a LP |
349 | if (streq(name, root_group.gr_name)) { |
350 | *gr = root_group; | |
351 | *errnop = 0; | |
352 | return NSS_STATUS_SUCCESS; | |
353 | } | |
354 | if (streq(name, nobody_group.gr_name)) { | |
355 | *gr = nobody_group; | |
356 | *errnop = 0; | |
357 | return NSS_STATUS_SUCCESS; | |
358 | } | |
2129011e LP |
359 | } |
360 | ||
71e0accc | 361 | if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) |
409093fe LP |
362 | goto not_found; |
363 | ||
689ca202 YW |
364 | bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS"); |
365 | if (bypass <= 0) { | |
fd63e712 LP |
366 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
367 | _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; | |
368 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
369 | ||
370 | r = sd_bus_open_system(&bus); | |
689ca202 YW |
371 | if (r < 0) { |
372 | bypass = 1; | |
373 | goto direct_lookup; | |
374 | } | |
fd63e712 LP |
375 | |
376 | r = sd_bus_call_method(bus, | |
377 | "org.freedesktop.systemd1", | |
378 | "/org/freedesktop/systemd1", | |
379 | "org.freedesktop.systemd1.Manager", | |
380 | "LookupDynamicUserByName", | |
381 | &error, | |
382 | &reply, | |
383 | "s", | |
384 | name); | |
385 | if (r < 0) { | |
386 | if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) | |
387 | goto not_found; | |
388 | ||
389 | goto fail; | |
390 | } | |
391 | ||
392 | r = sd_bus_message_read(reply, "u", &translated); | |
393 | if (r < 0) | |
394 | goto fail; | |
409093fe LP |
395 | } |
396 | ||
689ca202 YW |
397 | direct_lookup: |
398 | if (bypass > 0) { | |
399 | /* Access the dynamic GID allocation directly if we are called from dbus-daemon, see above. */ | |
400 | r = direct_lookup_name(name, (uid_t*) &translated); | |
401 | if (r == -ENOENT) | |
402 | goto not_found; | |
403 | if (r < 0) | |
404 | goto fail; | |
405 | } | |
406 | ||
409093fe LP |
407 | l = sizeof(char*) + strlen(name) + 1; |
408 | if (buflen < l) { | |
cda458a5 | 409 | *errnop = ERANGE; |
409093fe LP |
410 | return NSS_STATUS_TRYAGAIN; |
411 | } | |
412 | ||
413 | memzero(buffer, sizeof(char*)); | |
414 | strcpy(buffer + sizeof(char*), name); | |
415 | ||
416 | gr->gr_name = buffer + sizeof(char*); | |
417 | gr->gr_gid = (gid_t) translated; | |
418 | gr->gr_passwd = (char*) "*"; /* locked */ | |
419 | gr->gr_mem = (char**) buffer; | |
420 | ||
421 | *errnop = 0; | |
422 | return NSS_STATUS_SUCCESS; | |
423 | ||
424 | not_found: | |
425 | *errnop = 0; | |
426 | return NSS_STATUS_NOTFOUND; | |
427 | ||
428 | fail: | |
429 | *errnop = -r; | |
430 | return NSS_STATUS_UNAVAIL; | |
431 | } | |
432 | ||
433 | enum nss_status _nss_systemd_getgrgid_r( | |
434 | gid_t gid, | |
435 | struct group *gr, | |
436 | char *buffer, size_t buflen, | |
437 | int *errnop) { | |
438 | ||
439 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
440 | _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; | |
441 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
fd63e712 | 442 | _cleanup_free_ char *direct = NULL; |
409093fe LP |
443 | const char *translated; |
444 | size_t l; | |
689ca202 | 445 | int bypass, r; |
409093fe LP |
446 | |
447 | BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); | |
448 | ||
d6c575e3 LP |
449 | if (!gid_is_valid(gid)) |
450 | goto not_found; | |
409093fe | 451 | |
2129011e | 452 | /* Synthesize records for root and nobody, in case they are missing from /etc/group */ |
71e0accc | 453 | if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { |
fe102d6a LP |
454 | if (gid == root_group.gr_gid) { |
455 | *gr = root_group; | |
456 | *errnop = 0; | |
457 | return NSS_STATUS_SUCCESS; | |
458 | } | |
459 | if (gid == nobody_group.gr_gid) { | |
460 | *gr = nobody_group; | |
461 | *errnop = 0; | |
462 | return NSS_STATUS_SUCCESS; | |
463 | } | |
2129011e LP |
464 | } |
465 | ||
83438277 | 466 | if (!gid_is_dynamic(gid)) |
409093fe LP |
467 | goto not_found; |
468 | ||
71e0accc | 469 | if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) |
409093fe LP |
470 | goto not_found; |
471 | ||
689ca202 YW |
472 | bypass = getenv_bool_secure("SYSTEMD_NSS_BYPASS_BUS"); |
473 | if (bypass <= 0) { | |
fd63e712 | 474 | r = sd_bus_open_system(&bus); |
689ca202 YW |
475 | if (r < 0) { |
476 | bypass = 1; | |
477 | goto direct_lookup; | |
478 | } | |
fd63e712 LP |
479 | |
480 | r = sd_bus_call_method(bus, | |
481 | "org.freedesktop.systemd1", | |
482 | "/org/freedesktop/systemd1", | |
483 | "org.freedesktop.systemd1.Manager", | |
484 | "LookupDynamicUserByUID", | |
485 | &error, | |
486 | &reply, | |
487 | "u", | |
488 | (uint32_t) gid); | |
489 | if (r < 0) { | |
490 | if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) | |
491 | goto not_found; | |
492 | ||
493 | goto fail; | |
494 | } | |
495 | ||
496 | r = sd_bus_message_read(reply, "s", &translated); | |
497 | if (r < 0) | |
498 | goto fail; | |
409093fe LP |
499 | } |
500 | ||
689ca202 YW |
501 | direct_lookup: |
502 | if (bypass > 0) { | |
689ca202 YW |
503 | r = direct_lookup_uid(gid, &direct); |
504 | if (r == -ENOENT) | |
505 | goto not_found; | |
506 | if (r < 0) | |
507 | goto fail; | |
508 | ||
509 | translated = direct; | |
510 | } | |
511 | ||
409093fe LP |
512 | l = sizeof(char*) + strlen(translated) + 1; |
513 | if (buflen < l) { | |
cda458a5 | 514 | *errnop = ERANGE; |
409093fe LP |
515 | return NSS_STATUS_TRYAGAIN; |
516 | } | |
517 | ||
518 | memzero(buffer, sizeof(char*)); | |
519 | strcpy(buffer + sizeof(char*), translated); | |
520 | ||
521 | gr->gr_name = buffer + sizeof(char*); | |
522 | gr->gr_gid = gid; | |
523 | gr->gr_passwd = (char*) "*"; /* locked */ | |
524 | gr->gr_mem = (char**) buffer; | |
525 | ||
526 | *errnop = 0; | |
527 | return NSS_STATUS_SUCCESS; | |
528 | ||
529 | not_found: | |
530 | *errnop = 0; | |
531 | return NSS_STATUS_NOTFOUND; | |
532 | ||
533 | fail: | |
534 | *errnop = -r; | |
535 | return NSS_STATUS_UNAVAIL; | |
536 | } |