On Sat, 4 May 2024, openssh@xxxxxxxx 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 . _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev