aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/libssh/bind_config.h3
-rw-r--r--src/bind_config.c215
2 files changed, 205 insertions, 13 deletions
diff --git a/include/libssh/bind_config.h b/include/libssh/bind_config.h
index 0a1a6d75..28216329 100644
--- a/include/libssh/bind_config.h
+++ b/include/libssh/bind_config.h
@@ -29,6 +29,8 @@
#include "libssh/server.h"
enum ssh_bind_config_opcode_e {
+ /* Known but not allowed in Match block */
+ BIND_CFG_NOT_ALLOWED_IN_MATCH = -4,
/* Unknown opcode */
BIND_CFG_UNKNOWN = -3,
/* Known and not applicable to libssh */
@@ -43,6 +45,7 @@ enum ssh_bind_config_opcode_e {
BIND_CFG_CIPHERS,
BIND_CFG_MACS,
BIND_CFG_KEXALGORITHMS,
+ BIND_CFG_MATCH,
BIND_CFG_MAX /* Keep this one last in the list */
};
diff --git a/src/bind_config.c b/src/bind_config.c
index 8966494f..72849dc3 100644
--- a/src/bind_config.c
+++ b/src/bind_config.c
@@ -42,9 +42,14 @@
#define MAX_LINE_SIZE 1024
+/* Flags used for the parser state */
+#define PARSING 1
+#define IN_MATCH (1<<1)
+
struct ssh_bind_config_keyword_table_s {
const char *name;
enum ssh_bind_config_opcode_e opcode;
+ bool allowed_in_match;
};
static struct ssh_bind_config_keyword_table_s
@@ -67,7 +72,8 @@ ssh_bind_config_keyword_table[] = {
},
{
.name = "loglevel",
- .opcode = BIND_CFG_LOGLEVEL
+ .opcode = BIND_CFG_LOGLEVEL,
+ .allowed_in_match = true,
},
{
.name = "ciphers",
@@ -82,17 +88,83 @@ ssh_bind_config_keyword_table[] = {
.opcode = BIND_CFG_KEXALGORITHMS
},
{
+ .name = "match",
+ .opcode = BIND_CFG_MATCH,
+ .allowed_in_match = true
+ },
+ {
.opcode = BIND_CFG_UNKNOWN,
}
};
+enum ssh_bind_config_match_e {
+ BIND_MATCH_UNKNOWN = -1,
+ BIND_MATCH_ALL,
+ BIND_MATCH_USER,
+ BIND_MATCH_GROUP,
+ BIND_MATCH_HOST,
+ BIND_MATCH_LOCALADDRESS,
+ BIND_MATCH_LOCALPORT,
+ BIND_MATCH_RDOMAIN,
+ BIND_MATCH_ADDRESS,
+};
+
+struct ssh_bind_config_match_keyword_table_s {
+ const char *name;
+ enum ssh_bind_config_match_e opcode;
+};
+
+static struct ssh_bind_config_match_keyword_table_s
+ssh_bind_config_match_keyword_table[] = {
+ {
+ .name = "all",
+ .opcode = BIND_MATCH_ALL
+ },
+ {
+ .name = "user",
+ .opcode = BIND_MATCH_USER
+ },
+ {
+ .name = "group",
+ .opcode = BIND_MATCH_GROUP
+ },
+ {
+ .name = "host",
+ .opcode = BIND_MATCH_HOST
+ },
+ {
+ .name = "localaddress",
+ .opcode = BIND_MATCH_LOCALADDRESS
+ },
+ {
+ .name = "localport",
+ .opcode = BIND_MATCH_LOCALPORT
+ },
+ {
+ .name = "rdomain",
+ .opcode = BIND_MATCH_RDOMAIN
+ },
+ {
+ .name = "address",
+ .opcode = BIND_MATCH_ADDRESS
+ },
+ {
+ .opcode = BIND_MATCH_UNKNOWN
+ },
+};
+
static enum ssh_bind_config_opcode_e
-ssh_bind_config_get_opcode(char *keyword)
+ssh_bind_config_get_opcode(char *keyword, uint32_t *parser_flags)
{
int i;
for (i = 0; ssh_bind_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_bind_config_keyword_table[i].name) == 0) {
+ if ((*parser_flags & IN_MATCH) &&
+ !(ssh_bind_config_keyword_table[i].allowed_in_match))
+ {
+ return BIND_CFG_NOT_ALLOWED_IN_MATCH;
+ }
return ssh_bind_config_keyword_table[i].opcode;
}
}
@@ -171,6 +243,19 @@ static void local_parse_glob(ssh_bind bind,
}
#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */
+static enum ssh_bind_config_match_e
+ssh_bind_config_get_match_opcode(const char *keyword)
+{
+ size_t i;
+
+ for (i = 0; ssh_bind_config_match_keyword_table[i].name != NULL; i++) {
+ if (strcasecmp(keyword, ssh_bind_config_match_keyword_table[i].name) == 0) {
+ return ssh_bind_config_match_keyword_table[i].opcode;
+ }
+ }
+
+ return BIND_MATCH_UNKNOWN;
+}
static int
ssh_bind_config_parse_line(ssh_bind bind,
@@ -215,10 +300,11 @@ ssh_bind_config_parse_line(ssh_bind bind,
return 0;
}
- opcode = ssh_bind_config_get_opcode(keyword);
- if (*parser_flags == 1 &&
+ opcode = ssh_bind_config_get_opcode(keyword, parser_flags);
+ if ((*parser_flags & PARSING) &&
opcode != BIND_CFG_HOSTKEY &&
opcode != BIND_CFG_INCLUDE &&
+ opcode != BIND_CFG_MATCH &&
opcode > BIND_CFG_UNSUPPORTED) { /* Ignore all unknown types here */
/* Skip all the options that were already applied */
if (seen[opcode] != 0) {
@@ -231,7 +317,7 @@ ssh_bind_config_parse_line(ssh_bind bind,
switch (opcode) {
case BIND_CFG_INCLUDE:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
local_parse_glob(bind, p, parser_flags, seen);
#else
@@ -242,39 +328,39 @@ ssh_bind_config_parse_line(ssh_bind bind,
case BIND_CFG_HOSTKEY:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p);
}
break;
case BIND_CFG_LISTENADDRESS:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p);
}
break;
case BIND_CFG_PORT:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, p);
}
break;
case BIND_CFG_CIPHERS:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, p);
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p);
}
break;
case BIND_CFG_MACS:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, p);
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p);
}
break;
case BIND_CFG_LOGLEVEL:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
int value = -1;
if (strcasecmp(p, "quiet") == 0) {
@@ -300,10 +386,113 @@ ssh_bind_config_parse_line(ssh_bind bind,
break;
case BIND_CFG_KEXALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
- if (p && *parser_flags) {
+ if (p && (*parser_flags & PARSING)) {
ssh_bind_options_set(bind, SSH_BIND_OPTIONS_KEY_EXCHANGE, p);
}
break;
+ case BIND_CFG_MATCH: {
+ bool negate;
+ int result = PARSING;
+ size_t args = 0;
+ enum ssh_bind_config_match_e opt;
+ const char *p2 = NULL;
+
+ /* The options set in Match blocks should be applied when a connection
+ * is accepted, and not right away when parsing the file (as it is
+ * currently done). This means the configuration files should be parsed
+ * again or the options set in the Match blocks should be stored and
+ * applied as necessary. */
+
+ /* If this is the first Match block, erase the seen table to allow
+ * options to be overridden. Erasing the seen table was the easiest way
+ * to allow overriding an option, but only for the first occurrence of
+ * an option in a Match block. This is sufficient for the current
+ * implementation which supports only the 'All' criterion, meaning the
+ * options can be applied right away. */
+ if (!(*parser_flags & IN_MATCH)) {
+ memset(seen, 0x00, BIND_CFG_MAX * sizeof(uint8_t));
+ }
+
+ /* In this line the PARSING bit is cleared from the flags */
+ *parser_flags = IN_MATCH;
+ do {
+ p = p2 = 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_bind_config_get_match_opcode(p);
+ switch (opt) {
+ case BIND_MATCH_ALL:
+ p = ssh_config_get_str_tok(&s, NULL);
+ if ((args == 1) && (p == NULL || p[0] == '\0')) {
+ /* The "all" keyword does not accept arguments or modifiers
+ */
+ if (negate == true) {
+ result = 0;
+ }
+ break;
+ }
+ ssh_set_error(bind, SSH_FATAL,
+ "line %d: ERROR - Match all cannot be combined with "
+ "other Match attributes", count);
+ SAFE_FREE(x);
+ return -1;
+ case BIND_MATCH_USER:
+ case BIND_MATCH_GROUP:
+ case BIND_MATCH_HOST:
+ case BIND_MATCH_LOCALADDRESS:
+ case BIND_MATCH_LOCALPORT:
+ case BIND_MATCH_RDOMAIN:
+ case BIND_MATCH_ADDRESS:
+ /* Only "All" is supported for now */
+ /* Skip one argument */
+ p = ssh_config_get_str_tok(&s, NULL);
+ if (p == NULL || p[0] == '\0') {
+ SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword "
+ "'%s' requires argument\n", count, p2);
+ SAFE_FREE(x);
+ return -1;
+ }
+ args++;
+ SSH_LOG(SSH_LOG_WARN,
+ "line %d: Unsupported Match keyword '%s', ignoring\n",
+ count,
+ p2);
+ result = 0;
+ break;
+ case BIND_MATCH_UNKNOWN:
+ default:
+ ssh_set_error(bind, SSH_FATAL,
+ "ERROR - Unknown argument '%s' for Match keyword", p);
+ SAFE_FREE(x);
+ return -1;
+ }
+ } while (p != NULL && p[0] != '\0');
+ if (args == 0) {
+ ssh_set_error(bind, SSH_FATAL,
+ "ERROR - Match keyword requires an argument");
+ SAFE_FREE(x);
+ return -1;
+ }
+ /* This line only sets the PARSING flag if all checks passed */
+ *parser_flags |= result;
+ break;
+ }
+ case BIND_CFG_NOT_ALLOWED_IN_MATCH:
+ SSH_LOG(SSH_LOG_WARN, "Option not allowed in Match block: %s, line: %d",
+ keyword, count);
+ break;
case BIND_CFG_UNKNOWN:
SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d",
keyword, count);
@@ -349,7 +538,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
- parser_flags = 1;
+ parser_flags = PARSING;
while (fgets(line, sizeof(line), f)) {
count++;
rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen);