Add support for %include funcionality on torrc #1922

config_get_lines is now split into two functions:
 - config_get_lines which is the same as before we had %include
 - config_get_lines_include which actually processes %include
This commit is contained in:
Daniel Pinto 2017-05-18 23:44:16 +01:00
parent ec6b2bbf9b
commit ba3a5f82f1
13 changed files with 908 additions and 24 deletions

11
changes/feature1922 Normal file
View file

@ -0,0 +1,11 @@
o Minor feature (include on config files):
- Allow the use of %include on configuration files to include settings
from other files or directories. Using %include with a directory will
include all (non-dot) files in that directory in lexically sorted order
(non-recursive), closes ticket 1922.
- Makes SAVECONF command return error when overwriting a torrc
that has includes. Using SAVECONF with the FORCE option will
allow it to overwrite torrc even if includes are used, closes ticket
1922.
- Adds config-can-saveconf to GETINFO command to tell if SAVECONF
will work without the FORCE option, closes ticket 1922.

View file

@ -153,6 +153,13 @@ values. To split one configuration entry into multiple lines, use a single
backslash character (\) before the end of the line. Comments can be used in backslash character (\) before the end of the line. Comments can be used in
such multiline entries, but they must start at the beginning of a line. such multiline entries, but they must start at the beginning of a line.
Configuration options can be imported from files or folders using the %include
option with the value being a path. If the path is a file, the options from the
file will be parsed as if they were written where the %include option is. If
the path is a folder, all files on that folder will be parsed following lexical
order. Files starting with a dot are ignored. Files on subfolders are ignored.
The %include option can be used recursively.
By default, an option on the command line overrides an option found in the By default, an option on the command line overrides an option found in the
configuration file, and an option in a configuration file overrides one in configuration file, and an option in a configuration file overrides one in
the defaults file. the defaults file.

View file

