Merge pull request #21265 from poettering/userdb-fixes

userdb: various userdb fixes and minor feature additions
This commit is contained in:
Yu Watanabe 2021-11-09 08:14:52 +09:00 committed by GitHub
commit aac977c892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 183 additions and 82 deletions

View file

@ -71,6 +71,16 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--json=</option><replaceable>FORMAT</replaceable></term>
<listitem><para>Selects JSON out mode (like <option>--output=json</option>) and selects the precise
display mode. Takes one of <literal>pretty</literal> or <literal>short</literal>. If
<literal>pretty</literal> human-friendly whitespace and newlines are inserted in the output to make
the JSON data more readable. If <literal>short</literal> all superfluous whitespace is
suppressed.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--service=</option><replaceable>SERVICE</replaceable><optional>:<replaceable>SERVICE…</replaceable></optional></term>
<term><option>-s</option> <replaceable>SERVICE</replaceable>:<replaceable>SERVICE…</replaceable></term>
@ -128,6 +138,14 @@
off.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--multiplexer=</option><replaceable>BOOL</replaceable></term>
<listitem><para>Controls whether to do lookups via the multiplexer service (if specified as true, the
default) or do lookups in the client (if specified as false). Using the multiplexer service is
typically preferable, since it runs in a locked down sandbox.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="help" />

View file

@ -33,6 +33,7 @@ static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static char** arg_services = NULL;
static UserDBFlags arg_userdb_flags = 0;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep);
@ -58,7 +59,7 @@ static int show_user(UserRecord *ur, Table *table) {
break;
case OUTPUT_JSON:
json_variant_dump(ur->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
json_variant_dump(ur->json, arg_json_format_flags, NULL, 0);
break;
case OUTPUT_FRIENDLY:
@ -152,35 +153,49 @@ static int display_user(int argc, char *argv[], void *userdata) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = userdb_all(arg_userdb_flags, &iterator);
if (r < 0)
if (r == -ENOLINK) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
else if (r == -ESRCH) /* ESRCH → Couldn't find any suitable entry, but we checked all sources */
log_debug_errno(r, "No entries found.");
else if (r < 0)
return log_error_errno(r, "Failed to enumerate users: %m");
else {
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
for (;;) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = userdb_iterator_get(iterator, &ur);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected user database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next user: %m");
r = userdb_iterator_get(iterator, &ur);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected user database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next user: %m");
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
r = show_user(ur, table);
if (r < 0)
return r;
r = show_user(ur, table);
if (r < 0)
return r;
draw_separator = true;
draw_separator = true;
}
}
}
if (table) {
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
if (table_get_rows(table) > 1) {
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
if (r < 0)
return table_log_print_error(r);
}
if (arg_legend) {
if (table_get_rows(table) > 1)
printf("\n%zu users listed.\n", table_get_rows(table) - 1);
else
printf("No users.\n");
}
}
return ret;
@ -211,7 +226,7 @@ static int show_group(GroupRecord *gr, Table *table) {
}
case OUTPUT_JSON:
json_variant_dump(gr->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
json_variant_dump(gr->json, arg_json_format_flags, NULL, 0);
break;
case OUTPUT_FRIENDLY:
@ -303,36 +318,49 @@ static int display_group(int argc, char *argv[], void *userdata) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = groupdb_all(arg_userdb_flags, &iterator);
if (r < 0)
if (r == -ENOLINK)
log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
else if (r == -ESRCH)
log_debug_errno(r, "No entries found.");
else if (r < 0)
return log_error_errno(r, "Failed to enumerate groups: %m");
else {
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
for (;;) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
r = groupdb_iterator_get(iterator, &gr);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected group database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next group: %m");
r = groupdb_iterator_get(iterator, &gr);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected group database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next group: %m");
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
r = show_group(gr, table);
if (r < 0)
return r;
r = show_group(gr, table);
if (r < 0)
return r;
draw_separator = true;
draw_separator = true;
}
}
}
if (table) {
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
if (table_get_rows(table) > 1) {
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
if (r < 0)
return table_log_print_error(r);
}
if (arg_legend) {
if (table_get_rows(table) > 1)
printf("\n%zu groups listed.\n", table_get_rows(table) - 1);
else
printf("No groups.\n");
}
}
return ret;
@ -362,7 +390,7 @@ static int show_membership(const char *user, const char *group, Table *table) {
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
json_variant_dump(v, arg_json_format_flags, NULL, NULL);
break;
}
@ -442,30 +470,44 @@ static int display_memberships(int argc, char *argv[], void *userdata) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = membershipdb_all(arg_userdb_flags, &iterator);
if (r < 0)
if (r == -ENOLINK)
log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
else if (r == -ESRCH)
log_debug_errno(r, "No entries found.");
else if (r < 0)
return log_error_errno(r, "Failed to enumerate memberships: %m");
else {
for (;;) {
_cleanup_free_ char *user = NULL, *group = NULL;
for (;;) {
_cleanup_free_ char *user = NULL, *group = NULL;
r = membershipdb_iterator_get(iterator, &user, &group);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected membership database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next membership: %m");
r = membershipdb_iterator_get(iterator, &user, &group);
if (r == -ESRCH)
break;
if (r == -EHOSTDOWN)
return log_error_errno(r, "Selected membership database service is not available for this request.");
if (r < 0)
return log_error_errno(r, "Failed acquire next membership: %m");
r = show_membership(user, group, table);
if (r < 0)
return r;
r = show_membership(user, group, table);
if (r < 0)
return r;
}
}
}
if (table) {
r = table_print(table, NULL);
if (r < 0)
return table_log_print_error(r);
if (table_get_rows(table) > 1) {
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
if (r < 0)
return table_log_print_error(r);
}
if (arg_legend) {
if (table_get_rows(table) > 1)
printf("\n%zu memberships listed.\n", table_get_rows(table) - 1);
else
printf("No memberships.\n");
}
}
return ret;
@ -526,15 +568,18 @@ static int display_services(int argc, char *argv[], void *userdata) {
return table_log_add_error(r);
}
if (table_get_rows(t) <= 0) {
log_info("No services.");
return 0;
if (table_get_rows(t) > 1) {
r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
if (r < 0)
return table_log_print_error(r);
}
if (arg_output == OUTPUT_JSON)
table_print_json(t, NULL, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO);
else
table_print(t, NULL);
if (arg_legend) {
if (table_get_rows(t) > 1)
printf("\n%zu services listed.\n", table_get_rows(t) - 1);
else
printf("No services.\n");
}
return 0;
}
@ -603,6 +648,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --synthesize=BOOL Synthesize root/nobody user\n"
" --with-dropin=BOOL Control whether to include drop-in records\n"
" --with-varlink=BOOL Control whether to talk to services at all\n"
" --multiplexer=BOOL Control whether to use the multiplexer\n"
" --json=pretty|short JSON output mode\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@ -623,19 +670,23 @@ static int parse_argv(int argc, char *argv[]) {
ARG_WITH_DROPIN,
ARG_WITH_VARLINK,
ARG_SYNTHESIZE,
ARG_MULTIPLEXER,
ARG_JSON,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "output", required_argument, NULL, ARG_OUTPUT },
{ "service", required_argument, NULL, 's' },
{ "with-nss", required_argument, NULL, ARG_WITH_NSS },
{ "with-dropin", required_argument, NULL, ARG_WITH_DROPIN },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "output", required_argument, NULL, ARG_OUTPUT },
{ "service", required_argument, NULL, 's' },
{ "with-nss", required_argument, NULL, ARG_WITH_NSS },
{ "with-dropin", required_argument, NULL, ARG_WITH_DROPIN },
{ "with-varlink", required_argument, NULL, ARG_WITH_VARLINK },
{ "synthesize", required_argument, NULL, ARG_SYNTHESIZE },
{ "synthesize", required_argument, NULL, ARG_SYNTHESIZE },
{ "multiplexer", required_argument, NULL, ARG_MULTIPLEXER },
{ "json", required_argument, NULL, ARG_JSON },
{}
};
@ -682,7 +733,9 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_OUTPUT:
if (streq(optarg, "classic"))
if (isempty(optarg))
arg_output = _OUTPUT_INVALID;
else if (streq(optarg, "classic"))
arg_output = OUTPUT_CLASSIC;
else if (streq(optarg, "friendly"))
arg_output = OUTPUT_FRIENDLY;
@ -699,9 +752,19 @@ static int parse_argv(int argc, char *argv[]) {
} else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg);
arg_json_format_flags = arg_output == OUTPUT_JSON ? JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO : JSON_FORMAT_OFF;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
return r;
arg_output = FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) ? _OUTPUT_INVALID : OUTPUT_JSON;
break;
case 'j':
arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO;
arg_output = OUTPUT_JSON;
break;
@ -758,6 +821,14 @@ static int parse_argv(int argc, char *argv[]) {
SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE, !r);
break;
case ARG_MULTIPLEXER:
r = parse_boolean_argument("--multiplexer=", optarg, NULL);
if (r < 0)
return r;
SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r);
break;
case '?':
return -EINVAL;

