userdbctl: actually implement option parsing stop after --chain

The basic idea is that --chain should stop option parsing. But
previously this didn't work, so --chain could be specified anywhere
in the command line. To maintain with compatibility with that,
allow --chain to be specified anywhere until the first positional
arg or option in the command string. This allows options to be passed
in the expected fashion:
  userdbctl --chain ssh-authorized-keys user cmd --opt1 --opt2
  userdbctl --chain ssh-authorized-keys user -- cmd --opt1 --opt2
but also allows the invocations which worked previously:
  userdbctl ssh-authorized-keys user --chain cmd
  userdbctl ssh-authorized-keys user cmd --chain

Fixes 8072a7e6a9.

The error messages are extended a bit. "binary path" is misleading:
we support all kinds of executables, not only compiled programs.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-05-08 17:15:57 +02:00
parent cb8359877e
commit b2c6cc6a8e
2 changed files with 37 additions and 6 deletions
+28 -6
View File
@@ -1148,11 +1148,13 @@ static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, voi
/* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */
if (!path_is_absolute(argv[2]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument.");
"Chain invocation of ssh-authorized-keys commands requires an absolute program path (got '%s').",
argv[2]);
if (!path_is_normalized(argv[2]))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument.");
"Chain invocation of ssh-authorized-keys commands requires a normalized program path (got '%s').",
argv[2]);
chain_invocation = argv + 2;
} else {
@@ -1628,9 +1630,10 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
arg_services = l;
}
OptionParser opts = { argc, argv };
OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS };
_cleanup_strv_free_ char **args = NULL;
FOREACH_OPTION_OR_RETURN(c, &opts)
FOREACH_OPTION_OR_RETURN(c, &opts) {
switch (c) {
OPTION_COMMON_HELP:
@@ -1733,6 +1736,12 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
arg_chain = true;
break;
OPTION_POSITIONAL:
r = strv_extend(&args, opts.arg);
if (r < 0)
return log_oom();
break;
OPTION_LONG("uid-min", "ID", "Filter by minimum UID/GID (default 0)"):
r = parse_uid(opts.arg, &arg_uid_min);
if (r < 0)
@@ -1811,6 +1820,15 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
}
}
/* When --chain was seen, stop parsing switches after the second positional argument:
* [OPTS0…, VERB, OPTS1…, USERNAME, OPTS2…, COMMAND, OPTS3…]
* We shall parse OPTS0, OPTS1, OPTS2, but OPTS3 are for COMMAND.
* --chain can be anywhere in OPTS0, OPTS1, OPTS2, or first in OPTS3.
*/
if (arg_chain && strv_length(args) >= 3)
opts.state = OPTION_PARSER_DONE;
}
if (arg_uid_min > arg_uid_max)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.",
@@ -1823,12 +1841,16 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
if (arg_from_file)
arg_boundaries = false;
*remaining_args = option_parser_get_args(&opts);
/* We gathered some positional args in 'args' ourselves. Append the remaining ones. */
if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0)
return log_oom();
*remaining_args = TAKE_PTR(args);
return 1;
}
static int run(int argc, char *argv[]) {
char **args = NULL;
_cleanup_strv_free_ char **args = NULL;
int r;
log_setup();
+9
View File
@@ -586,6 +586,15 @@ EOF
(! userdbctl ssh-authorized-keys dropin-user --chain '')
(! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /usr/bin/false)
# Check that invocations with --chain work as expected
userdbctl ssh-authorized-keys --chain dropin-user /bin/echo --asdf | grep -e --asdf
userdbctl ssh-authorized-keys dropin-user --chain /bin/echo --asdf | grep -e --asdf
userdbctl ssh-authorized-keys dropin-user /bin/echo --chain --asdf | grep -e --asdf
userdbctl ssh-authorized-keys --chain dropin-user -- /bin/echo --asdf | grep -e --asdf
userdbctl ssh-authorized-keys --chain -- dropin-user /bin/echo --asdf | grep -e --asdf
userdbctl --chain -- ssh-authorized-keys dropin-user /bin/echo --asdf | grep -e --asdf
(! userdbctl --chain -- ssh-authorized-keys dropin-user -- /bin/echo --asdf)
(! userdbctl '')
for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do
(! userdbctl "--$opt=''")