@ -8,6 +8,19 @@
#include "confline.h" #include "confline.h"
#include "torlog.h" #include "torlog.h"
#include "util.h" #include "util.h"
#include "container.h"
static int config_get_lines_aux(const char *string, config_line_t **result,
int extended, int allow_include,
int *has_include, int recursion_level,
config_line_t **last);
static smartlist_t *config_get_file_list(const char *path);
static int config_get_included_list(const char *path, int recursion_level,
int extended, config_line_t **list,
config_line_t **list_last);
static int config_process_include(const char *path, int recursion_level,
int extended, config_line_t ***next,
config_line_t **list_last);
/** Helper: allocate a new configuration option mapping 'key' to 'val', /** Helper: allocate a new configuration option mapping 'key' to 'val',
* append it to *<b>lst</b>. */ * append it to *<b>lst</b>. */
@ -65,19 +78,25 @@ config_line_find(const config_line_t *lines,
return NULL; return NULL;
} }
/** Helper: parse the config string and strdup into key/value /** Auxiliary function that does all the work of config_get_lines.
* strings. Set *result to the list, or NULL if parsing the string * <b>recursion_level</b> is the count of how many nested %includes we have.
* failed. Return 0 on success, -1 on failure. Warn and ignore any * Returns the a pointer to the last element of the <b>result</b> in
* misformatted lines. * <b>last</b>. */
* static int
* If <b>extended</b> is set, then treat keys beginning with / and with + as config_get_lines_aux(const char *string, config_line_t **result, int extended,
* indicating "clear" and "append" respectively. */ int allow_include, int *has_include, int recursion_level,
int config_line_t **last)
config_get_lines(const char *string, config_line_t **result, int extended)
{ {
config_line_t *list = NULL, **next; config_line_t *list = NULL, **next, *list_last = NULL;
char *k, *v; char *k, *v;
const char *parse_err; const char *parse_err;
int include_used = 0;
if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
"nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
return -1;
}
next = &list; next = &list;
do { do {
@ -108,25 +127,179 @@ config_get_lines(const char *string, config_line_t **result, int extended)
command = CONFIG_LINE_CLEAR; command = CONFIG_LINE_CLEAR;
} }
} }
if (allow_include && !strcmp(k, "%include")) {
tor_free(k);
include_used = 1;
if (config_process_include(v, recursion_level, extended, &next,
&list_last) < 0) {
log_warn(LD_CONFIG, "Error reading included configuration "
"file or directory: \"%s\".", v);
config_free_lines(list);
tor_free(v);
return -1;
}
tor_free(v);
} else {
/* This list can get long, so we keep a pointer to the end of it /* This list can get long, so we keep a pointer to the end of it
* rather than using config_line_append over and over and getting * rather than using config_line_append over and over and getting
* n^2 performance. */ * n^2 performance. */
*next = tor_malloc_zero(sizeof(config_line_t)); *next = tor_malloc_zero(sizeof(**next));
(*next)->key = k; (*next)->key = k;
(*next)->value = v; (*next)->value = v;
(*next)->next = NULL; (*next)->next = NULL;
(*next)->command = command; (*next)->command = command;
list_last = *next;
next = &((*next)->next); next = &((*next)->next);
}
} else { } else {
tor_free(k); tor_free(k);
tor_free(v); tor_free(v);
} }
} while (*string); } while (*string);
if (last) {
*last = list_last;
}
if (has_include) {
*has_include = include_used;
}
*result = list; *result = list;
return 0; return 0;
} }
/** Helper: parse the config string and strdup into key/value
* strings. Set *result to the list, or NULL if parsing the string
* failed. Set *has_include to 1 if <b>result</b> has values from
* %included files. Return 0 on success, -1 on failure. Warn and ignore any
* misformatted lines.
*
* If <b>extended</b> is set, then treat keys beginning with / and with + as
* indicating "clear" and "append" respectively. */
int
config_get_lines_include(const char *string, config_line_t **result,
int extended, int *has_include)
{
return config_get_lines_aux(string, result, extended, 1, has_include, 1,
NULL);
}
/** Same as config_get_lines_include but does not allow %include */
int
config_get_lines(const char *string, config_line_t **result, int extended)
{
return config_get_lines_aux(string, result, extended, 0, NULL, 1, NULL);
}
/** Adds a list of configuration files present on <b>path</b> to
* <b>file_list</b>. <b>path</b> can be a file or a directory. If it is a file,
* only that file will be added to <b>file_list</b>. If it is a directory,
* all paths for files on that directory root (no recursion) except for files
* whose name starts with a dot will be added to <b>file_list</b>.
* Return 0 on success, -1 on failure. Ignores empty files.
*/
static smartlist_t *
config_get_file_list(const char *path)
{
smartlist_t *file_list = smartlist_new();
file_status_t file_type = file_status(path);
if (file_type == FN_FILE) {
smartlist_add_strdup(file_list, path);
return file_list;
} else if (file_type == FN_DIR) {
smartlist_t *all_files = tor_listdir(path);
if (!all_files) {
smartlist_free(file_list);
return NULL;
}
smartlist_sort_strings(all_files);
SMARTLIST_FOREACH_BEGIN(all_files, char *, f) {
if (f[0] == '.') {
tor_free(f);
continue;
}
char *fullname;
tor_asprintf(&fullname, "%s"PATH_SEPARATOR"%s", path, f);
tor_free(f);
if (file_status(fullname) != FN_FILE) {
tor_free(fullname);
continue;
}
smartlist_add(file_list, fullname);
} SMARTLIST_FOREACH_END(f);
smartlist_free(all_files);
return file_list;
} else if (file_type == FN_EMPTY) {
return file_list;
} else {
smartlist_free(file_list);
return NULL;
}
}
/** Creates a list of config lines present on included <b>path</b>.
* Set <b>list</b> to the list and <b>list_last</b> to the last element of
* <b>list</b>. Return 0 on success, -1 on failure. */
static int
config_get_included_list(const char *path, int recursion_level, int extended,
config_line_t **list, config_line_t **list_last)
{
char *included_conf = read_file_to_str(path, 0, NULL);
if (!included_conf) {
return -1;
}
if (config_get_lines_aux(included_conf, list, extended, 1, NULL,
recursion_level+1, list_last) < 0) {
tor_free(included_conf);
return -1;
}
tor_free(included_conf);
return 0;
}
/** Process an %include <b>path</b> in a config file. Set <b>next</b> to a
* pointer to the next pointer of the last element of the config_line_t list
* obtained from the config file and <b>list_last</b> to the last element of
* the same list. Return 0 on success, -1 on failure. */
static int
config_process_include(const char *path, int recursion_level, int extended,
config_line_t ***next, config_line_t **list_last)
{
char *unquoted_path = get_unquoted_path(path);
if (!unquoted_path) {
return -1;
}
smartlist_t *config_files = config_get_file_list(unquoted_path);
if (!config_files) {
tor_free(unquoted_path);
return -1;
}
tor_free(unquoted_path);
SMARTLIST_FOREACH_BEGIN(config_files, char *, config_file) {
config_line_t *included_list = NULL;
if (config_get_included_list(config_file, recursion_level, extended,
&included_list, list_last) < 0) {
SMARTLIST_FOREACH(config_files, char *, f, tor_free(f));
smartlist_free(config_files);
return -1;
}
tor_free(config_file);
**next = included_list;
*next = &(*list_last)->next;
} SMARTLIST_FOREACH_END(config_file);
smartlist_free(config_files);
return 0;
}
/** /**
* Free all the configuration lines on the linked list <b>front</b>. * Free all the configuration lines on the linked list <b>front</b>.
*/ */