View file

@ -114,7 +114,6 @@ static int build_user_json(Varlink *link, UserRecord *ur, JsonVariant **ret) {
static int userdb_flags_from_service(Varlink *link, const char *service, UserDBFlags *ret) {
assert(link);
assert(service);
assert(ret);
if (streq_ptr(service, "io.systemd.NameServiceSwitch"))
@ -153,7 +152,8 @@ static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, Var
return r;
r = userdb_flags_from_service(link, p.service, &userdb_flags);
if (r < 0)
if (r != 0) /* return value of < 0 means error (as usual); > 0 means 'already processed and replied,
* we are done'; == 0 means 'not processed, caller should process now' */
return r;
if (uid_is_valid(p.uid))
@ -165,6 +165,14 @@ static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, Var
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
r = userdb_all(userdb_flags, &iterator);
if (IN_SET(r, -ESRCH, -ENOLINK))
/* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn
* backend) this might make userdb_all return ENOLINK (which indicates that varlink
* was off and no other suitable source or entries were found). Let's hide this
* implementation detail and always return NoRecordFound in this case, since from a
* client's perspective it's irrelevant if there was no entry at all or just not on
* the service that the query was limited to. */
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r < 0)
return r;
@ -280,7 +288,7 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va
return r;
r = userdb_flags_from_service(link, p.service, &userdb_flags);
if (r < 0)
if (r != 0)
return r;
if (gid_is_valid(p.gid))
@ -292,6 +300,8 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
r = groupdb_all(userdb_flags, &iterator);
if (IN_SET(r, -ESRCH, -ENOLINK))
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r < 0)
return r;
@ -361,7 +371,7 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var
return r;
r = userdb_flags_from_service(link, p.service, &userdb_flags);
if (r < 0)
if (r != 0)
return r;
if (p.group_name)
@ -370,6 +380,8 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var
r = membershipdb_by_user(p.user_name, userdb_flags, &iterator);
else
r = membershipdb_all(userdb_flags, &iterator);
if (IN_SET(r, -ESRCH, -ENOLINK))
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
if (r < 0)
return r;