1
1
libssh/src/config.c
Jakub Jelen 574690ae2e config: Ignore empty lines to avoid OOB array access
Fixes T187

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2019-12-09 16:08:03 +01:00

1170 строки
35 KiB
C

/*
* config.c - parse the ssh config file
*
* This file is part of the SSH Library
*
* Copyright (c) 2009-2013 by Andreas Schneider <asn@cryptomilk.org>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_GLOB_H
# include <glob.h>
#endif
#include <stdbool.h>
#include <limits.h>
#ifndef _WIN32
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <errno.h>
# include <signal.h>
# include <sys/wait.h>
#endif
#include "libssh/config_parser.h"
#include "libssh/config.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/misc.h"
#include "libssh/options.h"
#define MAX_LINE_SIZE 1024
struct ssh_config_keyword_table_s {
const char *name;
enum ssh_config_opcode_e opcode;
};
static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
{ "host", SOC_HOST },
{ "match", SOC_MATCH },
{ "hostname", SOC_HOSTNAME },
{ "port", SOC_PORT },
{ "user", SOC_USERNAME },
{ "identityfile", SOC_IDENTITY },
{ "ciphers", SOC_CIPHERS },
{ "macs", SOC_MACS },
{ "compression", SOC_COMPRESSION },
{ "connecttimeout", SOC_TIMEOUT },
{ "protocol", SOC_PROTOCOL },
{ "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK },
{ "userknownhostsfile", SOC_KNOWNHOSTS },
{ "proxycommand", SOC_PROXYCOMMAND },
{ "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY },
{ "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY },
{ "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS },
{ "include", SOC_INCLUDE },
{ "bindaddress", SOC_BINDADDRESS},
{ "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE},
{ "loglevel", SOC_LOGLEVEL},
{ "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS},
{ "kexalgorithms", SOC_KEXALGORITHMS},
{ "mac", SOC_UNSUPPORTED}, /* SSHv1 */
{ "gssapiauthentication", SOC_GSSAPIAUTHENTICATION},
{ "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION},
{ "passwordauthentication", SOC_PASSWORDAUTHENTICATION},
{ "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION},
{ "addkeystoagent", SOC_UNSUPPORTED},
{ "addressfamily", SOC_UNSUPPORTED},
{ "batchmode", SOC_UNSUPPORTED},
{ "canonicaldomains", SOC_UNSUPPORTED},
{ "canonicalizefallbacklocal", SOC_UNSUPPORTED},
{ "canonicalizehostname", SOC_UNSUPPORTED},
{ "canonicalizemaxdots", SOC_UNSUPPORTED},
{ "canonicalizepermittedcnames", SOC_UNSUPPORTED},
{ "certificatefile", SOC_UNSUPPORTED},
{ "challengeresponseauthentication", SOC_UNSUPPORTED},
{ "checkhostip", SOC_UNSUPPORTED},
{ "cipher", SOC_UNSUPPORTED}, /* SSHv1 */
{ "compressionlevel", SOC_UNSUPPORTED}, /* SSHv1 */
{ "connectionattempts", SOC_UNSUPPORTED},
{ "enablesshkeysign", SOC_UNSUPPORTED},
{ "fingerprinthash", SOC_UNSUPPORTED},
{ "forwardagent", SOC_UNSUPPORTED},
{ "gssapikeyexchange", SOC_UNSUPPORTED},
{ "gssapirenewalforcesrekey", SOC_UNSUPPORTED},
{ "gssapitrustdns", SOC_UNSUPPORTED},
{ "hashknownhosts", SOC_UNSUPPORTED},
{ "hostbasedauthentication", SOC_UNSUPPORTED},
{ "hostbasedkeytypes", SOC_UNSUPPORTED},
{ "hostkeyalias", SOC_UNSUPPORTED},
{ "identitiesonly", SOC_UNSUPPORTED},
{ "identityagent", SOC_UNSUPPORTED},
{ "ipqos", SOC_UNSUPPORTED},
{ "kbdinteractivedevices", SOC_UNSUPPORTED},
{ "nohostauthenticationforlocalhost", SOC_UNSUPPORTED},
{ "numberofpasswordprompts", SOC_UNSUPPORTED},
{ "pkcs11provider", SOC_UNSUPPORTED},
{ "preferredauthentications", SOC_UNSUPPORTED},
{ "proxyjump", SOC_PROXYJUMP},
{ "proxyusefdpass", SOC_UNSUPPORTED},
{ "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES},
{ "rekeylimit", SOC_REKEYLIMIT},
{ "remotecommand", SOC_UNSUPPORTED},
{ "revokedhostkeys", SOC_UNSUPPORTED},
{ "rhostsrsaauthentication", SOC_UNSUPPORTED},
{ "rsaauthentication", SOC_UNSUPPORTED}, /* SSHv1 */
{ "serveralivecountmax", SOC_UNSUPPORTED},
{ "serveraliveinterval", SOC_UNSUPPORTED},
{ "streamlocalbindmask", SOC_UNSUPPORTED},
{ "streamlocalbindunlink", SOC_UNSUPPORTED},
{ "syslogfacility", SOC_UNSUPPORTED},
{ "tcpkeepalive", SOC_UNSUPPORTED},
{ "updatehostkeys", SOC_UNSUPPORTED},
{ "useprivilegedport", SOC_UNSUPPORTED},
{ "verifyhostkeydns", SOC_UNSUPPORTED},
{ "visualhostkey", SOC_UNSUPPORTED},
{ "clearallforwardings", SOC_NA},
{ "controlmaster", SOC_NA},
{ "controlpersist", SOC_NA},
{ "controlpath", SOC_NA},
{ "dynamicforward", SOC_NA},
{ "escapechar", SOC_NA},
{ "exitonforwardfailure", SOC_NA},
{ "forwardx11", SOC_NA},
{ "forwardx11timeout", SOC_NA},
{ "forwardx11trusted", SOC_NA},
{ "gatewayports", SOC_NA},
{ "ignoreunknown", SOC_NA},
{ "localcommand", SOC_NA},
{ "localforward", SOC_NA},
{ "permitlocalcommand", SOC_NA},
{ "remoteforward", SOC_NA},
{ "requesttty", SOC_NA},
{ "sendenv", SOC_NA},
{ "tunnel", SOC_NA},
{ "tunneldevice", SOC_NA},
{ "xauthlocation", SOC_NA},
{ "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES},
{ NULL, SOC_UNKNOWN }
};
enum ssh_config_match_e {
MATCH_UNKNOWN = -1,
MATCH_ALL,
MATCH_FINAL,
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 },
{ "final", MATCH_FINAL },
{ "exec", MATCH_EXEC },
{ "host", MATCH_HOST },
{ "originalhost", MATCH_ORIGINALHOST },
{ "user", MATCH_USER },
{ "localuser", MATCH_LOCALUSER },
{ NULL, MATCH_UNKNOWN },
};
static int ssh_config_parse_line(ssh_session session, const char *line,
unsigned int count, int *parsing);
static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
int i;
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
return ssh_config_keyword_table[i].opcode;
}
}
return SOC_UNKNOWN;
}
static void
local_parse_file(ssh_session session,
const char *filename,
int *parsing)
{
FILE *f;
char line[MAX_LINE_SIZE] = {0};
unsigned int count = 0;
int rv;
f = fopen(filename, "r");
if (f == NULL) {
SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
filename);
return;
}
SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename);
while (fgets(line, sizeof(line), f)) {
count++;
rv = ssh_config_parse_line(session, line, count, parsing);
if (rv < 0) {
fclose(f);
return;
}
}
fclose(f);
return;
}
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
static void local_parse_glob(ssh_session session,
const char *fileglob,
int *parsing)
{
glob_t globbuf = {
.gl_flags = 0,
};
int rt;
size_t i;
rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf);
if (rt == GLOB_NOMATCH) {
globfree(&globbuf);
return;
} else if (rt != 0) {
SSH_LOG(SSH_LOG_RARE, "Glob error: %s",
fileglob);
globfree(&globbuf);
return;
}
for (i = 0; i < globbuf.gl_pathc; i++) {
local_parse_file(session, globbuf.gl_pathv[i], parsing);
}
globfree(&globbuf);
}
#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */
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;
ok = match_pattern_list(value, 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);
return result;
}
#ifdef _WIN32
static int
ssh_match_exec(ssh_session session, const char *command, bool negate)
{
(void) session;
(void) command;
(void) negate;
SSH_LOG(SSH_LOG_TRACE, "Unsupported 'exec' command on Windows '%s'",
command);
return 0;
}
#else /* _WIN32 */
static int
ssh_exec_shell(char *cmd)
{
char *shell = NULL;
pid_t pid;
int status, devnull, rc;
shell = getenv("SHELL");
if (shell == NULL || shell[0] == '\0') {
shell = (char *)"/bin/sh";
}
rc = access(shell, X_OK);
if (rc != 0) {
SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell);
return -1;
}
/* Need this to redirect subprocess stdin/out */
devnull = open("/dev/null", O_RDWR);
if (devnull == -1) {
SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", strerror(errno));
return -1;
}
SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd);
pid = fork();
if (pid == 0) { /* Child */
char *argv[4];
/* Redirect child stdin and stdout. Leave stderr */
rc = dup2(devnull, STDIN_FILENO);
if (rc == -1) {
SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno));
exit(1);
}
rc = dup2(devnull, STDOUT_FILENO);
if (rc == -1) {
SSH_LOG(SSH_LOG_WARN, "dup2: %s", strerror(errno));
exit(1);
}
if (devnull > STDERR_FILENO) {
close(devnull);
}
argv[0] = shell;
argv[1] = (char *) "-c";
argv[2] = strdup(cmd);
argv[3] = NULL;
rc = execv(argv[0], argv);
if (rc == -1) {
SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd,
strerror(errno));
/* Die with signal to make this error apparent to parent. */
signal(SIGTERM, SIG_DFL);
kill(getpid(), SIGTERM);
_exit(1);
}
}
/* Parent */
close(devnull);
if (pid == -1) { /* Error */
SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", strerror(errno));
return -1;
}
while (waitpid(pid, &status, 0) == -1) {
if (errno != EINTR) {
SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno));
return -1;
}
}
if (!WIFEXITED(status)) {
SSH_LOG(SSH_LOG_WARN, "Command %s exitted abnormally", cmd);
return -1;
}
SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status));
return WEXITSTATUS(status);
}
static int
ssh_match_exec(ssh_session session, const char *command, bool negate)
{
int rv, result = 0;
char *cmd = NULL;
/* TODO There should be more supported expansions */
cmd = ssh_path_expand_escape(session, command);
rv = ssh_exec_shell(cmd);
if (rv > 0 && negate == true) {
result = 1;
} else if (rv == 0 && negate == false) {
result = 1;
}
SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)",
result == 1 ? "Matched" : "Not matched", cmd,
negate == true ? " (negated)" : "", rv);
free(cmd);
return result;
}
#endif /* _WIN32 */
/* @brief: Parse the ProxyJump configuration line and if parsing,
* stores the result in the configuration option
*/
static int
ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing)
{
char *c = NULL, *cp = NULL, *endp = NULL;
char *username = NULL;
char *hostname = NULL;
char *port = NULL;
char *next = NULL;
int cmp, rv = SSH_ERROR;
bool parse_entry = do_parsing;
/* Special value none disables the proxy */
cmp = strcasecmp(s, "none");
if (cmp == 0 && do_parsing) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s);
return SSH_OK;
}
/* This is comma-separated list of [user@]host[:port] entries */
c = strdup(s);
if (c == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
cp = c;
do {
endp = strchr(cp, ',');
if (endp != NULL) {
/* Split out the token */
*endp = '\0';
}
if (parse_entry) {
/* We actually care only about the first item */
rv = ssh_config_parse_uri(cp, &username, &hostname, &port);
/* The rest of the list needs to be passed on */
if (endp != NULL) {
next = strdup(endp + 1);
if (next == NULL) {
ssh_set_error_oom(session);
rv = SSH_ERROR;
}
}
} else {
/* The rest is just sanity-checked to avoid failures later */
rv = ssh_config_parse_uri(cp, NULL, NULL, NULL);
}
if (rv != SSH_OK) {
goto out;
}
parse_entry = 0;
if (endp != NULL) {
cp = endp + 1;
} else {
cp = NULL; /* end */
}
} while (cp != NULL);
if (hostname != NULL && do_parsing) {
char com[512] = {0};
rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s",
username ? " -l " : "",
username ? username : "",
port ? " -p " : "",
port ? port : "",
next ? " -J " : "",
next ? next : "",
hostname);
if (rv < 0 || rv >= (int)sizeof(com)) {
SSH_LOG(SSH_LOG_WARN, "Too long ProxyJump configuration line");
rv = SSH_ERROR;
goto out;
}
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com);
}
rv = SSH_OK;
out:
SAFE_FREE(username);
SAFE_FREE(hostname);
SAFE_FREE(port);
SAFE_FREE(next);
SAFE_FREE(c);
return rv;
}
static int
ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing)
{
enum ssh_config_opcode_e opcode;
const char *p = NULL, *p2 = NULL;
char *s = NULL, *x = NULL;
char *keyword = NULL;
char *lowerhost = NULL;
size_t len;
int i, rv;
uint8_t *seen = session->opts.options_seen;
long l;
int64_t ll;
/* Ignore empty lines */
if (line == NULL || *line == '\0') {
return 0;
}
x = s = strdup(line);
if (s == NULL) {
ssh_set_error_oom(session);
return -1;
}
/* Remove trailing spaces */
for (len = strlen(s) - 1; len > 0; len--) {
if (! isspace(s[len])) {
break;
}
s[len] = '\0';
}
keyword = ssh_config_get_token(&s);
if (keyword == NULL || *keyword == '#' ||
*keyword == '\0' || *keyword == '\n') {
SAFE_FREE(x);
return 0;
}
opcode = ssh_config_get_opcode(keyword);
if (*parsing == 1 &&
opcode != SOC_HOST &&
opcode != SOC_MATCH &&
opcode != SOC_INCLUDE &&
opcode > SOC_UNSUPPORTED) { /* Ignore all unknown types here */
/* Skip all the options that were already applied */
if (seen[opcode] != 0) {
SAFE_FREE(x);
return 0;
}
seen[opcode] = 1;
}
switch (opcode) {
case SOC_INCLUDE: /* recursive include of other files */
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
local_parse_glob(session, p, parsing);
#else
local_parse_file(session, p, parsing);
#endif /* HAVE_GLOB */
}
break;
case SOC_MATCH: {
bool negate;
int result = 1;
size_t args = 0;
enum ssh_config_match_e opt;
char *localuser = NULL;
*parsing = 0;
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_config_get_match_opcode(p);
switch (opt) {
case MATCH_ALL:
p = ssh_config_get_str_tok(&s, NULL);
if (args <= 2 && (p == NULL || p[0] == '\0')) {
/* The first or second, but last argument. The "all" keyword
* can be prefixed with either "final" or "canonical"
* keywords which do not have any effect here. */
if (negate == true) {
result = 0;
}
break;
}
ssh_set_error(session, SSH_FATAL,
"line %d: ERROR - Match all cannot be combined with "
"other Match attributes", count);
SAFE_FREE(x);
return -1;
case MATCH_FINAL:
case MATCH_CANONICAL:
SSH_LOG(SSH_LOG_INFO,
"line %d: Unsupported Match keyword '%s', skipping",
count,
p);
/* Not set any result here -- the result is dependent on the
* following matches after this keyword */
break;
case MATCH_EXEC:
/* Skip one argument (including in quotes) */
p = ssh_config_get_token(&s);
if (p == NULL || p[0] == '\0') {
SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword "
"'%s' requires argument", count, p2);
SAFE_FREE(x);
return -1;
}
if (result != 1) {
SSH_LOG(SSH_LOG_INFO, "line %d: Skipped match exec "
"'%s' as previous conditions already failed.",
count, p2);
continue;
}
result &= ssh_match_exec(session, p, negate);
args++;
break;
case MATCH_LOCALUSER:
/* 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;
}
localuser = ssh_get_local_username();
if (localuser == NULL) {
SSH_LOG(SSH_LOG_WARN, "line %d: Can not get local username "
"for conditional matching.", count);
SAFE_FREE(x);
return -1;
}
result &= ssh_config_match(localuser, p, negate);
SAFE_FREE(localuser);
args++;
break;
case MATCH_ORIGINALHOST:
/* 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", count, p2);
SAFE_FREE(x);
return -1;
}
args++;
SSH_LOG(SSH_LOG_INFO,
"line %d: Unsupported Match keyword '%s', ignoring",
count,
p2);
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 == 0) {
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;
*parsing = 0;
lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL;
for (p = ssh_config_get_str_tok(&s, NULL);
p != NULL && p[0] != '\0';
p = ssh_config_get_str_tok(&s, NULL)) {
if (ok >= 0) {
ok = match_hostname(lowerhost, p, strlen(p));
if (result == -1 && ok < 0) {
result = 0;
} else if (result == -1 && ok > 0) {
result = 1;
}
}
}
SAFE_FREE(lowerhost);
if (result != -1) {
*parsing = result;
}
break;
}
case SOC_HOSTNAME:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
char *z = ssh_path_expand_escape(session, p);
if (z == NULL) {
z = strdup(p);
}
ssh_options_set(session, SSH_OPTIONS_HOST, z);
free(z);
}
break;
case SOC_PORT:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
}
break;
case SOC_USERNAME:
if (session->opts.username == NULL) {
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_USER, p);
}
}
break;
case SOC_IDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
}
break;
case SOC_CIPHERS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
}
break;
case SOC_MACS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
}
break;
case SOC_COMPRESSION:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
if (i) {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
} else {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
}
}
break;
case SOC_PROTOCOL:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
char *a, *b;
b = strdup(p);
if (b == NULL) {
SAFE_FREE(x);
ssh_set_error_oom(session);
return -1;
}
i = 0;
ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
for (a = strtok(b, ","); a; a = strtok(NULL, ",")) {
switch (atoi(a)) {
case 1:
break;
case 2:
i = 1;
ssh_options_set(session, SSH_OPTIONS_SSH2, &i);
break;
default:
break;
}
}
SAFE_FREE(b);
}
break;
case SOC_TIMEOUT:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
}
break;
case SOC_STRICTHOSTKEYCHECK:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
}
break;
case SOC_KNOWNHOSTS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
}
break;
case SOC_PROXYCOMMAND:
p = ssh_config_get_cmd(&s);
/* We share the seen value with the ProxyJump */
if (p && *parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
}
break;
case SOC_PROXYJUMP:
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
SAFE_FREE(x);
return -1;
}
/* We share the seen value with the ProxyCommand */
rv = ssh_config_parse_proxy_jump(session, p,
(*parsing && !seen[SOC_PROXYCOMMAND]));
if (rv != SSH_OK) {
SAFE_FREE(x);
return -1;
}
break;
case SOC_GSSAPISERVERIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
}
break;
case SOC_GSSAPICLIENTIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
}
break;
case SOC_GSSAPIDELEGATECREDENTIALS:
i = ssh_config_get_yesno(&s, -1);
if (i >=0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
}
break;
case SOC_BINDADDRESS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
}
break;
case SOC_GLOBALKNOWNHOSTSFILE:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
}
break;
case SOC_LOGLEVEL:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
int value = -1;
if (strcasecmp(p, "quiet") == 0) {
value = SSH_LOG_NONE;
} else if (strcasecmp(p, "fatal") == 0 ||
strcasecmp(p, "error")== 0 ||
strcasecmp(p, "info") == 0) {
value = SSH_LOG_WARN;
} else if (strcasecmp(p, "verbose") == 0) {
value = SSH_LOG_INFO;
} else if (strcasecmp(p, "DEBUG") == 0 ||
strcasecmp(p, "DEBUG1") == 0) {
value = SSH_LOG_DEBUG;
} else if (strcasecmp(p, "DEBUG2") == 0 ||
strcasecmp(p, "DEBUG3") == 0) {
value = SSH_LOG_TRACE;
}
if (value != -1) {
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
}
}
break;
case SOC_HOSTKEYALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
}
break;
case SOC_PUBKEYACCEPTEDTYPES:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
}
break;
case SOC_KEXALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
}
break;
case SOC_REKEYLIMIT:
/* Parse the data limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
break;
} else if (strcmp(p, "default") == 0) {
/* Default rekey limits enforced automaticaly */
ll = 0;
} else {
char *endp = NULL;
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit");
break;
}
switch (*endp) {
case 'G':
if (ll > LLONG_MAX / 1024) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 1024;
FALL_THROUGH;
case 'M':
if (ll > LLONG_MAX / 1024) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 1024;
FALL_THROUGH;
case 'K':
if (ll > LLONG_MAX / 1024) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 1024;
endp++;
FALL_THROUGH;
case '\0':
/* just the number */
break;
default:
/* Invalid suffix */
ll = -1;
break;
}
if (*endp != ' ' && *endp != '\0') {
SSH_LOG(SSH_LOG_WARN,
"Invalid trailing characters after the rekey limit: %s",
endp);
break;
}
}
if (ll > -1 && *parsing) {
uint64_t v = (uint64_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
}
/* Parse the time limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
break;
} else if (strcmp(p, "none") == 0) {
ll = 0;
} else {
char *endp = NULL;
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_WARN, "Invalid argument to rekey limit");
break;
}
switch (*endp) {
case 'w':
case 'W':
if (ll > LLONG_MAX / 7) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 7;
FALL_THROUGH;
case 'd':
case 'D':
if (ll > LLONG_MAX / 24) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 24;
FALL_THROUGH;
case 'h':
case 'H':
if (ll > LLONG_MAX / 60) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 60;
FALL_THROUGH;
case 'm':
case 'M':
if (ll > LLONG_MAX / 60) {
SSH_LOG(SSH_LOG_WARN, "Possible overflow of rekey limit");
ll = -1;
break;
}
ll = ll * 60;
FALL_THROUGH;
case 's':
case 'S':
endp++;
FALL_THROUGH;
case '\0':
/* just the number */
break;
default:
/* Invalid suffix */
ll = -1;
break;
}
if (*endp != '\0') {
SSH_LOG(SSH_LOG_WARN, "Invalid trailing characters after the"
" rekey limit: %s", endp);
break;
}
}
if (ll > -1 && *parsing) {
uint32_t v = (uint32_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
}
break;
case SOC_GSSAPIAUTHENTICATION:
case SOC_KBDINTERACTIVEAUTHENTICATION:
case SOC_PASSWORDAUTHENTICATION:
case SOC_PUBKEYAUTHENTICATION:
i = ssh_config_get_yesno(&s, 0);
if (i>=0 && *parsing) {
switch(opcode){
case SOC_GSSAPIAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i);
break;
case SOC_KBDINTERACTIVEAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i);
break;
case SOC_PASSWORDAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i);
break;
case SOC_PUBKEYAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i);
break;
/* make gcc happy */
default:
break;
}
}
break;
case SOC_NA:
SSH_LOG(SSH_LOG_INFO, "Unapplicable option: %s, line: %d",
keyword, count);
break;
case SOC_UNSUPPORTED:
SSH_LOG(SSH_LOG_INFO, "Unsupported option: %s, line: %d",
keyword, count);
break;
case SOC_UNKNOWN:
SSH_LOG(SSH_LOG_INFO, "Unknown option: %s, line: %d",
keyword, count);
break;
default:
ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
opcode);
SAFE_FREE(x);
return -1;
break;
}
SAFE_FREE(x);
return 0;
}
/* @brief Parse configuration file and set the options to the given session
*
* @params[in] session The ssh session
* @params[in] filename The path to the ssh configuration file
*
* @returns 0 on successful parsing the configuration file, -1 on error
*/
int ssh_config_parse_file(ssh_session session, const char *filename)
{
char line[MAX_LINE_SIZE] = {0};
unsigned int count = 0;
FILE *f;
int parsing, rv;
f = fopen(filename, "r");
if (f == NULL) {
return 0;
}
SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename);
parsing = 1;
while (fgets(line, sizeof(line), f)) {
count++;
rv = ssh_config_parse_line(session, line, count, &parsing);
if (rv < 0) {
fclose(f);
return -1;
}
}
fclose(f);
return 0;
}