View file

@ -15,6 +15,8 @@
/* Removes all previous configuration for an option. */ /* Removes all previous configuration for an option. */
#define CONFIG_LINE_CLEAR 2 #define CONFIG_LINE_CLEAR 2
#define MAX_INCLUDE_RECURSION_LEVEL 31
/** A linked list of lines in a config file, or elsewhere */ /** A linked list of lines in a config file, or elsewhere */
typedef struct config_line_t { typedef struct config_line_t {
char *key; char *key;
@ -41,6 +43,8 @@ const config_line_t *config_line_find(const config_line_t *lines,
int config_lines_eq(config_line_t *a, config_line_t *b); int config_lines_eq(config_line_t *a, config_line_t *b);
int config_count_key(const config_line_t *a, const char *key); int config_count_key(const config_line_t *a, const char *key);
int config_get_lines(const char *string, config_line_t **result, int extended); int config_get_lines(const char *string, config_line_t **result, int extended);
int config_get_lines_include(const char *string, config_line_t **result,
int extended, int *has_include);
void config_free_lines(config_line_t *front); void config_free_lines(config_line_t *front);
const char *parse_config_line_from_str_verbose(const char *line, const char *parse_config_line_from_str_verbose(const char *line,
char **key_out, char **value_out, char **key_out, char **value_out,

View file

@ -3045,6 +3045,41 @@ unescape_string(const char *s, char **result, size_t *size_out)
} }
} }
/** Removes enclosing quotes from <b>path</b> and unescapes quotes between the
* enclosing quotes. Backslashes are not unescaped. Return the unquoted
* <b>path</b> on sucess or 0 if <b>path</b> is not quoted correctly. */
char *
get_unquoted_path(const char *path)
{
int len = strlen(path);
if (len == 0) {
return tor_strdup("");
}
int has_start_quote = (path[0] == '\"');
int has_end_quote = (len > 0 && path[len-1] == '\"');
if (has_start_quote != has_end_quote || (len == 1 && has_start_quote)) {
return NULL;
}
char *unquoted_path = tor_malloc(len - has_start_quote - has_end_quote + 1);
char *s = unquoted_path;
int i;
for (i = has_start_quote; i < len - has_end_quote; i++) {
if (path[i] == '\"' && (i > 0 && path[i-1] == '\\')) {
*(s-1) = path[i];
} else if (path[i] != '\"') {
*s++ = path[i];
} else { /* unescaped quote */
tor_free(unquoted_path);
return NULL;
}
}
*s = '\0';
return unquoted_path;
}
/** Expand any homedir prefix on <b>filename</b>; return a newly allocated /** Expand any homedir prefix on <b>filename</b>; return a newly allocated
* string. */ * string. */
char * char *

View file

@ -389,6 +389,7 @@ char *read_file_to_str_until_eof(int fd, size_t max_bytes_to_read,
size_t *sz_out) size_t *sz_out)
ATTR_MALLOC; ATTR_MALLOC;
const char *unescape_string(const char *s, char **result, size_t *size_out); const char *unescape_string(const char *s, char **result, size_t *size_out);
char *get_unquoted_path(const char *path);
char *expand_filename(const char *filename); char *expand_filename(const char *filename);
MOCK_DECL(struct smartlist_t *, tor_listdir, (const char *dirname)); MOCK_DECL(struct smartlist_t *, tor_listdir, (const char *dirname));
int path_is_relative(const char *filename); int path_is_relative(const char *filename);

