diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2013-10-14 22:22:06 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2013-10-14 22:22:06 +0000 |
commit | f30aac5cc0dc877321988e1871b7742b03bb0f68 (patch) | |
tree | 80a1283f72dcd91746f1083c14e2c8f28e303ad3 /usr.bin | |
parent | 9c54ec9472db69f4d2b95fa9f7e6f645f08fbb5f (diff) |
add a "Match" keyword to ssh_config that allows matching on hostname,
user and result of arbitrary commands. "nice work" markus@
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/ssh/readconf.c | 227 | ||||
-rw-r--r-- | usr.bin/ssh/readconf.h | 12 | ||||
-rw-r--r-- | usr.bin/ssh/ssh-keysign.c | 4 | ||||
-rw-r--r-- | usr.bin/ssh/ssh.c | 23 | ||||
-rw-r--r-- | usr.bin/ssh/ssh_config.5 | 52 |
5 files changed, 283 insertions, 35 deletions
diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c index f4f78a74689..5b12d232606 100644 --- a/usr.bin/ssh/readconf.c +++ b/usr.bin/ssh/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.205 2013/08/20 00:11:37 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.206 2013/10/14 22:22:02 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -15,6 +15,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> +#include <sys/wait.h> #include <netinet/in.h> #include <netinet/in_systm.h> @@ -22,7 +23,10 @@ #include <ctype.h> #include <errno.h> +#include <fcntl.h> #include <netdb.h> +#include <paths.h> +#include <pwd.h> #include <signal.h> #include <stdio.h> #include <string.h> @@ -42,6 +46,7 @@ #include "buffer.h" #include "kex.h" #include "mac.h" +#include "uidswap.h" /* Format of the configuration file: @@ -110,12 +115,13 @@ typedef enum { oBadOption, + oHost, oMatch, oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, oGatewayPorts, oExitOnForwardFailure, oPasswordAuthentication, oRSAAuthentication, oChallengeResponseAuthentication, oXAuthLocation, oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, - oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, + oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, @@ -189,6 +195,7 @@ static struct { { "localforward", oLocalForward }, { "user", oUser }, { "host", oHost }, + { "match", oMatch }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, { "globalknownhostsfile2", oDeprecated }, @@ -343,10 +350,188 @@ add_identity_file(Options *options, const char *dir, const char *filename, options->identity_files[options->num_identity_files++] = path; } +int +default_ssh_port(void) +{ + static int port; + struct servent *sp; + + if (port == 0) { + sp = getservbyname(SSH_SERVICE_NAME, "tcp"); + port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; + } + return port; +} + /* - * Returns the number of the token pointed to by cp or oBadOption. + * Execute a command in a shell. + * Return its exit status or -1 on abnormal exit. */ +static int +execute_in_shell(const char *cmd) +{ + char *shell, *command_string; + pid_t pid; + int devnull, status; + extern uid_t original_real_uid; + if ((shell = getenv("SHELL")) == NULL) + shell = _PATH_BSHELL; + + /* + * Use "exec" to avoid "sh -c" processes on some platforms + * (e.g. Solaris) + */ + xasprintf(&command_string, "exec %s", cmd); + + /* Need this to redirect subprocess stdin/out */ + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) + fatal("open(/dev/null): %s", strerror(errno)); + + debug("Executing command: '%.500s'", cmd); + + /* Fork and execute the command. */ + if ((pid = fork()) == 0) { + char *argv[4]; + + /* Child. Permanently give up superuser privileges. */ + permanently_drop_suid(original_real_uid); + + /* Redirect child stdin and stdout. Leave stderr */ + if (dup2(devnull, STDIN_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (dup2(devnull, STDOUT_FILENO) == -1) + fatal("dup2: %s", strerror(errno)); + if (devnull > STDERR_FILENO) + close(devnull); + closefrom(STDERR_FILENO + 1); + + argv[0] = shell; + argv[1] = "-c"; + argv[2] = command_string; + argv[3] = NULL; + + execv(argv[0], argv); + error("Unable to execute '%.100s': %s", cmd, strerror(errno)); + /* Die with signal to make this error apparent to parent. */ + signal(SIGTERM, SIG_DFL); + kill(getpid(), SIGTERM); + _exit(1); + } + /* Parent. */ + if (pid < 0) + fatal("%s: fork: %.100s", __func__, strerror(errno)); + + close(devnull); + free(command_string); + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR && errno != EAGAIN) + fatal("%s: waitpid: %s", __func__, strerror(errno)); + } + if (!WIFEXITED(status)) { + error("command '%.100s' exited abnormally", cmd); + return -1; + } + debug3("command returned status %d", WEXITSTATUS(status)); + return WEXITSTATUS(status); +} + +/* + * Parse and execute a Match directive. + */ +static int +match_cfg_line(Options *options, char **condition, struct passwd *pw, + const char *host_arg, const char *filename, int linenum) +{ + char *arg, *attrib, *cmd, *cp = *condition; + const char *ruser, *host; + int r, port, result = 1; + size_t len; + char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; + + /* + * Configuration is likely to be incomplete at this point so we + * must be prepared to use default values. + */ + port = options->port <= 0 ? default_ssh_port() : options->port; + ruser = options->user == NULL ? pw->pw_name : options->user; + host = options->hostname == NULL ? host_arg : options->hostname; + + debug3("checking match for '%s' host %s", cp, host); + while ((attrib = strdelim(&cp)) && *attrib != '\0') { + if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { + error("Missing Match criteria for %s", attrib); + return -1; + } + len = strlen(arg); + if (strcasecmp(attrib, "host") == 0) { + if (match_hostname(host, arg, len) != 1) + result = 0; + else + debug("%.200s line %d: matched 'Host %.100s' ", + filename, linenum, host); + } else if (strcasecmp(attrib, "originalhost") == 0) { + if (match_hostname(host_arg, arg, len) != 1) + result = 0; + else + debug("%.200s line %d: matched " + "'OriginalHost %.100s' ", + filename, linenum, host_arg); + } else if (strcasecmp(attrib, "user") == 0) { + if (match_pattern_list(ruser, arg, len, 0) != 1) + result = 0; + else + debug("%.200s line %d: matched 'User %.100s' ", + filename, linenum, ruser); + } else if (strcasecmp(attrib, "localuser") == 0) { + if (match_pattern_list(pw->pw_name, arg, len, 0) != 1) + result = 0; + else + debug("%.200s line %d: matched " + "'LocalUser %.100s' ", + filename, linenum, pw->pw_name); + } else if (strcasecmp(attrib, "command") == 0) { + if (gethostname(thishost, sizeof(thishost)) == -1) + fatal("gethostname: %s", strerror(errno)); + strlcpy(shorthost, thishost, sizeof(shorthost)); + shorthost[strcspn(thishost, ".")] = '\0'; + snprintf(portstr, sizeof(portstr), "%d", port); + + cmd = percent_expand(arg, + "L", shorthost, + "d", pw->pw_dir, + "h", host, + "l", thishost, + "n", host_arg, + "p", portstr, + "r", ruser, + "u", pw->pw_name, + (char *)NULL); + r = execute_in_shell(cmd); + if (r == -1) { + fatal("%.200s line %d: match command '%.100s' " + "error", filename, linenum, cmd); + } else if (r == 0) { + debug("%.200s line %d: matched " + "'Command \"%.100s\"' ", + filename, linenum, cmd); + } else + result = 0; + free(cmd); + } else { + error("Unsupported Match attribute %s", attrib); + return -1; + } + } + debug3("match %sfound", result ? "" : "not "); + *condition = cp; + return result; +} + +/* + * Returns the number of the token pointed to by cp or oBadOption. + */ static OpCodes parse_token(const char *cp, const char *filename, int linenum, const char *ignored_unknown) @@ -369,21 +554,24 @@ parse_token(const char *cp, const char *filename, int linenum, * only sets those values that have not already been set. */ #define WHITESPACE " \t\r\n" - int -process_config_line(Options *options, const char *host, - char *line, const char *filename, int linenum, - int *activep, int userconfig) +process_config_line(Options *options, struct passwd *pw, const char *host, + char *line, const char *filename, int linenum, int *activep, int userconfig) { char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; char **cpptr, fwdarg[256]; u_int i, *uintptr, max_entries = 0; - int negated, opcode, *intptr, value, value2; + int negated, opcode, *intptr, value, value2, cmdline = 0; LogLevel *log_level_ptr; long long val64; size_t len; Forward fwd; + if (activep == NULL) { /* We are processing a command line directive */ + cmdline = 1; + activep = &cmdline; + } + /* Strip trailing whitespace */ for (len = strlen(line) - 1; len > 0; len--) { if (strchr(WHITESPACE, line[len]) == NULL) @@ -822,6 +1010,9 @@ parse_int: goto parse_flag; case oHost: + if (cmdline) + fatal("Host directive not supported as a command-line " + "option"); *activep = 0; arg2 = NULL; while ((arg = strdelim(&s)) != NULL && *arg != '\0') { @@ -848,6 +1039,18 @@ parse_int: /* Avoid garbage check below, as strdelim is done. */ return 0; + case oMatch: + if (cmdline) + fatal("Host directive not supported as a command-line " + "option"); + value = match_cfg_line(options, &s, pw, host, + filename, linenum); + if (value < 0) + fatal("%.200s line %d: Bad Match condition", filename, + linenum); + *activep = value; + break; + case oEscapeChar: intptr = &options->escape_char; arg = strdelim(&s); @@ -1101,8 +1304,8 @@ parse_int: */ int -read_config_file(const char *filename, const char *host, Options *options, - int flags) +read_config_file(const char *filename, struct passwd *pw, const char *host, + Options *options, int flags) { FILE *f; char line[1024]; @@ -1133,8 +1336,8 @@ read_config_file(const char *filename, const char *host, Options *options, while (fgets(line, sizeof(line), f)) { /* Update line number counter. */ linenum++; - if (process_config_line(options, host, line, filename, linenum, - &active, flags & SSHCONF_USERCONF) != 0) + if (process_config_line(options, pw, host, line, filename, + linenum, &active, flags & SSHCONF_USERCONF) != 0) bad_options++; } fclose(f); diff --git a/usr.bin/ssh/readconf.h b/usr.bin/ssh/readconf.h index ca4a042ad4b..cde8b52427c 100644 --- a/usr.bin/ssh/readconf.h +++ b/usr.bin/ssh/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.96 2013/08/20 00:11:38 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.97 2013/10/14 22:22:03 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -159,12 +159,12 @@ typedef struct { void initialize_options(Options *); void fill_default_options(Options *); -int read_config_file(const char *, const char *, Options *, int); +int process_config_line(Options *, struct passwd *, const char *, char *, + const char *, int, int *, int); +int read_config_file(const char *, struct passwd *, const char *, + Options *, int); int parse_forward(Forward *, const char *, int, int); - -int -process_config_line(Options *, const char *, char *, const char *, int, int *, - int); +int default_ssh_port(void); void add_local_forward(Options *, const Forward *); void add_remote_forward(Options *, const Forward *); diff --git a/usr.bin/ssh/ssh-keysign.c b/usr.bin/ssh/ssh-keysign.c index 822cc291ead..e0602b26a56 100644 --- a/usr.bin/ssh/ssh-keysign.c +++ b/usr.bin/ssh/ssh-keysign.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keysign.c,v 1.37 2013/05/17 00:13:14 djm Exp $ */ +/* $OpenBSD: ssh-keysign.c,v 1.38 2013/10/14 22:22:04 djm Exp $ */ /* * Copyright (c) 2002 Markus Friedl. All rights reserved. * @@ -179,7 +179,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, "", &options, 0); + (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", &options, 0); fill_default_options(&options); if (options.enable_ssh_keysign != 1) fatal("ssh-keysign not enabled in %s", diff --git a/usr.bin/ssh/ssh.c b/usr.bin/ssh/ssh.c index e9c86a1b272..3458ff31bd3 100644 --- a/usr.bin/ssh/ssh.c +++ b/usr.bin/ssh/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.381 2013/07/25 00:29:10 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.382 2013/10/14 22:22:04 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -229,10 +229,9 @@ main(int ac, char **av) char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; struct stat st; struct passwd *pw; - int dummy, timeout_ms; + int timeout_ms; extern int optind, optreset; extern char *optarg; - struct servent *sp; Forward fwd; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ @@ -566,10 +565,9 @@ main(int ac, char **av) options.request_tty = REQUEST_TTY_NO; break; case 'o': - dummy = 1; line = xstrdup(optarg); - if (process_config_line(&options, host ? host : "", - line, "command-line", 0, &dummy, SSHCONF_USERCONF) + if (process_config_line(&options, pw, host ? host : "", + line, "command-line", 0, NULL, SSHCONF_USERCONF) != 0) exit(255); free(line); @@ -674,18 +672,19 @@ main(int ac, char **av) */ if (config != NULL) { if (strcasecmp(config, "none") != 0 && - !read_config_file(config, host, &options, SSHCONF_USERCONF)) + !read_config_file(config, pw, host, &options, + SSHCONF_USERCONF)) fatal("Can't open user config file %.100s: " "%.100s", config, strerror(errno)); } else { r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, _PATH_SSH_USER_CONFFILE); if (r > 0 && (size_t)r < sizeof(buf)) - (void)read_config_file(buf, host, &options, + (void)read_config_file(buf, pw, host, &options, SSHCONF_CHECKPERM|SSHCONF_USERCONF); /* Read systemwide configuration file after user config. */ - (void)read_config_file(_PATH_HOST_CONFIG_FILE, host, + (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, &options, 0); } @@ -721,10 +720,8 @@ main(int ac, char **av) options.user = xstrdup(pw->pw_name); /* Get default port if port has not been set. */ - if (options.port == 0) { - sp = getservbyname(SSH_SERVICE_NAME, "tcp"); - options.port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; - } + if (options.port == 0) + options.port = default_ssh_port(); /* preserve host name given on command line for %n expansion */ host_arg = host; diff --git a/usr.bin/ssh/ssh_config.5 b/usr.bin/ssh/ssh_config.5 index 9ddd6b8a6b1..f35f468f42b 100644 --- a/usr.bin/ssh/ssh_config.5 +++ b/usr.bin/ssh/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.168 2013/08/20 06:56:07 jmc Exp $ -.Dd $Mdocdate: August 20 2013 $ +.\" $OpenBSD: ssh_config.5,v 1.169 2013/10/14 22:22:05 djm Exp $ +.Dd $Mdocdate: October 14 2013 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -100,6 +100,8 @@ keywords are case-insensitive and arguments are case-sensitive): .It Cm Host Restricts the following declarations (up to the next .Cm Host +or +.Cm Match keyword) to be only for those hosts that match one of the patterns given after the keyword. If more than one pattern is provided, they should be separated by whitespace. @@ -124,6 +126,52 @@ matches. See .Sx PATTERNS for more information on patterns. +.It Cm Match +Restricts the following declarations (up to the next +.Cm Host +or +.Cm Match +keyword) to be used only when the conditions following the +.Cm Match +keyword are satisfied. +Match conditions are specified using one or more keyword/criteria pairs. +The available keywords are: +.Cm command , +.Cm host , +.Cm originalhost , +.Cm user , +and +.Cm localuser . +.Pp +The criteria for the +.Cm command +keyword is a path to a command that is executed. +If the command returns a zero exit status then the condition is considered true. +Commands containing whitespace characters must be quoted. +.Pp +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. +The criteria for the +.Cm host +keyword are matched against the target hostname, after any substitution +by the +.Cm Hostname +option. +The +.Cm originalhost +keyword matches against the hostname as it was specified on the command-line. +The +.Cm user +keyword matches against the target username on the remote host. +The +.Cm localuser +keyword matches against the name of the local user running +.Xr ssh 1 +(this keyword may be useful in system-wide +.Nm +files). .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are |