1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <sys/auxv.h>
4 
5 #include "conf-files.h"
6 #include "dirent-util.h"
7 #include "dlfcn-util.h"
8 #include "errno-util.h"
9 #include "fd-util.h"
10 #include "format-util.h"
11 #include "missing_syscall.h"
12 #include "parse-util.h"
13 #include "set.h"
14 #include "socket-util.h"
15 #include "strv.h"
16 #include "user-record-nss.h"
17 #include "user-util.h"
18 #include "userdb-dropin.h"
19 #include "userdb.h"
20 #include "varlink.h"
21 
22 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, Varlink, varlink_unref);
23 
24 typedef enum LookupWhat {
25         LOOKUP_USER,
26         LOOKUP_GROUP,
27         LOOKUP_MEMBERSHIP,
28         _LOOKUP_WHAT_MAX,
29 } LookupWhat;
30 
31 struct UserDBIterator {
32         LookupWhat what;
33         UserDBFlags flags;
34         Set *links;
35         bool nss_covered:1;
36         bool nss_iterating:1;
37         bool dropin_covered:1;
38         bool synthesize_root:1;
39         bool synthesize_nobody:1;
40         bool nss_systemd_blocked:1;
41         char **dropins;
42         size_t current_dropin;
43         int error;
44         unsigned n_found;
45         sd_event *event;
46         UserRecord *found_user;                   /* when .what == LOOKUP_USER */
47         GroupRecord *found_group;                 /* when .what == LOOKUP_GROUP */
48 
49         char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
50         char **members_of_group;
51         size_t index_members_of_group;
52         char *filter_user_name, *filter_group_name;
53 };
54 
userdb_iterator_free(UserDBIterator * iterator)55 UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
56         if (!iterator)
57                 return NULL;
58 
59         set_free(iterator->links);
60         strv_free(iterator->dropins);
61 
62         switch (iterator->what) {
63 
64         case LOOKUP_USER:
65                 user_record_unref(iterator->found_user);
66 
67                 if (iterator->nss_iterating)
68                         endpwent();
69 
70                 break;
71 
72         case LOOKUP_GROUP:
73                 group_record_unref(iterator->found_group);
74 
75                 if (iterator->nss_iterating)
76                         endgrent();
77 
78                 break;
79 
80         case LOOKUP_MEMBERSHIP:
81                 free(iterator->found_user_name);
82                 free(iterator->found_group_name);
83                 strv_free(iterator->members_of_group);
84                 free(iterator->filter_user_name);
85                 free(iterator->filter_group_name);
86 
87                 if (iterator->nss_iterating)
88                         endgrent();
89 
90                 break;
91 
92         default:
93                 assert_not_reached();
94         }
95 
96         sd_event_unref(iterator->event);
97 
98         if (iterator->nss_systemd_blocked)
99                 assert_se(userdb_block_nss_systemd(false) >= 0);
100 
101         return mfree(iterator);
102 }
103 
userdb_iterator_new(LookupWhat what,UserDBFlags flags)104 static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
105         UserDBIterator *i;
106 
107         assert(what >= 0);
108         assert(what < _LOOKUP_WHAT_MAX);
109 
110         i = new(UserDBIterator, 1);
111         if (!i)
112                 return NULL;
113 
114         *i = (UserDBIterator) {
115                 .what = what,
116                 .flags = flags,
117                 .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
118                 .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
119         };
120 
121         return i;
122 }
123 
userdb_iterator_block_nss_systemd(UserDBIterator * iterator)124 static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
125         int r;
126 
127         assert(iterator);
128 
129         if (iterator->nss_systemd_blocked)
130                 return 0;
131 
132         r = userdb_block_nss_systemd(true);
133         if (r < 0)
134                 return r;
135 
136         iterator->nss_systemd_blocked = true;
137         return 1;
138 }
139 
140 struct user_group_data {
141         JsonVariant *record;
142         bool incomplete;
143 };
144 
user_group_data_release(struct user_group_data * d)145 static void user_group_data_release(struct user_group_data *d) {
146         json_variant_unref(d->record);
147 }
148 
userdb_on_query_reply(Varlink * link,JsonVariant * parameters,const char * error_id,VarlinkReplyFlags flags,void * userdata)149 static int userdb_on_query_reply(
150                 Varlink *link,
151                 JsonVariant *parameters,
152                 const char *error_id,
153                 VarlinkReplyFlags flags,
154                 void *userdata) {
155 
156         UserDBIterator *iterator = userdata;
157         int r;
158 
159         assert(iterator);
160 
161         if (error_id) {
162                 log_debug("Got lookup error: %s", error_id);
163 
164                 if (STR_IN_SET(error_id,
165                                "io.systemd.UserDatabase.NoRecordFound",
166                                "io.systemd.UserDatabase.ConflictingRecordFound"))
167                         r = -ESRCH;
168                 else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
169                         r = -EHOSTDOWN;
170                 else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
171                         r = -EOPNOTSUPP;
172                 else if (streq(error_id, VARLINK_ERROR_TIMEOUT))
173                         r = -ETIMEDOUT;
174                 else
175                         r = -EIO;
176 
177                 goto finish;
178         }
179 
180         switch (iterator->what) {
181 
182         case LOOKUP_USER: {
183                 _cleanup_(user_group_data_release) struct user_group_data user_data = {};
184 
185                 static const JsonDispatch dispatch_table[] = {
186                         { "record",     _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
187                         { "incomplete", JSON_VARIANT_BOOLEAN,       json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
188                         {}
189                 };
190                 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
191 
192                 assert_se(!iterator->found_user);
193 
194                 r = json_dispatch(parameters, dispatch_table, NULL, 0, &user_data);
195                 if (r < 0)
196                         goto finish;
197 
198                 if (!user_data.record) {
199                         r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
200                         goto finish;
201                 }
202 
203                 hr = user_record_new();
204                 if (!hr) {
205                         r = -ENOMEM;
206                         goto finish;
207                 }
208 
209                 r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
210                 if (r < 0)
211                         goto finish;
212 
213                 if (!hr->service) {
214                         r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "User record does not carry service information, refusing.");
215                         goto finish;
216                 }
217 
218                 hr->incomplete = user_data.incomplete;
219 
220                 /* We match the root user by the name since the name is our primary key. We match the nobody
221                  * use by UID though, since the name might differ on OSes */
222                 if (streq_ptr(hr->user_name, "root"))
223                         iterator->synthesize_root = false;
224                 if (hr->uid == UID_NOBODY)
225                         iterator->synthesize_nobody = false;
226 
227                 iterator->found_user = TAKE_PTR(hr);
228                 iterator->n_found++;
229 
230                 /* More stuff coming? then let's just exit cleanly here */
231                 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
232                         return 0;
233 
234                 /* Otherwise, let's remove this link and exit cleanly then */
235                 r = 0;
236                 goto finish;
237         }
238 
239         case LOOKUP_GROUP: {
240                 _cleanup_(user_group_data_release) struct user_group_data group_data = {};
241 
242                 static const JsonDispatch dispatch_table[] = {
243                         { "record",     _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
244                         { "incomplete", JSON_VARIANT_BOOLEAN,       json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
245                         {}
246                 };
247                 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
248 
249                 assert_se(!iterator->found_group);
250 
251                 r = json_dispatch(parameters, dispatch_table, NULL, 0, &group_data);
252                 if (r < 0)
253                         goto finish;
254 
255                 if (!group_data.record) {
256                         r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
257                         goto finish;
258                 }
259 
260                 g = group_record_new();
261                 if (!g) {
262                         r = -ENOMEM;
263                         goto finish;
264                 }
265 
266                 r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
267                 if (r < 0)
268                         goto finish;
269 
270                 if (!g->service) {
271                         r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Group record does not carry service information, refusing.");
272                         goto finish;
273                 }
274 
275                 g->incomplete = group_data.incomplete;
276 
277                 if (streq_ptr(g->group_name, "root"))
278                         iterator->synthesize_root = false;
279                 if (g->gid == GID_NOBODY)
280                         iterator->synthesize_nobody = false;
281 
282                 iterator->found_group = TAKE_PTR(g);
283                 iterator->n_found++;
284 
285                 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
286                         return 0;
287 
288                 r = 0;
289                 goto finish;
290         }
291 
292         case LOOKUP_MEMBERSHIP: {
293                 struct membership_data {
294                         const char *user_name;
295                         const char *group_name;
296                 } membership_data = {};
297 
298                 static const JsonDispatch dispatch_table[] = {
299                         { "userName",  JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct membership_data, user_name),  JSON_SAFE },
300                         { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct membership_data, group_name), JSON_SAFE },
301                         {}
302                 };
303 
304                 assert(!iterator->found_user_name);
305                 assert(!iterator->found_group_name);
306 
307                 r = json_dispatch(parameters, dispatch_table, NULL, 0, &membership_data);
308                 if (r < 0)
309                         goto finish;
310 
311                 iterator->found_user_name = mfree(iterator->found_user_name);
312                 iterator->found_group_name = mfree(iterator->found_group_name);
313 
314                 iterator->found_user_name = strdup(membership_data.user_name);
315                 if (!iterator->found_user_name) {
316                         r = -ENOMEM;
317                         goto finish;
318                 }
319 
320                 iterator->found_group_name = strdup(membership_data.group_name);
321                 if (!iterator->found_group_name) {
322                         r = -ENOMEM;
323                         goto finish;
324                 }
325 
326                 iterator->n_found++;
327 
328                 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
329                         return 0;
330 
331                 r = 0;
332                 goto finish;
333         }
334 
335         default:
336                 assert_not_reached();
337         }
338 
339 finish:
340         /* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
341          * errors if at least one connection ended cleanly */
342         if (r == -ESRCH || iterator->error == 0)
343                 iterator->error = -r;
344 
345         assert_se(set_remove(iterator->links, link) == link);
346         link = varlink_unref(link);
347         return 0;
348 }
349 
userdb_connect(UserDBIterator * iterator,const char * path,const char * method,bool more,JsonVariant * query)350 static int userdb_connect(
351                 UserDBIterator *iterator,
352                 const char *path,
353                 const char *method,
354                 bool more,
355                 JsonVariant *query) {
356 
357         _cleanup_(varlink_unrefp) Varlink *vl = NULL;
358         int r;
359 
360         assert(iterator);
361         assert(path);
362         assert(method);
363 
364         r = varlink_connect_address(&vl, path);
365         if (r < 0)
366                 return log_debug_errno(r, "Unable to connect to %s: %m", path);
367 
368         varlink_set_userdata(vl, iterator);
369 
370         if (!iterator->event) {
371                 r = sd_event_new(&iterator->event);
372                 if (r < 0)
373                         return log_debug_errno(r, "Unable to allocate event loop: %m");
374         }
375 
376         r = varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
377         if (r < 0)
378                 return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
379 
380         (void) varlink_set_description(vl, path);
381 
382         r = varlink_bind_reply(vl, userdb_on_query_reply);
383         if (r < 0)
384                 return log_debug_errno(r, "Failed to bind reply callback: %m");
385 
386         if (more)
387                 r = varlink_observe(vl, method, query);
388         else
389                 r = varlink_invoke(vl, method, query);
390         if (r < 0)
391                 return log_debug_errno(r, "Failed to invoke varlink method: %m");
392 
393         r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
394         if (r < 0)
395                 return log_debug_errno(r, "Failed to add varlink connection to set: %m");
396         return r;
397 }
398 
userdb_start_query(UserDBIterator * iterator,const char * method,bool more,JsonVariant * query,UserDBFlags flags)399 static int userdb_start_query(
400                 UserDBIterator *iterator,
401                 const char *method,
402                 bool more,
403                 JsonVariant *query,
404                 UserDBFlags flags) {
405 
406         _cleanup_(strv_freep) char **except = NULL, **only = NULL;
407         _cleanup_(closedirp) DIR *d = NULL;
408         const char *e;
409         int r, ret = 0;
410 
411         assert(iterator);
412         assert(method);
413 
414         if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
415                 return -ENOLINK;
416 
417         e = getenv("SYSTEMD_BYPASS_USERDB");
418         if (e) {
419                 r = parse_boolean(e);
420                 if (r > 0)
421                         return -ENOLINK;
422                 if (r < 0) {
423                         except = strv_split(e, ":");
424                         if (!except)
425                                 return -ENOMEM;
426                 }
427         }
428 
429         e = getenv("SYSTEMD_ONLY_USERDB");
430         if (e) {
431                 only = strv_split(e, ":");
432                 if (!only)
433                         return -ENOMEM;
434         }
435 
436         /* First, let's talk to the multiplexer, if we can */
437         if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
438             !strv_contains(except, "io.systemd.Multiplexer") &&
439             (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
440                 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
441 
442                 r = json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer");
443                 if (r < 0)
444                         return log_debug_errno(r, "Unable to set service JSON field: %m");
445 
446                 r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
447                 if (r >= 0) {
448                         iterator->nss_covered = true; /* The multiplexer does NSS */
449                         iterator->dropin_covered = true; /* It also handles drop-in stuff */
450                         return 0;
451                 }
452         }
453 
454         d = opendir("/run/systemd/userdb/");
455         if (!d) {
456                 if (errno == ENOENT)
457                         return -ESRCH;
458 
459                 return -errno;
460         }
461 
462         FOREACH_DIRENT(de, d, return -errno) {
463                 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
464                 _cleanup_free_ char *p = NULL;
465                 bool is_nss, is_dropin;
466 
467                 if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
468                         continue;
469 
470                 if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
471                     streq(de->d_name, "io.systemd.DynamicUser"))
472                         continue;
473 
474                 /* Avoid NSS is this is requested. Note that we also skip NSS when we were asked to skip the
475                  * multiplexer, since in that case it's safer to do NSS in the client side emulation below
476                  * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
477                  * anyway). */
478                 is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
479                 if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
480                         continue;
481 
482                 /* Similar for the drop-in service */
483                 is_dropin = streq(de->d_name, "io.systemd.DropIn");
484                 if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
485                         continue;
486 
487                 if (strv_contains(except, de->d_name))
488                         continue;
489 
490                 if (only && !strv_contains(only, de->d_name))
491                         continue;
492 
493                 p = path_join("/run/systemd/userdb/", de->d_name);
494                 if (!p)
495                         return -ENOMEM;
496 
497                 patched_query = json_variant_ref(query);
498                 r = json_variant_set_field_string(&patched_query, "service", de->d_name);
499                 if (r < 0)
500                         return log_debug_errno(r, "Unable to set service JSON field: %m");
501 
502                 r = userdb_connect(iterator, p, method, more, patched_query);
503                 if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
504                                        * and could connect to it */
505                         iterator->nss_covered = true;
506                 if (is_dropin && r >= 0)
507                         iterator->dropin_covered = true;
508 
509                 if (ret == 0 && r < 0)
510                         ret = r;
511         }
512 
513         if (set_isempty(iterator->links))
514                 return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
515 
516         /* We connected to some services, in this case, ignore the ones we failed on */
517         return 0;
518 }
519 
userdb_process(UserDBIterator * iterator,UserRecord ** ret_user_record,GroupRecord ** ret_group_record,char ** ret_user_name,char ** ret_group_name)520 static int userdb_process(
521                 UserDBIterator *iterator,
522                 UserRecord **ret_user_record,
523                 GroupRecord **ret_group_record,
524                 char **ret_user_name,
525                 char **ret_group_name) {
526 
527         int r;
528 
529         assert(iterator);
530 
531         for (;;) {
532                 if (iterator->what == LOOKUP_USER && iterator->found_user) {
533                         if (ret_user_record)
534                                 *ret_user_record = TAKE_PTR(iterator->found_user);
535                         else
536                                 iterator->found_user = user_record_unref(iterator->found_user);
537 
538                         if (ret_group_record)
539                                 *ret_group_record = NULL;
540                         if (ret_user_name)
541                                 *ret_user_name = NULL;
542                         if (ret_group_name)
543                                 *ret_group_name = NULL;
544 
545                         return 0;
546                 }
547 
548                 if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
549                         if (ret_group_record)
550                                 *ret_group_record = TAKE_PTR(iterator->found_group);
551                         else
552                                 iterator->found_group = group_record_unref(iterator->found_group);
553 
554                         if (ret_user_record)
555                                 *ret_user_record = NULL;
556                         if (ret_user_name)
557                                 *ret_user_name = NULL;
558                         if (ret_group_name)
559                                 *ret_group_name = NULL;
560 
561                         return 0;
562                 }
563 
564                 if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
565                         if (ret_user_name)
566                                 *ret_user_name = TAKE_PTR(iterator->found_user_name);
567                         else
568                                 iterator->found_user_name = mfree(iterator->found_user_name);
569 
570                         if (ret_group_name)
571                                 *ret_group_name = TAKE_PTR(iterator->found_group_name);
572                         else
573                                 iterator->found_group_name = mfree(iterator->found_group_name);
574 
575                         if (ret_user_record)
576                                 *ret_user_record = NULL;
577                         if (ret_group_record)
578                                 *ret_group_record = NULL;
579 
580                         return 0;
581                 }
582 
583                 if (set_isempty(iterator->links)) {
584                         if (iterator->error == 0)
585                                 return -ESRCH;
586 
587                         return -abs(iterator->error);
588                 }
589 
590                 if (!iterator->event)
591                         return -ESRCH;
592 
593                 r = sd_event_run(iterator->event, UINT64_MAX);
594                 if (r < 0)
595                         return r;
596         }
597 }
598 
synthetic_root_user_build(UserRecord ** ret)599 static int synthetic_root_user_build(UserRecord **ret) {
600         return user_record_build(
601                         ret,
602                         JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
603                                           JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
604                                           JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
605                                           JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
606                                           JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
607 }
608 
synthetic_nobody_user_build(UserRecord ** ret)609 static int synthetic_nobody_user_build(UserRecord **ret) {
610         return user_record_build(
611                         ret,
612                         JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
613                                           JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY)),
614                                           JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
615                                           JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
616                                           JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
617                                           JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
618 }
619 
userdb_by_name(const char * name,UserDBFlags flags,UserRecord ** ret)620 int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
621         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
622         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
623         int r;
624 
625         if (!valid_user_group_name(name, VALID_USER_RELAX))
626                 return -EINVAL;
627 
628         r = json_build(&query, JSON_BUILD_OBJECT(
629                                        JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
630         if (r < 0)
631                 return r;
632 
633         iterator = userdb_iterator_new(LOOKUP_USER, flags);
634         if (!iterator)
635                 return -ENOMEM;
636 
637         r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
638         if (r >= 0) {
639                 r = userdb_process(iterator, ret, NULL, NULL, NULL);
640                 if (r >= 0)
641                         return r;
642         }
643 
644         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
645                 r = dropin_user_record_by_name(name, NULL, flags, ret);
646                 if (r >= 0)
647                         return r;
648         }
649 
650         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
651                 /* Make sure the NSS lookup doesn't recurse back to us. */
652 
653                 r = userdb_iterator_block_nss_systemd(iterator);
654                 if (r >= 0) {
655                         /* Client-side NSS fallback */
656                         r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
657                         if (r >= 0)
658                                 return r;
659                 }
660         }
661 
662         if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
663                 if (streq(name, "root"))
664                         return synthetic_root_user_build(ret);
665 
666                 if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
667                         return synthetic_nobody_user_build(ret);
668         }
669 
670         return r;
671 }
672 
userdb_by_uid(uid_t uid,UserDBFlags flags,UserRecord ** ret)673 int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
674         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
675         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
676         int r;
677 
678         if (!uid_is_valid(uid))
679                 return -EINVAL;
680 
681         r = json_build(&query, JSON_BUILD_OBJECT(
682                                        JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid))));
683         if (r < 0)
684                 return r;
685 
686         iterator = userdb_iterator_new(LOOKUP_USER, flags);
687         if (!iterator)
688                 return -ENOMEM;
689 
690         r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
691         if (r >= 0) {
692                 r = userdb_process(iterator, ret, NULL, NULL, NULL);
693                 if (r >= 0)
694                         return r;
695         }
696 
697         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
698                 r = dropin_user_record_by_uid(uid, NULL, flags, ret);
699                 if (r >= 0)
700                         return r;
701         }
702 
703         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
704                 r = userdb_iterator_block_nss_systemd(iterator);
705                 if (r >= 0) {
706                         /* Client-side NSS fallback */
707                         r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
708                         if (r >= 0)
709                                 return r;
710                 }
711         }
712 
713         if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
714                 if (uid == 0)
715                         return synthetic_root_user_build(ret);
716 
717                 if (uid == UID_NOBODY && synthesize_nobody())
718                         return synthetic_nobody_user_build(ret);
719         }
720 
721         return r;
722 }
723 
userdb_all(UserDBFlags flags,UserDBIterator ** ret)724 int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
725         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
726         int r, qr;
727 
728         assert(ret);
729 
730         iterator = userdb_iterator_new(LOOKUP_USER, flags);
731         if (!iterator)
732                 return -ENOMEM;
733 
734         qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
735 
736         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
737                 r = userdb_iterator_block_nss_systemd(iterator);
738                 if (r < 0)
739                         return r;
740 
741                 setpwent();
742                 iterator->nss_iterating = true;
743         }
744 
745         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
746                 r = conf_files_list_nulstr(
747                                 &iterator->dropins,
748                                 ".user",
749                                 NULL,
750                                 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
751                                 USERDB_DROPIN_DIR_NULSTR("userdb"));
752                 if (r < 0)
753                         log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
754         }
755 
756         /* propagate IPC error, but only if there are no drop-ins */
757         if (qr < 0 &&
758             !iterator->nss_iterating &&
759             strv_isempty(iterator->dropins))
760                 return qr;
761 
762         *ret = TAKE_PTR(iterator);
763         return 0;
764 }
765 
userdb_iterator_get(UserDBIterator * iterator,UserRecord ** ret)766 int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
767         int r;
768 
769         assert(iterator);
770         assert(iterator->what == LOOKUP_USER);
771 
772         if (iterator->nss_iterating) {
773                 struct passwd *pw;
774 
775                 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
776                  * the more traditional sources, which are probably good to show first. */
777 
778                 pw = getpwent();
779                 if (pw) {
780                         _cleanup_free_ char *buffer = NULL;
781                         bool incomplete = false;
782                         struct spwd spwd;
783 
784                         if (streq_ptr(pw->pw_name, "root"))
785                                 iterator->synthesize_root = false;
786                         if (pw->pw_uid == UID_NOBODY)
787                                 iterator->synthesize_nobody = false;
788 
789                         if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
790                                 r = nss_spwd_for_passwd(pw, &spwd, &buffer);
791                                 if (r < 0) {
792                                         log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
793                                         incomplete = ERRNO_IS_PRIVILEGE(r);
794                                 }
795                         } else {
796                                 r = -EUCLEAN;
797                                 incomplete = true;
798                         }
799 
800                         r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
801                         if (r < 0)
802                                 return r;
803 
804                         if (ret)
805                                 (*ret)->incomplete = incomplete;
806 
807                         iterator->n_found++;
808                         return r;
809                 }
810 
811                 if (errno != 0)
812                         log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
813 
814                 iterator->nss_iterating = false;
815                 endpwent();
816         }
817 
818         for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
819                 const char *i = iterator->dropins[iterator->current_dropin];
820                 _cleanup_free_ char *fn = NULL;
821                 uid_t uid;
822                 char *e;
823 
824                 /* Next, let's add in the static drop-ins, which are quick to retrieve */
825 
826                 r = path_extract_filename(i, &fn);
827                 if (r < 0)
828                         return r;
829 
830                 e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
831                 if (!e)
832                         continue;
833 
834                 *e = 0; /* Chop off suffix */
835 
836                 if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
837                         continue;
838 
839                 r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
840                 if (r < 0) {
841                         log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
842                         continue; /* If we failed to parse this record, let's suppress it from enumeration,
843                                    * and continue with the next record. Maybe someone is dropping it files
844                                    * and only partially wrote this one. */
845                 }
846 
847                 iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
848                 iterator->n_found++;
849                 return 0;
850         }
851 
852         /* Then, let's return the users provided by varlink IPC */
853         r = userdb_process(iterator, ret, NULL, NULL, NULL);
854         if (r < 0) {
855 
856                 /* Finally, synthesize root + nobody if not done yet */
857                 if (iterator->synthesize_root) {
858                         iterator->synthesize_root = false;
859                         iterator->n_found++;
860                         return synthetic_root_user_build(ret);
861                 }
862 
863                 if (iterator->synthesize_nobody) {
864                         iterator->synthesize_nobody = false;
865                         iterator->n_found++;
866                         return synthetic_nobody_user_build(ret);
867                 }
868 
869                 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
870                 if (iterator->n_found > 0)
871                         return -ESRCH;
872         }
873 
874         return r;
875 }
876 
synthetic_root_group_build(GroupRecord ** ret)877 static int synthetic_root_group_build(GroupRecord **ret) {
878         return group_record_build(
879                         ret,
880                         JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
881                                           JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
882                                           JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
883 }
884 
synthetic_nobody_group_build(GroupRecord ** ret)885 static int synthetic_nobody_group_build(GroupRecord **ret) {
886         return group_record_build(
887                         ret,
888                         JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
889                                           JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
890                                           JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
891 }
892 
groupdb_by_name(const char * name,UserDBFlags flags,GroupRecord ** ret)893 int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
894         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
895         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
896         int r;
897 
898         if (!valid_user_group_name(name, VALID_USER_RELAX))
899                 return -EINVAL;
900 
901         r = json_build(&query, JSON_BUILD_OBJECT(
902                                        JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
903         if (r < 0)
904                 return r;
905 
906         iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
907         if (!iterator)
908                 return -ENOMEM;
909 
910         r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
911         if (r >= 0) {
912                 r = userdb_process(iterator, NULL, ret, NULL, NULL);
913                 if (r >= 0)
914                         return r;
915         }
916 
917         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
918                 r = dropin_group_record_by_name(name, NULL, flags, ret);
919                 if (r >= 0)
920                         return r;
921         }
922 
923 
924         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
925                 r = userdb_iterator_block_nss_systemd(iterator);
926                 if (r >= 0) {
927                         r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
928                         if (r >= 0)
929                                 return r;
930                 }
931         }
932 
933         if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
934                 if (streq(name, "root"))
935                         return synthetic_root_group_build(ret);
936 
937                 if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
938                         return synthetic_nobody_group_build(ret);
939         }
940 
941         return r;
942 }
943 
groupdb_by_gid(gid_t gid,UserDBFlags flags,GroupRecord ** ret)944 int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
945         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
946         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
947         int r;
948 
949         if (!gid_is_valid(gid))
950                 return -EINVAL;
951 
952         r = json_build(&query, JSON_BUILD_OBJECT(
953                                        JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))));
954         if (r < 0)
955                 return r;
956 
957         iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
958         if (!iterator)
959                 return -ENOMEM;
960 
961         r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
962         if (r >= 0) {
963                 r = userdb_process(iterator, NULL, ret, NULL, NULL);
964                 if (r >= 0)
965                         return r;
966         }
967 
968         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
969                 r = dropin_group_record_by_gid(gid, NULL, flags, ret);
970                 if (r >= 0)
971                         return r;
972         }
973 
974         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
975                 r = userdb_iterator_block_nss_systemd(iterator);
976                 if (r >= 0) {
977                         r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
978                         if (r >= 0)
979                                 return r;
980                 }
981         }
982 
983         if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
984                 if (gid == 0)
985                         return synthetic_root_group_build(ret);
986 
987                 if (gid == GID_NOBODY && synthesize_nobody())
988                         return synthetic_nobody_group_build(ret);
989         }
990 
991         return r;
992 }
993 
groupdb_all(UserDBFlags flags,UserDBIterator ** ret)994 int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
995         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
996         int r, qr;
997 
998         assert(ret);
999 
1000         iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
1001         if (!iterator)
1002                 return -ENOMEM;
1003 
1004         qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
1005 
1006         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1007                 r = userdb_iterator_block_nss_systemd(iterator);
1008                 if (r < 0)
1009                         return r;
1010 
1011                 setgrent();
1012                 iterator->nss_iterating = true;
1013         }
1014 
1015         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
1016                 r = conf_files_list_nulstr(
1017                                 &iterator->dropins,
1018                                 ".group",
1019                                 NULL,
1020                                 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1021                                 USERDB_DROPIN_DIR_NULSTR("userdb"));
1022                 if (r < 0)
1023                         log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
1024         }
1025 
1026         if (qr < 0 &&
1027             !iterator->nss_iterating &&
1028             strv_isempty(iterator->dropins))
1029                 return qr;
1030 
1031         *ret = TAKE_PTR(iterator);
1032         return 0;
1033 }
1034 
groupdb_iterator_get(UserDBIterator * iterator,GroupRecord ** ret)1035 int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
1036         int r;
1037 
1038         assert(iterator);
1039         assert(iterator->what == LOOKUP_GROUP);
1040 
1041         if (iterator->nss_iterating) {
1042                 struct group *gr;
1043 
1044                 errno = 0;
1045                 gr = getgrent();
1046                 if (gr) {
1047                         _cleanup_free_ char *buffer = NULL;
1048                         bool incomplete = false;
1049                         struct sgrp sgrp;
1050 
1051                         if (streq_ptr(gr->gr_name, "root"))
1052                                 iterator->synthesize_root = false;
1053                         if (gr->gr_gid == GID_NOBODY)
1054                                 iterator->synthesize_nobody = false;
1055 
1056                         if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
1057                                 r = nss_sgrp_for_group(gr, &sgrp, &buffer);
1058                                 if (r < 0) {
1059                                         log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
1060                                         incomplete = ERRNO_IS_PRIVILEGE(r);
1061                                 }
1062                         } else {
1063                                 r = -EUCLEAN;
1064                                 incomplete = true;
1065                         }
1066 
1067                         r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
1068                         if (r < 0)
1069                                 return r;
1070 
1071                         if (ret)
1072                                 (*ret)->incomplete = incomplete;
1073 
1074                         iterator->n_found++;
1075                         return r;
1076                 }
1077 
1078                 if (errno != 0)
1079                         log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
1080 
1081                 iterator->nss_iterating = false;
1082                 endgrent();
1083         }
1084 
1085         for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1086                 const char *i = iterator->dropins[iterator->current_dropin];
1087                 _cleanup_free_ char *fn = NULL;
1088                 gid_t gid;
1089                 char *e;
1090 
1091                 r = path_extract_filename(i, &fn);
1092                 if (r < 0)
1093                         return r;
1094 
1095                 e = endswith(fn, ".group");
1096                 if (!e)
1097                         continue;
1098 
1099                 *e = 0; /* Chop off suffix */
1100 
1101                 if (parse_gid(fn, &gid) < 0)
1102                         continue;
1103 
1104                 r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
1105                 if (r < 0) {
1106                         log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
1107                         continue;
1108                 }
1109 
1110                 iterator->current_dropin++;
1111                 iterator->n_found++;
1112                 return 0;
1113         }
1114 
1115         r = userdb_process(iterator, NULL, ret, NULL, NULL);
1116         if (r < 0) {
1117                 if (iterator->synthesize_root) {
1118                         iterator->synthesize_root = false;
1119                         iterator->n_found++;
1120                         return synthetic_root_group_build(ret);
1121                 }
1122 
1123                 if (iterator->synthesize_nobody) {
1124                         iterator->synthesize_nobody = false;
1125                         iterator->n_found++;
1126                         return synthetic_nobody_group_build(ret);
1127                 }
1128 
1129                 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1130                 if (iterator->n_found > 0)
1131                         return -ESRCH;
1132         }
1133 
1134         return r;
1135 }
1136 
discover_membership_dropins(UserDBIterator * i,UserDBFlags flags)1137 static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
1138         int r;
1139 
1140         r = conf_files_list_nulstr(
1141                         &i->dropins,
1142                         ".membership",
1143                         NULL,
1144                         CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1145                         USERDB_DROPIN_DIR_NULSTR("userdb"));
1146         if (r < 0)
1147                 log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
1148 }
1149 
membershipdb_by_user(const char * name,UserDBFlags flags,UserDBIterator ** ret)1150 int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1151         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1152         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1153         int r, qr;
1154 
1155         assert(ret);
1156 
1157         if (!valid_user_group_name(name, VALID_USER_RELAX))
1158                 return -EINVAL;
1159 
1160         r = json_build(&query, JSON_BUILD_OBJECT(
1161                                        JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
1162         if (r < 0)
1163                 return r;
1164 
1165         iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1166         if (!iterator)
1167                 return -ENOMEM;
1168 
1169         iterator->filter_user_name = strdup(name);
1170         if (!iterator->filter_user_name)
1171                 return -ENOMEM;
1172 
1173         qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1174 
1175         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1176                 r = userdb_iterator_block_nss_systemd(iterator);
1177                 if (r < 0)
1178                         return r;
1179 
1180                 setgrent();
1181                 iterator->nss_iterating = true;
1182         }
1183 
1184         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1185                 discover_membership_dropins(iterator, flags);
1186 
1187         if (qr < 0 &&
1188             !iterator->nss_iterating &&
1189             strv_isempty(iterator->dropins))
1190                 return qr;
1191 
1192         *ret = TAKE_PTR(iterator);
1193         return 0;
1194 }
1195 
membershipdb_by_group(const char * name,UserDBFlags flags,UserDBIterator ** ret)1196 int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1197         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1198         _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1199         int r, qr;
1200 
1201         assert(ret);
1202 
1203         if (!valid_user_group_name(name, VALID_USER_RELAX))
1204                 return -EINVAL;
1205 
1206         r = json_build(&query, JSON_BUILD_OBJECT(
1207                                        JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
1208         if (r < 0)
1209                 return r;
1210 
1211         iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1212         if (!iterator)
1213                 return -ENOMEM;
1214 
1215         iterator->filter_group_name = strdup(name);
1216         if (!iterator->filter_group_name)
1217                 return -ENOMEM;
1218 
1219         qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1220 
1221         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1222                 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
1223 
1224                 r = userdb_iterator_block_nss_systemd(iterator);
1225                 if (r < 0)
1226                         return r;
1227 
1228                 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1229                 (void) nss_group_record_by_name(name, false, &gr);
1230                 if (gr) {
1231                         iterator->members_of_group = strv_copy(gr->members);
1232                         if (!iterator->members_of_group)
1233                                 return -ENOMEM;
1234 
1235                         iterator->index_members_of_group = 0;
1236 
1237                         iterator->found_group_name = strdup(name);
1238                         if (!iterator->found_group_name)
1239                                 return -ENOMEM;
1240                 }
1241         }
1242 
1243         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1244                 discover_membership_dropins(iterator, flags);
1245 
1246         if (qr < 0 &&
1247             strv_isempty(iterator->members_of_group) &&
1248             strv_isempty(iterator->dropins))
1249                 return qr;
1250 
1251         *ret = TAKE_PTR(iterator);
1252         return 0;
1253 }
1254 
membershipdb_all(UserDBFlags flags,UserDBIterator ** ret)1255 int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
1256         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1257         int r, qr;
1258 
1259         assert(ret);
1260 
1261         iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1262         if (!iterator)
1263                 return -ENOMEM;
1264 
1265         qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
1266 
1267         if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1268                 r = userdb_iterator_block_nss_systemd(iterator);
1269                 if (r < 0)
1270                         return r;
1271 
1272                 setgrent();
1273                 iterator->nss_iterating = true;
1274         }
1275 
1276         if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1277                 discover_membership_dropins(iterator, flags);
1278 
1279         if (qr < 0 &&
1280             !iterator->nss_iterating &&
1281             strv_isempty(iterator->dropins))
1282                 return qr;
1283 
1284         *ret = TAKE_PTR(iterator);
1285         return 0;
1286 }
1287 
membershipdb_iterator_get(UserDBIterator * iterator,char ** ret_user,char ** ret_group)1288 int membershipdb_iterator_get(
1289                 UserDBIterator *iterator,
1290                 char **ret_user,
1291                 char **ret_group) {
1292 
1293         int r;
1294 
1295         assert(iterator);
1296 
1297         for (;;) {
1298                 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1299                 if (!iterator->members_of_group) {
1300                         struct group *g;
1301 
1302                         if (!iterator->nss_iterating)
1303                                 break;
1304 
1305                         assert(!iterator->found_user_name);
1306                         do {
1307                                 errno = 0;
1308                                 g = getgrent();
1309                                 if (!g) {
1310                                         if (errno != 0)
1311                                                 log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
1312                                         break;
1313                                 }
1314 
1315                         } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
1316                                                               strv_isempty(g->gr_mem));
1317 
1318                         if (g) {
1319                                 r = free_and_strdup(&iterator->found_group_name, g->gr_name);
1320                                 if (r < 0)
1321                                         return r;
1322 
1323                                 if (iterator->filter_user_name)
1324                                         iterator->members_of_group = strv_new(iterator->filter_user_name);
1325                                 else
1326                                         iterator->members_of_group = strv_copy(g->gr_mem);
1327                                 if (!iterator->members_of_group)
1328                                         return -ENOMEM;
1329 
1330                                 iterator->index_members_of_group = 0;
1331                         } else {
1332                                 iterator->nss_iterating = false;
1333                                 endgrent();
1334                                 break;
1335                         }
1336                 }
1337 
1338                 assert(iterator->found_group_name);
1339                 assert(iterator->members_of_group);
1340                 assert(!iterator->found_user_name);
1341 
1342                 if (iterator->members_of_group[iterator->index_members_of_group]) {
1343                         _cleanup_free_ char *cu = NULL, *cg = NULL;
1344 
1345                         if (ret_user) {
1346                                 cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
1347                                 if (!cu)
1348                                         return -ENOMEM;
1349                         }
1350 
1351                         if (ret_group) {
1352                                 cg = strdup(iterator->found_group_name);
1353                                 if (!cg)
1354                                         return -ENOMEM;
1355                         }
1356 
1357                         if (ret_user)
1358                                 *ret_user = TAKE_PTR(cu);
1359 
1360                         if (ret_group)
1361                                 *ret_group = TAKE_PTR(cg);
1362 
1363                         iterator->index_members_of_group++;
1364                         return 0;
1365                 }
1366 
1367                 iterator->members_of_group = strv_free(iterator->members_of_group);
1368                 iterator->found_group_name = mfree(iterator->found_group_name);
1369         }
1370 
1371         for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1372                 const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
1373                 _cleanup_free_ char *un = NULL, *gn = NULL;
1374 
1375                 e = endswith(i, ".membership");
1376                 if (!e)
1377                         continue;
1378 
1379                 c = memchr(i, ':', e - i);
1380                 if (!c)
1381                         continue;
1382 
1383                 un = strndup(i, c - i);
1384                 if (!un)
1385                         return -ENOMEM;
1386                 if (iterator->filter_user_name) {
1387                         if (!streq(un, iterator->filter_user_name))
1388                                 continue;
1389                 } else if (!valid_user_group_name(un, VALID_USER_RELAX))
1390                         continue;
1391 
1392                 c++; /* skip over ':' */
1393                 gn = strndup(c, e - c);
1394                 if (!gn)
1395                         return -ENOMEM;
1396                 if (iterator->filter_group_name) {
1397                         if (!streq(gn, iterator->filter_group_name))
1398                                 continue;
1399                 } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
1400                         continue;
1401 
1402                 iterator->current_dropin++;
1403                 iterator->n_found++;
1404 
1405                 if (ret_user)
1406                         *ret_user = TAKE_PTR(un);
1407                 if (ret_group)
1408                         *ret_group = TAKE_PTR(gn);
1409 
1410                 return 0;
1411         }
1412 
1413         r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
1414         if (r < 0 && iterator->n_found > 0)
1415                 return -ESRCH;
1416 
1417         return r;
1418 }
1419 
membershipdb_by_group_strv(const char * name,UserDBFlags flags,char *** ret)1420 int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
1421         _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1422         _cleanup_strv_free_ char **members = NULL;
1423         int r;
1424 
1425         assert(name);
1426         assert(ret);
1427 
1428         r = membershipdb_by_group(name, flags, &iterator);
1429         if (r < 0)
1430                 return r;
1431 
1432         for (;;) {
1433                 _cleanup_free_ char *user_name = NULL;
1434 
1435                 r = membershipdb_iterator_get(iterator, &user_name, NULL);
1436                 if (r == -ESRCH)
1437                         break;
1438                 if (r < 0)
1439                         return r;
1440 
1441                 r = strv_consume(&members, TAKE_PTR(user_name));
1442                 if (r < 0)
1443                         return r;
1444         }
1445 
1446         strv_sort(members);
1447         strv_uniq(members);
1448 
1449         *ret = TAKE_PTR(members);
1450         return 0;
1451 }
1452 
userdb_block_nss_systemd(int b)1453 int userdb_block_nss_systemd(int b) {
1454         _cleanup_(dlclosep) void *dl = NULL;
1455         int (*call)(bool b);
1456 
1457         /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1458 
1459         dl = dlopen(ROOTLIBDIR "/libnss_systemd.so.2", RTLD_LAZY|RTLD_NODELETE);
1460         if (!dl) {
1461                 /* If the file isn't installed, don't complain loudly */
1462                 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1463                 return 0;
1464         }
1465 
1466         call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
1467         if (!call)
1468                 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1469                 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
1470                                        "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1471 
1472         return call(b);
1473 }
1474