]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/userdb/userdbctl.c
add ipv6 range element creation test cases
[thirdparty/systemd.git] / src / userdb / userdbctl.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <utmp.h>
5
6 #include "dirent-util.h"
7 #include "errno-list.h"
8 #include "fd-util.h"
9 #include "format-table.h"
10 #include "format-util.h"
11 #include "main-func.h"
12 #include "pager.h"
13 #include "parse-util.h"
14 #include "pretty-print.h"
15 #include "socket-util.h"
16 #include "strv.h"
17 #include "terminal-util.h"
18 #include "user-record-show.h"
19 #include "user-util.h"
20 #include "userdb.h"
21 #include "verbs.h"
22
23 static enum {
24 OUTPUT_CLASSIC,
25 OUTPUT_TABLE,
26 OUTPUT_FRIENDLY,
27 OUTPUT_JSON,
28 _OUTPUT_INVALID = -1
29 } arg_output = _OUTPUT_INVALID;
30
31 static PagerFlags arg_pager_flags = 0;
32 static bool arg_legend = true;
33 static char** arg_services = NULL;
34 static UserDBFlags arg_userdb_flags = 0;
35
36 STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep);
37
38 static int show_user(UserRecord *ur, Table *table) {
39 int r;
40
41 assert(ur);
42
43 switch (arg_output) {
44
45 case OUTPUT_CLASSIC:
46 if (!uid_is_valid(ur->uid))
47 break;
48
49 printf("%s:x:" UID_FMT ":" GID_FMT ":%s:%s:%s\n",
50 ur->user_name,
51 ur->uid,
52 user_record_gid(ur),
53 strempty(user_record_real_name(ur)),
54 user_record_home_directory(ur),
55 user_record_shell(ur));
56
57 break;
58
59 case OUTPUT_JSON:
60 json_variant_dump(ur->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
61 break;
62
63 case OUTPUT_FRIENDLY:
64 user_record_show(ur, true);
65
66 if (ur->incomplete) {
67 fflush(stdout);
68 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
69 }
70
71 break;
72
73 case OUTPUT_TABLE:
74 assert(table);
75
76 r = table_add_many(
77 table,
78 TABLE_STRING, ur->user_name,
79 TABLE_STRING, user_disposition_to_string(user_record_disposition(ur)),
80 TABLE_UID, ur->uid,
81 TABLE_GID, user_record_gid(ur),
82 TABLE_STRING, empty_to_null(ur->real_name),
83 TABLE_STRING, user_record_home_directory(ur),
84 TABLE_STRING, user_record_shell(ur),
85 TABLE_INT, (int) user_record_disposition(ur));
86 if (r < 0)
87 return table_log_add_error(r);
88
89 break;
90
91 default:
92 assert_not_reached("Unexpected output mode");
93 }
94
95 return 0;
96 }
97
98 static int display_user(int argc, char *argv[], void *userdata) {
99 _cleanup_(table_unrefp) Table *table = NULL;
100 bool draw_separator = false;
101 int ret = 0, r;
102
103 if (arg_output < 0)
104 arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
105
106 if (arg_output == OUTPUT_TABLE) {
107 table = table_new("name", "disposition", "uid", "gid", "realname", "home", "shell", "disposition-numeric");
108 if (!table)
109 return log_oom();
110
111 (void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100);
112 (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
113 (void) table_set_empty_string(table, "-");
114 (void) table_set_sort(table, (size_t) 7, (size_t) 2, (size_t) -1);
115 (void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 5, (size_t) 6, (size_t) -1);
116 }
117
118 if (argc > 1) {
119 char **i;
120
121 STRV_FOREACH(i, argv + 1) {
122 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
123 uid_t uid;
124
125 if (parse_uid(*i, &uid) >= 0)
126 r = userdb_by_uid(uid, arg_userdb_flags, &ur);
127 else
128 r = userdb_by_name(*i, arg_userdb_flags, &ur);
129 if (r < 0) {
130 if (r == -ESRCH)
131 log_error_errno(r, "User %s does not exist.", *i);
132 else if (r == -EHOSTDOWN)
133 log_error_errno(r, "Selected user database service is not available for this request.");
134 else
135 log_error_errno(r, "Failed to find user %s: %m", *i);
136
137 if (ret >= 0)
138 ret = r;
139 } else {
140 if (draw_separator && arg_output == OUTPUT_FRIENDLY)
141 putchar('\n');
142
143 r = show_user(ur, table);
144 if (r < 0)
145 return r;
146
147 draw_separator = true;
148 }
149 }
150 } else {
151 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
152
153 r = userdb_all(arg_userdb_flags, &iterator);
154 if (r < 0)
155 return log_error_errno(r, "Failed to enumerate users: %m");
156
157 for (;;) {
158 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
159
160 r = userdb_iterator_get(iterator, &ur);
161 if (r == -ESRCH)
162 break;
163 if (r == -EHOSTDOWN)
164 return log_error_errno(r, "Selected user database service is not available for this request.");
165 if (r < 0)
166 return log_error_errno(r, "Failed acquire next user: %m");
167
168 if (draw_separator && arg_output == OUTPUT_FRIENDLY)
169 putchar('\n');
170
171 r = show_user(ur, table);
172 if (r < 0)
173 return r;
174
175 draw_separator = true;
176 }
177 }
178
179 if (table) {
180 r = table_print(table, NULL);
181 if (r < 0)
182 return table_log_print_error(r);
183 }
184
185 return ret;
186 }
187
188 static int show_group(GroupRecord *gr, Table *table) {
189 int r;
190
191 assert(gr);
192
193 switch (arg_output) {
194
195 case OUTPUT_CLASSIC: {
196 _cleanup_free_ char *m = NULL;
197
198 if (!gid_is_valid(gr->gid))
199 break;
200
201 m = strv_join(gr->members, ",");
202 if (!m)
203 return log_oom();
204
205 printf("%s:x:" GID_FMT ":%s\n",
206 gr->group_name,
207 gr->gid,
208 m);
209 break;
210 }
211
212 case OUTPUT_JSON:
213 json_variant_dump(gr->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
214 break;
215
216 case OUTPUT_FRIENDLY:
217 group_record_show(gr, true);
218
219 if (gr->incomplete) {
220 fflush(stdout);
221 log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr->group_name);
222 }
223
224 break;
225
226 case OUTPUT_TABLE:
227 assert(table);
228
229 r = table_add_many(
230 table,
231 TABLE_STRING, gr->group_name,
232 TABLE_STRING, user_disposition_to_string(group_record_disposition(gr)),
233 TABLE_GID, gr->gid,
234 TABLE_STRING, gr->description,
235 TABLE_INT, (int) group_record_disposition(gr));
236 if (r < 0)
237 return table_log_add_error(r);
238
239 break;
240
241 default:
242 assert_not_reached("Unexpected display mode");
243 }
244
245 return 0;
246 }
247
248
249 static int display_group(int argc, char *argv[], void *userdata) {
250 _cleanup_(table_unrefp) Table *table = NULL;
251 bool draw_separator = false;
252 int ret = 0, r;
253
254 if (arg_output < 0)
255 arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
256
257 if (arg_output == OUTPUT_TABLE) {
258 table = table_new("name", "disposition", "gid", "description", "disposition-numeric");
259 if (!table)
260 return log_oom();
261
262 (void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100);
263 (void) table_set_empty_string(table, "-");
264 (void) table_set_sort(table, (size_t) 3, (size_t) 2, (size_t) -1);
265 (void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) -1);
266 }
267
268 if (argc > 1) {
269 char **i;
270
271 STRV_FOREACH(i, argv + 1) {
272 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
273 gid_t gid;
274
275 if (parse_gid(*i, &gid) >= 0)
276 r = groupdb_by_gid(gid, arg_userdb_flags, &gr);
277 else
278 r = groupdb_by_name(*i, arg_userdb_flags, &gr);
279 if (r < 0) {
280 if (r == -ESRCH)
281 log_error_errno(r, "Group %s does not exist.", *i);
282 else if (r == -EHOSTDOWN)
283 log_error_errno(r, "Selected group database service is not available for this request.");
284 else
285 log_error_errno(r, "Failed to find group %s: %m", *i);
286
287 if (ret >= 0)
288 ret = r;
289 } else {
290 if (draw_separator && arg_output == OUTPUT_FRIENDLY)
291 putchar('\n');
292
293 r = show_group(gr, table);
294 if (r < 0)
295 return r;
296
297 draw_separator = true;
298 }
299 }
300
301 } else {
302 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
303
304 r = groupdb_all(arg_userdb_flags, &iterator);
305 if (r < 0)
306 return log_error_errno(r, "Failed to enumerate groups: %m");
307
308 for (;;) {
309 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
310
311 r = groupdb_iterator_get(iterator, &gr);
312 if (r == -ESRCH)
313 break;
314 if (r == -EHOSTDOWN)
315 return log_error_errno(r, "Selected group database service is not available for this request.");
316 if (r < 0)
317 return log_error_errno(r, "Failed acquire next group: %m");
318
319 if (draw_separator && arg_output == OUTPUT_FRIENDLY)
320 putchar('\n');
321
322 r = show_group(gr, table);
323 if (r < 0)
324 return r;
325
326 draw_separator = true;
327 }
328
329 }
330
331 if (table) {
332 r = table_print(table, NULL);
333 if (r < 0)
334 return table_log_print_error(r);
335 }
336
337 return ret;
338 }
339
340 static int show_membership(const char *user, const char *group, Table *table) {
341 int r;
342
343 assert(user);
344 assert(group);
345
346 switch (arg_output) {
347
348 case OUTPUT_CLASSIC:
349 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
350 * similar style to the classic output for user/group info */
351
352 printf("%s:%s\n", user, group);
353 break;
354
355 case OUTPUT_JSON: {
356 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
357
358 r = json_build(&v, JSON_BUILD_OBJECT(
359 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user)),
360 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group))));
361 if (r < 0)
362 return log_error_errno(r, "Failed to build JSON object: %m");
363
364 json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
365 break;
366 }
367
368 case OUTPUT_FRIENDLY:
369 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
370 printf("%s: %s\n", group, user);
371 break;
372
373 case OUTPUT_TABLE:
374 assert(table);
375
376 r = table_add_many(
377 table,
378 TABLE_STRING, user,
379 TABLE_STRING, group);
380 if (r < 0)
381 return table_log_add_error(r);
382
383 break;
384
385 default:
386 assert_not_reached("Unexpected output mode");
387 }
388
389 return 0;
390 }
391
392 static int display_memberships(int argc, char *argv[], void *userdata) {
393 _cleanup_(table_unrefp) Table *table = NULL;
394 int ret = 0, r;
395
396 if (arg_output < 0)
397 arg_output = OUTPUT_TABLE;
398
399 if (arg_output == OUTPUT_TABLE) {
400 table = table_new("user", "group");
401 if (!table)
402 return log_oom();
403
404 (void) table_set_sort(table, (size_t) 0, (size_t) 1, (size_t) -1);
405 }
406
407 if (argc > 1) {
408 char **i;
409
410 STRV_FOREACH(i, argv + 1) {
411 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
412
413 if (streq(argv[0], "users-in-group")) {
414 r = membershipdb_by_group(*i, arg_userdb_flags, &iterator);
415 if (r < 0)
416 return log_error_errno(r, "Failed to enumerate users in group: %m");
417 } else if (streq(argv[0], "groups-of-user")) {
418 r = membershipdb_by_user(*i, arg_userdb_flags, &iterator);
419 if (r < 0)
420 return log_error_errno(r, "Failed to enumerate groups of user: %m");
421 } else
422 assert_not_reached("Unexpected verb");
423
424 for (;;) {
425 _cleanup_free_ char *user = NULL, *group = NULL;
426
427 r = membershipdb_iterator_get(iterator, &user, &group);
428 if (r == -ESRCH)
429 break;
430 if (r == -EHOSTDOWN)
431 return log_error_errno(r, "Selected membership database service is not available for this request.");
432 if (r < 0)
433 return log_error_errno(r, "Failed acquire next membership: %m");
434
435 r = show_membership(user, group, table);
436 if (r < 0)
437 return r;
438 }
439 }
440 } else {
441 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
442
443 r = membershipdb_all(arg_userdb_flags, &iterator);
444 if (r < 0)
445 return log_error_errno(r, "Failed to enumerate memberships: %m");
446
447 for (;;) {
448 _cleanup_free_ char *user = NULL, *group = NULL;
449
450 r = membershipdb_iterator_get(iterator, &user, &group);
451 if (r == -ESRCH)
452 break;
453 if (r == -EHOSTDOWN)
454 return log_error_errno(r, "Selected membership database service is not available for this request.");
455 if (r < 0)
456 return log_error_errno(r, "Failed acquire next membership: %m");
457
458 r = show_membership(user, group, table);
459 if (r < 0)
460 return r;
461 }
462 }
463
464 if (table) {
465 r = table_print(table, NULL);
466 if (r < 0)
467 return table_log_print_error(r);
468 }
469
470 return ret;
471 }
472
473 static int display_services(int argc, char *argv[], void *userdata) {
474 _cleanup_(table_unrefp) Table *t = NULL;
475 _cleanup_(closedirp) DIR *d = NULL;
476 struct dirent *de;
477 int r;
478
479 d = opendir("/run/systemd/userdb/");
480 if (!d) {
481 if (errno == ENOENT) {
482 log_info("No services.");
483 return 0;
484 }
485
486 return log_error_errno(errno, "Failed to open /run/systemd/userdb/: %m");
487 }
488
489 t = table_new("service", "listening");
490 if (!t)
491 return log_oom();
492
493 (void) table_set_sort(t, (size_t) 0, (size_t) -1);
494
495 FOREACH_DIRENT(de, d, return -errno) {
496 _cleanup_free_ char *j = NULL, *no = NULL;
497 union sockaddr_union sockaddr;
498 socklen_t sockaddr_len;
499 _cleanup_close_ int fd = -1;
500
501 j = path_join("/run/systemd/userdb/", de->d_name);
502 if (!j)
503 return log_oom();
504
505 r = sockaddr_un_set_path(&sockaddr.un, j);
506 if (r < 0)
507 return log_error_errno(r, "Path %s does not fit in AF_UNIX socket address: %m", j);
508 sockaddr_len = r;
509
510 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
511 if (fd < 0)
512 return log_error_errno(r, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
513
514 if (connect(fd, &sockaddr.un, sockaddr_len) < 0) {
515 no = strjoin("No (", errno_to_name(errno), ")");
516 if (!no)
517 return log_oom();
518 }
519
520 r = table_add_many(t,
521 TABLE_STRING, de->d_name,
522 TABLE_STRING, no ?: "yes",
523 TABLE_SET_COLOR, no ? ansi_highlight_red() : ansi_highlight_green());
524 if (r < 0)
525 return table_log_add_error(r);
526 }
527
528 if (table_get_rows(t) <= 0) {
529 log_info("No services.");
530 return 0;
531 }
532
533 if (arg_output == OUTPUT_JSON)
534 table_print_json(t, NULL, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO);
535 else
536 table_print(t, NULL);
537
538 return 0;
539 }
540
541 static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
542 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
543 int r;
544
545 r = userdb_by_name(argv[1], arg_userdb_flags, &ur);
546 if (r == -ESRCH)
547 return log_error_errno(r, "User %s does not exist.", argv[1]);
548 else if (r == -EHOSTDOWN)
549 return log_error_errno(r, "Selected user database service is not available for this request.");
550 else if (r == -EINVAL)
551 return log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", argv[1]);
552 else if (r < 0)
553 return log_error_errno(r, "Failed to find user %s: %m", argv[1]);
554
555 if (strv_isempty(ur->ssh_authorized_keys))
556 log_debug("User record for %s has no public SSH keys.", argv[1]);
557 else {
558 char **i;
559
560 STRV_FOREACH(i, ur->ssh_authorized_keys)
561 printf("%s\n", *i);
562 }
563
564 if (ur->incomplete) {
565 fflush(stdout);
566 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
567 }
568
569 return EXIT_SUCCESS;
570 }
571
572 static int help(int argc, char *argv[], void *userdata) {
573 _cleanup_free_ char *link = NULL;
574 int r;
575
576 (void) pager_open(arg_pager_flags);
577
578 r = terminal_urlify_man("userdbctl", "1", &link);
579 if (r < 0)
580 return log_oom();
581
582 printf("%s [OPTIONS...] COMMAND ...\n\n"
583 "%sShow user and group information.%s\n"
584 "\nCommands:\n"
585 " user [USER…] Inspect user\n"
586 " group [GROUP…] Inspect group\n"
587 " users-in-group [GROUP…] Show users that are members of specified group(s)\n"
588 " groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
589 " services Show enabled database services\n"
590 "\nOptions:\n"
591 " -h --help Show this help\n"
592 " --version Show package version\n"
593 " --no-pager Do not pipe output into a pager\n"
594 " --no-legend Do not show the headers and footers\n"
595 " --output=MODE Select output mode (classic, friendly, table, json)\n"
596 " -j Equivalent to --output=json\n"
597 " -s --service=SERVICE[:SERVICE…]\n"
598 " Query the specified service\n"
599 " --with-nss=BOOL Control whether to include glibc NSS data\n"
600 " -N Do not synthesize or include glibc NSS data\n"
601 " (Same as --synthesize=no --with-nss=no)\n"
602 " --synthesize=BOOL Synthesize root/nobody user\n"
603 "\nSee the %s for details.\n"
604 , program_invocation_short_name
605 , ansi_highlight(), ansi_normal()
606 , link
607 );
608
609 return 0;
610 }
611
612 static int parse_argv(int argc, char *argv[]) {
613
614 enum {
615 ARG_VERSION = 0x100,
616 ARG_NO_PAGER,
617 ARG_NO_LEGEND,
618 ARG_OUTPUT,
619 ARG_WITH_NSS,
620 ARG_SYNTHESIZE,
621 };
622
623 static const struct option options[] = {
624 { "help", no_argument, NULL, 'h' },
625 { "version", no_argument, NULL, ARG_VERSION },
626 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
627 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
628 { "output", required_argument, NULL, ARG_OUTPUT },
629 { "service", required_argument, NULL, 's' },
630 { "with-nss", required_argument, NULL, ARG_WITH_NSS },
631 { "synthesize", required_argument, NULL, ARG_SYNTHESIZE },
632 {}
633 };
634
635 const char *e;
636 int r;
637
638 assert(argc >= 0);
639 assert(argv);
640
641 /* We are going to update this environment variable with our own, hence let's first read what is already set */
642 e = getenv("SYSTEMD_ONLY_USERDB");
643 if (e) {
644 char **l;
645
646 l = strv_split(e, ":");
647 if (!l)
648 return log_oom();
649
650 strv_free(arg_services);
651 arg_services = l;
652 }
653
654 for (;;) {
655 int c;
656
657 c = getopt_long(argc, argv, "hjs:N", options, NULL);
658 if (c < 0)
659 break;
660
661 switch (c) {
662
663 case 'h':
664 return help(0, NULL, NULL);
665
666 case ARG_VERSION:
667 return version();
668
669 case ARG_NO_PAGER:
670 arg_pager_flags |= PAGER_DISABLE;
671 break;
672
673 case ARG_NO_LEGEND:
674 arg_legend = false;
675 break;
676
677 case ARG_OUTPUT:
678 if (streq(optarg, "classic"))
679 arg_output = OUTPUT_CLASSIC;
680 else if (streq(optarg, "friendly"))
681 arg_output = OUTPUT_FRIENDLY;
682 else if (streq(optarg, "json"))
683 arg_output = OUTPUT_JSON;
684 else if (streq(optarg, "table"))
685 arg_output = OUTPUT_TABLE;
686 else if (streq(optarg, "help")) {
687 puts("classic\n"
688 "friendly\n"
689 "json\n"
690 "table");
691 return 0;
692 } else
693 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg);
694
695 break;
696
697 case 'j':
698 arg_output = OUTPUT_JSON;
699 break;
700
701 case 's':
702 if (isempty(optarg))
703 arg_services = strv_free(arg_services);
704 else {
705 _cleanup_strv_free_ char **l = NULL;
706
707 l = strv_split(optarg, ":");
708 if (!l)
709 return log_oom();
710
711 r = strv_extend_strv(&arg_services, l, true);
712 if (r < 0)
713 return log_oom();
714 }
715
716 break;
717
718 case 'N':
719 arg_userdb_flags |= USERDB_AVOID_NSS|USERDB_DONT_SYNTHESIZE;
720 break;
721
722 case ARG_WITH_NSS:
723 r = parse_boolean(optarg);
724 if (r < 0)
725 return log_error_errno(r, "Failed to parse --with-nss= parameter: %s", optarg);
726
727 SET_FLAG(arg_userdb_flags, USERDB_AVOID_NSS, !r);
728 break;
729
730 case ARG_SYNTHESIZE:
731 r = parse_boolean(optarg);
732 if (r < 0)
733 return log_error_errno(r, "Failed to parse --synthesize= parameter: %s", optarg);
734
735 SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE, !r);
736 break;
737
738 case '?':
739 return -EINVAL;
740
741 default:
742 assert_not_reached("Unhandled option");
743 }
744 }
745
746 return 1;
747 }
748
749 static int run(int argc, char *argv[]) {
750 static const Verb verbs[] = {
751 { "help", VERB_ANY, VERB_ANY, 0, help },
752 { "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, display_user },
753 { "group", VERB_ANY, VERB_ANY, 0, display_group },
754 { "users-in-group", VERB_ANY, VERB_ANY, 0, display_memberships },
755 { "groups-of-user", VERB_ANY, VERB_ANY, 0, display_memberships },
756 { "services", VERB_ANY, 1, 0, display_services },
757
758 /* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
759 * user-facing verb and thus should not appear in man pages or --help texts. */
760 { "ssh-authorized-keys", 2, 2, 0, ssh_authorized_keys },
761 {}
762 };
763
764 int r;
765
766 log_setup_cli();
767
768 r = parse_argv(argc, argv);
769 if (r <= 0)
770 return r;
771
772 if (arg_services) {
773 _cleanup_free_ char *e = NULL;
774
775 e = strv_join(arg_services, ":");
776 if (!e)
777 return log_oom();
778
779 if (setenv("SYSTEMD_ONLY_USERDB", e, true) < 0)
780 return log_error_errno(r, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
781
782 log_info("Enabled services: %s", e);
783 } else
784 assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0);
785
786 return dispatch_verb(argc, argv, verbs, NULL);
787 }
788
789 DEFINE_MAIN_FUNCTION(run);