config: Include files with relative paths

The libssh versions before this included files with relative path based
on the current working directory, which can result unexpected results
and is different from the OpenSSH include behavior. The manual page for
ssh_config lists:

> iles without absolute paths are assumed to be in ~/.ssh if included in
> a user configuration file or /etc/ssh if included from the system
> configuration file.

This is following the semantics as close as possible with a difference
that we do not use the hardcoded ~/.ssh for user configuration files,
but the path defined with SSH_OPTIONS_SSH_DIR, which is already used
to reference any other files in used home directory.

Fixes #93

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
This commit is contained in:
Jakub Jelen
2022-02-04 16:14:21 +01:00
parent ded3a17738
commit 4975487c18
2 changed files with 110 additions and 10 deletions

View File

@@ -191,7 +191,7 @@ static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[]
};
static int ssh_config_parse_line(ssh_session session, const char *line,
unsigned int count, int *parsing, unsigned int depth);
unsigned int count, int *parsing, unsigned int depth, bool global);
static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
int i;
@@ -210,7 +210,8 @@ static void
local_parse_file(ssh_session session,
const char *filename,
int *parsing,
unsigned int depth)
unsigned int depth,
bool global)
{
FILE *f;
char line[MAX_LINE_SIZE] = {0};
@@ -234,7 +235,7 @@ local_parse_file(ssh_session session,
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, depth);
rv = ssh_config_parse_line(session, line, count, parsing, depth, global);
if (rv < 0) {
fclose(f);
return;
@@ -249,7 +250,8 @@ local_parse_file(ssh_session session,
static void local_parse_glob(ssh_session session,
const char *fileglob,
int *parsing,
unsigned int depth)
unsigned int depth,
bool global)
{
glob_t globbuf = {
.gl_flags = 0,
@@ -269,7 +271,7 @@ static void local_parse_glob(ssh_session session,
}
for (i = 0; i < globbuf.gl_pathc; i++) {
local_parse_file(session, globbuf.gl_pathv[i], parsing, depth);
local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global);
}
globfree(&globbuf);
@@ -519,12 +521,59 @@ out:
return rv;
}
static char *
ssh_config_make_absolute(ssh_session session,
const char *path,
bool global)
{
size_t outlen = 0;
char *out = NULL;
int rv;
/* Looks like absolute path */
if (path[0] == '/') {
return strdup(path);
}
/* relative path */
if (global) {
/* Parsing global config */
outlen = strlen(path) + strlen("/etc/ssh/") + 1;
out = malloc(outlen);
if (out == NULL) {
ssh_set_error_oom(session);
return NULL;
}
rv = snprintf(out, outlen, "/etc/ssh/%s", path);
if (rv < 1) {
free(out);
return NULL;
}
return out;
}
/* Parsing user config relative to home directory (generally ~/.ssh) */
outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1;
out = malloc(outlen);
if (out == NULL) {
ssh_set_error_oom(session);
return NULL;
}
rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path);
if (rv < 1) {
free(out);
return NULL;
}
return out;
}
static int
ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth)
unsigned int depth,
bool global)
{
enum ssh_config_opcode_e opcode;
const char *p = NULL, *p2 = NULL;
@@ -583,11 +632,19 @@ ssh_config_parse_line(ssh_session session,
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
char *path = ssh_config_make_absolute(session, p, global);
if (path == NULL) {
SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory "
"for the include path expansion", count);
SAFE_FREE(x);
return -1;
}
#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER)
local_parse_glob(session, p, parsing, depth + 1);
local_parse_glob(session, path, parsing, depth + 1, global);
#else
local_parse_file(session, p, parsing, depth + 1);
local_parse_file(session, path, parsing, depth + 1, global);
#endif /* HAVE_GLOB */
free(path);
}
break;
@@ -1163,18 +1220,24 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
unsigned int count = 0;
FILE *f;
int parsing, rv;
bool global = 0;
f = fopen(filename, "r");
if (f == NULL) {
return 0;
}
rv = strcmp(filename, GLOBAL_CLIENT_CONFIG);
if (rv == 0) {
global = true;
}
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, 0);
rv = ssh_config_parse_line(session, line, count, &parsing, 0, global);
if (rv < 0) {
fclose(f);
return -1;
@@ -1226,7 +1289,7 @@ int ssh_config_parse_string(ssh_session session, const char *input)
memcpy(line, line_start, line_len);
line[line_len] = '\0';
SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line);
rv = ssh_config_parse_line(session, line, line_num, &parsing, 0);
rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false);
if (rv < 0) {
return SSH_ERROR;
}

View File

@@ -7,6 +7,7 @@
#include "libssh/session.h"
#include "libssh/config_parser.h"
#include "match.c"
#include "config.c"
extern LIBSSH_THREAD int ssh_log_level;
@@ -310,12 +311,16 @@ static int teardown_config_files(void **state)
static int setup(void **state)
{
ssh_session session = NULL;
char *wd = NULL;
int verbosity;
session = ssh_new();
verbosity = torture_libssh_verbosity();
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
wd = torture_get_current_working_dir();
ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd);
free(wd);
*state = session;
@@ -1659,6 +1664,36 @@ static void torture_config_identity(void **state)
assert_string_equal(id, "id_rsa_one");
}
/* Make absolute path for config include
*/
static void torture_config_make_absolute(void **state)
{
ssh_session session = *state;
char *result = NULL;
/* Absolute path already -- should not change in any case */
result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1);
assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf");
free(result);
result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0);
assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf");
free(result);
/* Global is relative to /etc/ssh/ */
result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1);
assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf");
free(result);
result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1);
assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf");
free(result);
/* User config is relative to sshdir -- here faked to /tmp/ssh/ */
ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh");
result = ssh_config_make_absolute(session, "my_config", 0);
assert_string_equal(result, "/tmp/ssh/my_config");
free(result);
}
int torture_run_tests(void)
{
@@ -1724,6 +1759,8 @@ int torture_run_tests(void)
setup, teardown),
cmocka_unit_test_setup_teardown(torture_config_identity,
setup, teardown),
cmocka_unit_test_setup_teardown(torture_config_make_absolute,
setup, teardown),
};