View file

@ -209,3 +209,12 @@
## address manually to your friends, uncomment this line: ## address manually to your friends, uncomment this line:
#PublishServerDescriptor 0 #PublishServerDescriptor 0
## Configuration options can be imported from files or folders using the %include
## option with the value being a path. If the path is a file, the options from the
## file will be parsed as if they were written where the %include option is. If
## the path is a folder, all files on that folder will be parsed following lexical
## order. Files starting with a dot are ignored. Files on subfolders are ignored.
## The %include option can be used recursively.
#%include /etc/torrc.d/
#%include /etc/torrc.custom

View file

@ -5056,6 +5056,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
config_line_t *cl; config_line_t *cl;
int retval; int retval;
setopt_err_t err = SETOPT_ERR_MISC; setopt_err_t err = SETOPT_ERR_MISC;
int cf_has_include;
tor_assert(msg); tor_assert(msg);
oldoptions = global_options; /* get_options unfortunately asserts if oldoptions = global_options; /* get_options unfortunately asserts if
@ -5072,7 +5073,8 @@ options_init_from_string(const char *cf_defaults, const char *cf,
if (!body) if (!body)
continue; continue;
/* get config lines, assign them */ /* get config lines, assign them */
retval = config_get_lines(body, &cl, 1); retval = config_get_lines_include(body, &cl, 1,
body == cf ? &cf_has_include : NULL);
if (retval < 0) { if (retval < 0) {
err = SETOPT_ERR_PARSE; err = SETOPT_ERR_PARSE;
goto err; goto err;
@ -5100,6 +5102,8 @@ options_init_from_string(const char *cf_defaults, const char *cf,
goto err; goto err;
} }
newoptions->IncludeUsed = cf_has_include;
/* If this is a testing network configuration, change defaults /* If this is a testing network configuration, change defaults
* for a list of dependent config options, re-initialize newoptions * for a list of dependent config options, re-initialize newoptions
* with the new defaults, and assign all options to it second time. */ * with the new defaults, and assign all options to it second time. */
@ -5143,7 +5147,8 @@ options_init_from_string(const char *cf_defaults, const char *cf,
if (!body) if (!body)
continue; continue;
/* get config lines, assign them */ /* get config lines, assign them */
retval = config_get_lines(body, &cl, 1); retval = config_get_lines_include(body, &cl, 1,
body == cf ? &cf_has_include : NULL);
if (retval < 0) { if (retval < 0) {
err = SETOPT_ERR_PARSE; err = SETOPT_ERR_PARSE;
goto err; goto err;
@ -5166,6 +5171,8 @@ options_init_from_string(const char *cf_defaults, const char *cf,
} }
} }
newoptions->IncludeUsed = cf_has_include;
/* Validate newoptions */ /* Validate newoptions */
if (options_validate(oldoptions, newoptions, newdefaultoptions, if (options_validate(oldoptions, newoptions, newdefaultoptions,
0, msg) < 0) { 0, msg) < 0) {

View file

@ -124,7 +124,6 @@ const char *config_find_deprecation(const config_format_t *fmt,
const char *key); const char *key);
const config_var_t *config_find_option(const config_format_t *fmt, const config_var_t *config_find_option(const config_format_t *fmt,
const char *key); const char *key);
const char *config_expand_abbrev(const config_format_t *fmt, const char *config_expand_abbrev(const config_format_t *fmt,
const char *option, const char *option,
int command_line, int warn_obsolete); int command_line, int warn_obsolete);

View file

@ -1462,8 +1462,10 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len,
const char *body) const char *body)
{ {
(void) len; (void) len;
(void) body;
if (options_save_current()<0) { int force = !strcmpstart(body, "FORCE");
const or_options_t *options = get_options();
if ((!force && options->IncludeUsed) || options_save_current() < 0) {
connection_write_str_to_buf( connection_write_str_to_buf(
"551 Unable to write configuration to disk.\r\n", conn); "551 Unable to write configuration to disk.\r\n", conn);
} else { } else {
@ -1677,6 +1679,8 @@ getinfo_helper_misc(control_connection_t *conn, const char *question,
*answer = tor_strdup(a); *answer = tor_strdup(a);
} else if (!strcmp(question, "config-text")) { } else if (!strcmp(question, "config-text")) {
*answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL); *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
} else if (!strcmp(question, "config-can-saveconf")) {
*answer = tor_strdup(get_options()->IncludeUsed ? "0" : "1");
} else if (!strcmp(question, "info/names")) { } else if (!strcmp(question, "info/names")) {
*answer = list_getinfo_options(); *answer = list_getinfo_options();
} else if (!strcmp(question, "dormant")) { } else if (!strcmp(question, "dormant")) {
@ -2931,6 +2935,8 @@ static const getinfo_item_t getinfo_items[] = {
ITEM("config-defaults-file", misc, "Current location of the defaults file."), ITEM("config-defaults-file", misc, "Current location of the defaults file."),
ITEM("config-text", misc, ITEM("config-text", misc,
"Return the string that would be written by a saveconf command."), "Return the string that would be written by a saveconf command."),
ITEM("config-can-saveconf", misc,
"Is it possible to save the configuration to the \"torrc\" file?"),
ITEM("accounting/bytes", accounting, ITEM("accounting/bytes", accounting,
"Number of bytes read/written so far in the accounting interval."), "Number of bytes read/written so far in the accounting interval."),
ITEM("accounting/bytes-left", accounting, ITEM("accounting/bytes-left", accounting,

View file

@ -4549,6 +4549,9 @@ typedef struct {
* do we enforce Ed25519 identity match? */ * do we enforce Ed25519 identity match? */
/* NOTE: remove this option someday. */ /* NOTE: remove this option someday. */
int AuthDirTestEd25519LinkKeys; int AuthDirTestEd25519LinkKeys;
/** Bool (default: 0): Tells if a %include was used on torrc */
int IncludeUsed;
} or_options_t; } or_options_t;
/** Persistent state for an onion router, as saved to disk. */ /** Persistent state for an onion router, as saved to disk. */

View file

@ -4810,6 +4810,542 @@ test_config_parse_log_severity(void *data)
tor_free(severity); tor_free(severity);
} }
static void
test_config_include_limit(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_limit"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrc_path[PATH_MAX+1];
tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s",
torrc_path);
tt_int_op(write_str_to_file(torrc_path, torrc_contents, 0), OP_EQ, 0);
config_line_t *result = NULL;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL),
OP_EQ, -1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_does_not_exist(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_does_not_exist"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char missing_path[PATH_MAX+1];
tor_snprintf(missing_path, sizeof(missing_path), "%s"PATH_SEPARATOR"missing",
dir);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s",
missing_path);
config_line_t *result = NULL;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL),
OP_EQ, -1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_error_in_included_file(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_error_in_included_file"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char invalid_path[PATH_MAX+1];
tor_snprintf(invalid_path, sizeof(invalid_path), "%s"PATH_SEPARATOR"invalid",
dir);
tt_int_op(write_str_to_file(invalid_path, "unclosed \"", 0), OP_EQ, 0);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s",
invalid_path);
config_line_t *result = NULL;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0, NULL),
OP_EQ, -1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_empty_file_folder(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_empty_file_folder"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char folder_path[PATH_MAX+1];
tor_snprintf(folder_path, sizeof(folder_path), "%s"PATH_SEPARATOR"empty_dir",
dir);
#ifdef _WIN32
tt_int_op(mkdir(folder_path), OP_EQ, 0);
#else
tt_int_op(mkdir(folder_path, 0700), OP_EQ, 0);
#endif
char file_path[PATH_MAX+1];
tor_snprintf(file_path, sizeof(file_path), "%s"PATH_SEPARATOR"empty_file",
dir);
tt_int_op(write_str_to_file(file_path, "", 0), OP_EQ, 0);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents),
"%%include %s\n"
"%%include %s\n",
folder_path, file_path);
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used),
OP_EQ, 0);
tt_ptr_op(result, OP_EQ, NULL);
tt_int_op(include_used, OP_EQ, 1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_recursion_before_after(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_recursion_before_after"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrc_path[PATH_MAX+1];
tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
char file_contents[1000];
const int limit = MAX_INCLUDE_RECURSION_LEVEL;
int i;
// Loop backwards so file_contents has the contents of the first file by the
// end of the loop
for (i = limit; i > 0; i--) {
if (i < limit) {
tor_snprintf(file_contents, sizeof(file_contents),
"Test %d\n"
"%%include %s%d\n"
"Test %d\n",
i, torrc_path, i + 1, 2 * limit - i);
} else {
tor_snprintf(file_contents, sizeof(file_contents), "Test %d\n", i);
}
if (i > 1) {
char file_path[PATH_MAX+1];
tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i);
tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0);
}
}
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used),
OP_EQ, 0);
tt_ptr_op(result, OP_NE, NULL);
tt_int_op(include_used, OP_EQ, 1);
int len = 0;
config_line_t *next;
for (next = result; next != NULL; next = next->next) {
char expected[10];
tor_snprintf(expected, sizeof(expected), "%d", len + 1);
tt_str_op(next->key, OP_EQ, "Test");
tt_str_op(next->value, OP_EQ, expected);
len++;
}
tt_int_op(len, OP_EQ, 2 * limit - 1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_recursion_after_only(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_recursion_after_only"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrc_path[PATH_MAX+1];
tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
char file_contents[1000];
const int limit = MAX_INCLUDE_RECURSION_LEVEL;
int i;
// Loop backwards so file_contents has the contents of the first file by the
// end of the loop
for (i = limit; i > 0; i--) {
int n = (i - limit - 1) * -1;
if (i < limit) {
tor_snprintf(file_contents, sizeof(file_contents),
"%%include %s%d\n"
"Test %d\n",
torrc_path, i + 1, n);
} else {
tor_snprintf(file_contents, sizeof(file_contents), "Test %d\n", n);
}
if (i > 1) {
char file_path[PATH_MAX+1];
tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i);
tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0);
}
}
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(file_contents, &result, 0, &include_used),
OP_EQ, 0);
tt_ptr_op(result, OP_NE, NULL);
tt_int_op(include_used, OP_EQ, 1);
int len = 0;
config_line_t *next;
for (next = result; next != NULL; next = next->next) {
char expected[10];
tor_snprintf(expected, sizeof(expected), "%d", len + 1);
tt_str_op(next->key, OP_EQ, "Test");
tt_str_op(next->value, OP_EQ, expected);
len++;
}
tt_int_op(len, OP_EQ, limit);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_folder_order(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_folder_order"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrcd[PATH_MAX+1];
tor_snprintf(torrcd, sizeof(torrcd), "%s"PATH_SEPARATOR"%s", dir, "torrc.d");
#ifdef _WIN32
tt_int_op(mkdir(torrcd), OP_EQ, 0);
#else
tt_int_op(mkdir(torrcd, 0700), OP_EQ, 0);
#endif
// test that files in subfolders are ignored
char path[PATH_MAX+1];
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd,
"subfolder");
#ifdef _WIN32
tt_int_op(mkdir(path), OP_EQ, 0);
#else
tt_int_op(mkdir(path, 0700), OP_EQ, 0);
#endif
char path2[PATH_MAX+1];
tor_snprintf(path2, sizeof(path2), "%s"PATH_SEPARATOR"%s", path,
"01_ignore");
tt_int_op(write_str_to_file(path2, "ShouldNotSee 1\n", 0), OP_EQ, 0);
// test that files starting with . are ignored
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, ".dot");
tt_int_op(write_str_to_file(path, "ShouldNotSee 2\n", 0), OP_EQ, 0);
// test file order
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "01_1st");
tt_int_op(write_str_to_file(path, "Test 1\n", 0), OP_EQ, 0);
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd");
tt_int_op(write_str_to_file(path, "Test 2\n", 0), OP_EQ, 0);
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd");
tt_int_op(write_str_to_file(path, "Test 3\n", 0), OP_EQ, 0);
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th");
tt_int_op(write_str_to_file(path, "Test 4\n", 0), OP_EQ, 0);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents),
"%%include %s\n",
torrcd);
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used),
OP_EQ, 0);
tt_ptr_op(result, OP_NE, NULL);
tt_int_op(include_used, OP_EQ, 1);
int len = 0;
config_line_t *next;
for (next = result; next != NULL; next = next->next) {
char expected[10];
tor_snprintf(expected, sizeof(expected), "%d", len + 1);
tt_str_op(next->key, OP_EQ, "Test");
tt_str_op(next->value, OP_EQ, expected);
len++;
}
tt_int_op(len, OP_EQ, 4);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_path_syntax(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_path_syntax"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents),
"%%include \"%s\"\n"
"%%include %s"PATH_SEPARATOR"\n"
"%%include \"%s"PATH_SEPARATOR"\"\n",
dir, dir, dir);
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used),
OP_EQ, 0);
tt_ptr_op(result, OP_EQ, NULL);
tt_int_op(include_used, OP_EQ, 1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_not_processed(void *data)
{
(void)data;
char torrc_contents[1000] = "%include does_not_exist\n";
config_line_t *result = NULL;
tt_int_op(config_get_lines(torrc_contents, &result, 0),OP_EQ, 0);
tt_ptr_op(result, OP_NE, NULL);
int len = 0;
config_line_t *next;
for (next = result; next != NULL; next = next->next) {
tt_str_op(next->key, OP_EQ, "%include");
tt_str_op(next->value, OP_EQ, "does_not_exist");
len++;
}
tt_int_op(len, OP_EQ, 1);
done:
config_free_lines(result);
}
static void
test_config_include_has_include(void *data)
{
(void)data;
char *dir = tor_strdup(get_fname("test_include_has_include"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char torrc_contents[1000] = "Test 1\n";
config_line_t *result = NULL;
int include_used;
tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used),
OP_EQ, 0);
tt_int_op(include_used, OP_EQ, 0);
config_free_lines(result);
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s\n", dir);
tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used),
OP_EQ, 0);
tt_int_op(include_used, OP_EQ, 1);
done:
config_free_lines(result);
tor_free(dir);
}
static void
test_config_include_flag_both_without(void *data)
{
(void)data;
char *errmsg = NULL;
char conf_empty[1000];
tor_snprintf(conf_empty, sizeof(conf_empty),
"DataDirectory %s\n",
get_fname(NULL));
// test with defaults-torrc and torrc without include
int ret = options_init_from_string(conf_empty, conf_empty, CMD_RUN_UNITTESTS,
NULL, &errmsg);
tt_int_op(ret, OP_EQ, 0);
const or_options_t *options = get_options();
tt_int_op(options->IncludeUsed, OP_EQ, 0);
done:
tor_free(errmsg);
}
static void
test_config_include_flag_torrc_only(void *data)
{
(void)data;
char *errmsg = NULL;
char *dir = tor_strdup(get_fname("test_include_flag_torrc_only"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char path[PATH_MAX+1];
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy");
tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0);
char conf_empty[1000];
tor_snprintf(conf_empty, sizeof(conf_empty),
"DataDirectory %s\n",
get_fname(NULL));
char conf_include[1000];
tor_snprintf(conf_include, sizeof(conf_include), "%%include %s", path);
// test with defaults-torrc without include and torrc with include
int ret = options_init_from_string(conf_empty, conf_include,
CMD_RUN_UNITTESTS, NULL, &errmsg);
tt_int_op(ret, OP_EQ, 0);
const or_options_t *options = get_options();
tt_int_op(options->IncludeUsed, OP_EQ, 1);
done:
tor_free(errmsg);
tor_free(dir);
}
static void
test_config_include_flag_defaults_only(void *data)
{
(void)data;
char *errmsg = NULL;
char *dir = tor_strdup(get_fname("test_include_flag_defaults_only"));
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
tt_int_op(mkdir(dir), OP_EQ, 0);
#else
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
char path[PATH_MAX+1];
tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy");
tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0);
char conf_empty[1000];
tor_snprintf(conf_empty, sizeof(conf_empty),
"DataDirectory %s\n",
get_fname(NULL));
char conf_include[1000];
tor_snprintf(conf_include, sizeof(conf_include), "%%include %s", path);
// test with defaults-torrc with include and torrc without include
int ret = options_init_from_string(conf_include, conf_empty,
CMD_RUN_UNITTESTS, NULL, &errmsg);
tt_int_op(ret, OP_EQ, 0);
const or_options_t *options = get_options();
tt_int_op(options->IncludeUsed, OP_EQ, 0);
done:
tor_free(errmsg);
tor_free(dir);
}
#define CONFIG_TEST(name, flags) \ #define CONFIG_TEST(name, flags) \
{ #name, test_config_ ## name, flags, NULL, NULL } { #name, test_config_ ## name, flags, NULL, NULL }
@ -4836,6 +5372,19 @@ struct testcase_t config_tests[] = {
CONFIG_TEST(parse_port_config__ports__server_options, 0), CONFIG_TEST(parse_port_config__ports__server_options, 0),
CONFIG_TEST(parse_port_config__ports__ports_given, 0), CONFIG_TEST(parse_port_config__ports__ports_given, 0),
CONFIG_TEST(parse_log_severity, 0), CONFIG_TEST(parse_log_severity, 0),
CONFIG_TEST(include_limit, 0),
CONFIG_TEST(include_does_not_exist, 0),
CONFIG_TEST(include_error_in_included_file, 0),
CONFIG_TEST(include_empty_file_folder, 0),
CONFIG_TEST(include_recursion_before_after, 0),
CONFIG_TEST(include_recursion_after_only, 0),
CONFIG_TEST(include_folder_order, 0),
CONFIG_TEST(include_path_syntax, 0),
CONFIG_TEST(include_not_processed, 0),
CONFIG_TEST(include_has_include, 0),
CONFIG_TEST(include_flag_both_without, TT_FORK),
CONFIG_TEST(include_flag_torrc_only, TT_FORK),
CONFIG_TEST(include_flag_defaults_only, TT_FORK),
END_OF_TESTCASES END_OF_TESTCASES
}; };

