diff --git a/src/config.c b/src/config.c index 7ff270ff..2496cb5c 100644 --- a/src/config.c +++ b/src/config.c @@ -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; } diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index ee070f62..8afbe143 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -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), };