diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 2123fc03..31abb106 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -262,6 +262,8 @@ int compress_buffer(ssh_session session,ssh_buffer buf); int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); /* match.c */ +int match_pattern_list(const char *string, const char *pattern, + unsigned int len, int dolower); int match_hostname(const char *host, const char *pattern, unsigned int len); /* connector.c */ diff --git a/src/config.c b/src/config.c index 5d1baa51..3e94f6b8 100644 --- a/src/config.c +++ b/src/config.c @@ -30,6 +30,7 @@ #ifdef HAVE_GLOB_H # include #endif +#include #include "libssh/priv.h" #include "libssh/session.h" @@ -185,6 +186,32 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { NULL, SOC_UNKNOWN } }; +enum ssh_config_match_e { + MATCH_UNKNOWN = -1, + MATCH_ALL, + MATCH_CANONICAL, + MATCH_EXEC, + MATCH_HOST, + MATCH_ORIGINALHOST, + MATCH_USER, + MATCH_LOCALUSER +}; + +struct ssh_config_match_keyword_table_s { + const char *name; + enum ssh_config_match_e opcode; +}; + +static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = { + { "all", MATCH_ALL }, + { "canonical", MATCH_CANONICAL }, + { "exec", MATCH_EXEC }, + { "host", MATCH_HOST }, + { "originalhost", MATCH_ORIGINALHOST }, + { "user", MATCH_USER }, + { "localuser", MATCH_LOCALUSER }, +}; + static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, int *parsing, int seen[]); @@ -351,6 +378,40 @@ static void local_parse_glob(ssh_session session, } #endif /* HAVE_GLOB */ +static enum ssh_config_match_e +ssh_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) { + return ssh_config_match_keyword_table[i].opcode; + } + } + + return MATCH_UNKNOWN; +} + +static int +ssh_config_match(char *value, const char *pattern, bool negate) +{ + int ok, result = 0; + char *lowervalue; + + lowervalue = (value) ? ssh_lowercase(value) : NULL; + ok = match_pattern_list(lowervalue, pattern, strlen(pattern), 0); + if (ok <= 0 && negate == true) { + result = 1; + } else if (ok > 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)", + result == 1 ? "Matched" : "Not matched", value, pattern, + negate == true ? " (negated)" : "", ok); + SAFE_FREE(lowervalue); + return result; +} + static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, int *parsing, int seen[]) { enum ssh_config_opcode_e opcode; @@ -384,7 +445,10 @@ static int ssh_config_parse_line(ssh_session session, const char *line, } opcode = ssh_config_get_opcode(keyword); - if (*parsing == 1 && opcode != SOC_HOST && opcode != SOC_INCLUDE && + if (*parsing == 1 && + opcode != SOC_HOST && + opcode != SOC_MATCH && + opcode != SOC_INCLUDE && opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */ if (seen[opcode] != 0) { SAFE_FREE(x); @@ -405,6 +469,105 @@ static int ssh_config_parse_line(ssh_session session, const char *line, #endif /* HAVE_GLOB */ } break; + + case SOC_MATCH: { + bool negate; + int result = 1, args = 0; + enum ssh_config_match_e opt; + + *parsing = 0; + do { + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_config_get_match_opcode(p); + switch (opt) { + case MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if (args == 1 && (p == NULL || p[0] == '\0')) { + /* The first argument and end of line */ + if (negate == true) { + result = 0; + } + break; + } + + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match all can not be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + + case MATCH_EXEC: + case MATCH_ORIGINALHOST: + case MATCH_LOCALUSER: + /* Skip one argument */ + p = ssh_config_get_str_tok(&s, NULL); + args++; + FALL_THROUGH; + case MATCH_CANONICAL: + SSH_LOG(SSH_LOG_WARN, "line: %d: Unsupported Match keyword " + "'%s', Ignoring\n", count, p); + result = 0; + break; + + case MATCH_HOST: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match host keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.host, p, negate); + args++; + break; + + case MATCH_USER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match user keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.username, p, negate); + args++; + break; + + case MATCH_UNKNOWN: + default: + ssh_set_error(session, SSH_FATAL, + "ERROR - Unknown argument '%s' for Match keyword", p); + SAFE_FREE(x); + return -1; + } + } while (p != NULL && p[0] != '\0'); + if (args < 1) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Match keyword requires an argument"); + SAFE_FREE(x); + return -1; + } + *parsing = result; + break; + } case SOC_HOST: { int ok = 0, result = -1; diff --git a/src/match.c b/src/match.c index ab943413..06587b92 100644 --- a/src/match.c +++ b/src/match.c @@ -119,7 +119,7 @@ static int match_pattern(const char *s, const char *pattern) { * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is * no match at all. */ -static int match_pattern_list(const char *string, const char *pattern, +int match_pattern_list(const char *string, const char *pattern, unsigned int len, int dolower) { char sub[1024]; int negated;