View file

@ -5731,6 +5731,85 @@ test_util_htonll(void *arg)
; ;
} }
static void
test_util_get_unquoted_path(void *arg)
{
(void)arg;
char *r;
r = get_unquoted_path("\""); // "
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("\"\"\""); // """
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("\\\""); // \"
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("\\\"\\\""); // \"\"
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("A\\B\\C\""); // A\B\C"
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("\"A\\B\\C"); // "A\B\C
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("\"A\\B\"C\""); // "A\B"C"
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("A\\B\"C"); // A\B"C
tt_ptr_op(r, OP_EQ, NULL);
tor_free(r);
r = get_unquoted_path("");
tt_str_op(r, OP_EQ, "");
tor_free(r);
r = get_unquoted_path("\"\""); // ""
tt_str_op(r, OP_EQ, "");
tor_free(r);
r = get_unquoted_path("A\\B\\C"); // A\B\C
tt_str_op(r, OP_EQ, "A\\B\\C"); // A\B\C
tor_free(r);
r = get_unquoted_path("\"A\\B\\C\""); // "A\B\C"
tt_str_op(r, OP_EQ, "A\\B\\C"); // A\B\C
tor_free(r);
r = get_unquoted_path("\"\\\""); // "\"
tt_str_op(r, OP_EQ, "\\"); // \ /* comment to prevent line continuation */
tor_free(r);
r = get_unquoted_path("\"\\\"\""); // "\""
tt_str_op(r, OP_EQ, "\""); // "
tor_free(r);
r = get_unquoted_path("\"A\\B\\C\\\"\""); // "A\B\C\""
tt_str_op(r, OP_EQ, "A\\B\\C\""); // A\B\C"
tor_free(r);
r = get_unquoted_path("A\\B\\\"C"); // A\B\"C
tt_str_op(r, OP_EQ, "A\\B\"C"); // A\B"C
tor_free(r);
r = get_unquoted_path("\"A\\B\\\"C\""); // "A\B\"C"
tt_str_op(r, OP_EQ, "A\\B\"C"); // A\B"C
tor_free(r);
done:
;
}
#define UTIL_LEGACY(name) \ #define UTIL_LEGACY(name) \
{ #name, test_util_ ## name , 0, NULL, NULL } { #name, test_util_ ## name , 0, NULL, NULL }
@ -5833,6 +5912,7 @@ struct testcase_t util_tests[] = {
UTIL_TEST(monotonic_time, 0), UTIL_TEST(monotonic_time, 0),
UTIL_TEST(monotonic_time_ratchet, TT_FORK), UTIL_TEST(monotonic_time_ratchet, TT_FORK),
UTIL_TEST(htonll, 0), UTIL_TEST(htonll, 0),
UTIL_TEST(get_unquoted_path, 0),
END_OF_TESTCASES END_OF_TESTCASES
}; };