On Sat, 4 May 2024, openssh at tr.id.au wrote:
> Hey there,
>
> I often want different behavior in my ssh client depending on
> whether I'm logging into an interactive session or running
> a remote non-interactive command. We can see at, say,
> https://unix.stackexchange.com/a/499562/305714 that this isn't a
> unique wish, and existing solutions are kind of baroque. Typical
> reasons to do this are to immediately go into a screen or tmux
> session; for myself, I often want to relaunch bash as "bash -lo
> vi" on boxes where I don't have bashrc control. Basically, we want
> RemoteCommand to be turned on for interactive sessions, but ignore
> it when we've already specified a command as part of the client
> invocation.
>
> I wondered if there would be support for, or interest in, adding a
> new condition called "interactive" (or similar) to the Match
keyword?
> Although my use case is for client-side, I guess it may also make
> sense in sshd_config. I can imagine cases where sysadmins would want
> to present different behavior depending on whether a client is coming
> in interactively or running a command.
>
> Alternatively, could there be a new option which specifies how to
> resolve conflicts between command-line commands and RemoteCommand
> directives? Eg something like "RemoteCommandOptional yes" which
can be
> paired with RemoteCommand. This would allow a default RemoteCommand
> which can be overridden by commands passed on cli.
>
> Or have I overlooked an already-existing simpler/better way of
> toggling different configurations for interactive vs non-interactive
> sessions exist, when serverside control is not an option? Sorry if
> this was already discussed before, nothing from this mailing list
> turned up in a web search about the topic.
Would something like this help?
Match sessiontype shell
User foo
Match remotecommand "none"
User foo2
Match sessiontype exec remotecommand "*/rsync*"
User bar
Match sessiontype subsystem remotecommand "sftp"
User baz
diff --git a/readconf.c b/readconf.c
index 3a64a0441..dff6a9df6 100644
--- a/readconf.c
+++ b/readconf.c
@@ -70,6 +70,7 @@
#include "uidswap.h"
#include "myproposal.h"
#include "digest.h"
+#include "sshbuf.h"
/* Format of the configuration file:
@@ -133,11 +134,11 @@
*/
static int read_config_file_depth(const char *filename, struct passwd *pw,
- const char *host, const char *original_host, Options *options,
- int flags, int *activep, int *want_final_pass, int depth);
+ const char *host, const char *original_host, struct sshbuf *remote_command,
+ Options *options, int flags, int *activep, int *want_final_pass, int
depth);
static int process_config_line_depth(Options *options, struct passwd *pw,
- const char *host, const char *original_host, char *line,
- const char *filename, int linenum, int *activep, int flags,
+ const char *host, const char *original_host, struct sshbuf *remote_command,
+ char *line, const char *filename, int linenum, int *activep, int flags,
int *want_final_pass, int depth);
/* Keyword tokens. */
@@ -650,8 +651,9 @@ check_match_ifaddrs(const char *addrlist)
*/
static int
match_cfg_line(Options *options, char **condition, struct passwd *pw,
- const char *host_arg, const char *original_host, int final_pass,
- int *want_final_pass, const char *filename, int linenum)
+ const char *host_arg, const char *original_host,
+ struct sshbuf *rcommand, int final_pass, int *want_final_pass,
+ const char *filename, int linenum)
{
char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria;
const char *ruser;
@@ -764,6 +766,30 @@ match_cfg_line(Options *options, char **condition, struct
passwd *pw,
r = match_pattern_list(criteria, arg, 0) == 1;
if (r == (negate ? 1 : 0))
this_result = result = 0;
+ } else if (strcasecmp(attrib, "sessiontype") == 0) {
+ if (options->session_type == SESSION_TYPE_SUBSYSTEM)
+ criteria = xstrdup("subsystem");
+ else if (options->session_type == SESSION_TYPE_NONE)
+ criteria = xstrdup("none");
+ else if (rcommand != NULL && sshbuf_len(rcommand) > 0)
+ criteria = xstrdup("exec");
+ else
+ criteria = xstrdup("shell");
+ r = match_pattern_list(criteria, arg, 0) == 1;
+ if (r == (negate ? 1 : 0))
+ this_result = result = 0;
+ } else if (strcasecmp(attrib, "remotecommand") == 0) {
+ if (rcommand != NULL && sshbuf_len(rcommand) > 0) {
+ if ((criteria + sshbuf_dup_string(rcommand)) == NULL)
+ fatal_f("dup command failed");
+ } else if (options->remote_command != NULL)
+ criteria = xstrdup(options->remote_command);
+ else
+ criteria = xstrdup("none");
+ r = match_pattern_list(criteria, arg, 0) == 1;
+ if (r == (negate ? 1 : 0))
+ this_result = result = 0;
} else if (strcasecmp(attrib, "exec") == 0) {
char *conn_hash_hex, *keyalias, *jmphost;
@@ -1031,18 +1057,19 @@ parse_multistate_value(const char *arg, const char
*filename, int linenum,
*/
int
process_config_line(Options *options, struct passwd *pw, const char *host,
- const char *original_host, char *line, const char *filename,
- int linenum, int *activep, int flags)
+ const char *original_host, struct sshbuf *remote_command,
+ char *line, const char *filename, int linenum, int *activep, int flags)
{
return process_config_line_depth(options, pw, host, original_host,
- line, filename, linenum, activep, flags, NULL, 0);
+ remote_command, line, filename, linenum, activep, flags, NULL, 0);
}
#define WHITESPACE " \t\r\n"
static int
process_config_line_depth(Options *options, struct passwd *pw, const char
*host,
- const char *original_host, char *line, const char *filename,
- int linenum, int *activep, int flags, int *want_final_pass, int depth)
+ const char *original_host, struct sshbuf *remote_command, char *line,
+ const char *filename, int linenum, int *activep, int flags,
+ int *want_final_pass, int depth)
{
char *str, **charptr, *endofnumber, *keyword, *arg, *arg2, *p;
char **cpptr, ***cppptr, fwdarg[256];
@@ -1779,7 +1806,7 @@ parse_pubkey_algos:
goto out;
}
value = match_cfg_line(options, &str, pw, host, original_host,
- flags & SSHCONF_FINAL, want_final_pass,
+ remote_command, flags & SSHCONF_FINAL, want_final_pass,
filename, linenum);
if (value < 0) {
error("%.200s line %d: Bad Match condition", filename,
@@ -2028,8 +2055,8 @@ parse_pubkey_algos:
gl.gl_pathv[i], depth,
oactive ? "" : " (parse only)");
r = read_config_file_depth(gl.gl_pathv[i],
- pw, host, original_host, options,
- flags | SSHCONF_CHECKPERM |
+ pw, host, original_host, remote_command,
+ options, flags | SSHCONF_CHECKPERM |
(oactive ? 0 : SSHCONF_NEVERMATCH),
activep, want_final_pass, depth + 1);
if (r != 1 && errno != ENOENT) {
@@ -2429,20 +2456,20 @@ parse_pubkey_algos:
*/
int
read_config_file(const char *filename, struct passwd *pw, const char *host,
- const char *original_host, Options *options, int flags,
- int *want_final_pass)
+ const char *original_host, struct sshbuf *remote_command,
+ Options *options, int flags, int *want_final_pass)
{
int active = 1;
return read_config_file_depth(filename, pw, host, original_host,
- options, flags, &active, want_final_pass, 0);
+ remote_command, options, flags, &active, want_final_pass, 0);
}
#define READCONF_MAX_DEPTH 16
static int
read_config_file_depth(const char *filename, struct passwd *pw,
- const char *host, const char *original_host, Options *options,
- int flags, int *activep, int *want_final_pass, int depth)
+ const char *host, const char *original_host, struct sshbuf *remote_command,
+ Options *options, int flags, int *activep, int *want_final_pass, int depth)
{
FILE *f;
char *line = NULL;
@@ -2482,8 +2509,8 @@ read_config_file_depth(const char *filename, struct passwd
*pw,
* line numbers later for error messages.
*/
if (process_config_line_depth(options, pw, host, original_host,
- line, filename, linenum, activep, flags, want_final_pass,
- depth) != 0)
+ remote_command, line, filename, linenum, activep, flags,
+ want_final_pass, depth) != 0)
bad_options++;
}
free(line);
diff --git a/readconf.h b/readconf.h
index 9447d5d6e..3da495e38 100644
--- a/readconf.h
+++ b/readconf.h
@@ -231,6 +231,8 @@ typedef struct {
#define SSH_KEYSTROKE_CHAFF_MIN_MS 1024
#define SSH_KEYSTROKE_CHAFF_RNG_MS 2048
+struct sshbuf;
+
const char *kex_default_pk_alg(void);
char *ssh_connection_hash(const char *thishost, const char *host,
const char *portstr, const char *user, const char *jump_host);
@@ -239,9 +241,9 @@ int fill_default_options(Options *);
void fill_default_options_for_canonicalization(Options *);
void free_options(Options *o);
int process_config_line(Options *, struct passwd *, const char *,
- const char *, char *, const char *, int, int *, int);
+ const char *, struct sshbuf *, char *, const char *, int, int *, int);
int read_config_file(const char *, struct passwd *, const char *,
- const char *, Options *, int, int *);
+ const char *, struct sshbuf *, Options *, int, int *);
int parse_forward(struct Forward *, const char *, int, int);
int parse_jump(const char *, Options *, int);
int parse_ssh_uri(const char *, char **, char **, int *);
diff --git a/ssh-keysign.c b/ssh-keysign.c
index 968344e79..4913b540d 100644
--- a/ssh-keysign.c
+++ b/ssh-keysign.c
@@ -222,7 +222,7 @@ main(int argc, char **argv)
/* verify that ssh-keysign is enabled by the admin */
initialize_options(&options);
- (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "",
+ (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "",
NULL,
&options, 0, NULL);
(void)fill_default_options(&options);
if (options.enable_ssh_keysign != 1)
diff --git a/ssh.c b/ssh.c
index 0019281f4..ecaff3844 100644
--- a/ssh.c
+++ b/ssh.c
@@ -566,7 +566,8 @@ process_config_files(const char *host_name, struct passwd
*pw, int final_pass,
if (config != NULL) {
if (strcasecmp(config, "none") != 0 &&
- !read_config_file(config, pw, host, host_name, &options,
+ !read_config_file(config, pw, host, host_name,
+ command, &options,
SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0),
want_final_pass))
fatal("Can't open user config file %.100s: "
@@ -576,12 +577,13 @@ process_config_files(const char *host_name, struct passwd
*pw, int final_pass,
_PATH_SSH_USER_CONFFILE);
if (r > 0 && (size_t)r < sizeof(buf))
(void)read_config_file(buf, pw, host, host_name,
- &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
+ command, &options,
+ SSHCONF_CHECKPERM | SSHCONF_USERCONF |
(final_pass ? SSHCONF_FINAL : 0), want_final_pass);
/* Read systemwide configuration file after user config. */
(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw,
- host, host_name, &options,
+ host, host_name, command, &options,
final_pass ? SSHCONF_FINAL : 0, want_final_pass);
}
}
@@ -1074,7 +1076,7 @@ main(int ac, char **av)
case 'o':
line = xstrdup(optarg);
if (process_config_line(&options, pw,
- host ? host : "", host ? host : "", line,
+ host ? host : "", host ? host : "", NULL, line,
"command-line", 0, NULL, SSHCONF_USERCONF) != 0)
exit(255);
free(line);
diff --git a/ssh_config.5 b/ssh_config.5
index 2931d807e..0500bc049 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -145,6 +145,8 @@ The available criteria keywords are:
.Cm host ,
.Cm originalhost ,
.Cm tagged ,
+.Cm sessiontype ,
+.Cm remotecommand ,
.Cm user ,
and
.Cm localuser .
@@ -212,6 +214,7 @@ The other keywords' criteria must be single entries or
comma-separated
lists and may use the wildcard and negation operators described in the
.Sx PATTERNS
section.
+.Pp
The criteria for the
.Cm host
keyword are matched against the target hostname, after any substitution
@@ -223,6 +226,7 @@ options.
The
.Cm originalhost
keyword matches against the hostname as it was specified on the command-line.
+.Pp
The
.Cm tagged
keyword matches a tag name specified by a prior
@@ -232,6 +236,7 @@ directive or on the
command-line using the
.Fl P
flag.
+.Pp
The
.Cm user
keyword matches against the target username on the remote host.
@@ -242,6 +247,31 @@ keyword matches against the name of the local user running
(this keyword may be useful in system-wide
.Nm
files).
+.Pp
+The
+.Cm sessiontype
+keyword matches the requested remote session type as a pattern-list, that may
+contain any of
+.Cm shell ,
+.Cm exec
+(for command execution),
+.Cm subsystem
+(e.g. for
+.Xr sftp 1
+sessions),
+or
+.Xr none
+for empty sessions, such as when
+.Xr ssh 1
+is started with the
+.Fl N
+flag.
+The
+.Cm remotecommand
+keyword matches against the remote command as a pattern-list.
+For example
+.Dq shutdown,reboot,halt*
+would match any of these commands.
.It Cm AddKeysToAgent
Specifies whether keys should be automatically added to a running
.Xr ssh-agent 1 .