/* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "compat.h" #include "confline.h" #include "torlog.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, smartlist_t *opened_lst, int recursion_level, config_line_t **last); static smartlist_t *config_get_file_list(const char *path, smartlist_t *opened_files); static int config_get_included_config(const char *path, int recursion_level, int extended, config_line_t **config, config_line_t **config_last, smartlist_t *opened_lst); static int config_process_include(const char *path, int recursion_level, int extended, config_line_t **list, config_line_t **list_last, smartlist_t *opened_lst); /** Helper: allocate a new configuration option mapping 'key' to 'val', * append it to *lst. */ void config_line_append(config_line_t **lst, const char *key, const char *val) { tor_assert(lst); config_line_t *newline; newline = tor_malloc_zero(sizeof(config_line_t)); newline->key = tor_strdup(key); newline->value = tor_strdup(val); newline->next = NULL; while (*lst) lst = &((*lst)->next); (*lst) = newline; } /** Helper: allocate a new configuration option mapping 'key' to 'val', * and prepend it to *lst */ void config_line_prepend(config_line_t **lst, const char *key, const char *val) { tor_assert(lst); config_line_t *newline; newline = tor_malloc_zero(sizeof(config_line_t)); newline->key = tor_strdup(key); newline->value = tor_strdup(val); newline->next = *lst; *lst = newline; } /** Return the first line in lines whose key is exactly key, or * NULL if no such key exists. * * (In options parsing, this is for handling commandline-only options only; * other options should be looked up in the appropriate data structure.) */ const config_line_t * config_line_find(const config_line_t *lines, const char *key) { const config_line_t *cl; for (cl = lines; cl; cl = cl->next) { if (!strcmp(cl->key, key)) return cl; } return NULL; } /** Auxiliary function that does all the work of config_get_lines. * recursion_level is the count of how many nested %includes we have. * opened_lst will have a list of opened files if provided. * Returns the a pointer to the last element of the result in * last. */ static int config_get_lines_aux(const char *string, config_line_t **result, int extended, int allow_include, int *has_include, smartlist_t *opened_lst, int recursion_level, config_line_t **last) { config_line_t *list = NULL, **next, *list_last = NULL; char *k, *v; 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; do { k = v = NULL; string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err); if (!string) { log_warn(LD_CONFIG, "Error while parsing configuration: %s", parse_err?parse_err:""); config_free_lines(list); tor_free(k); tor_free(v); return -1; } if (k && v) { unsigned command = CONFIG_LINE_NORMAL; if (extended) { if (k[0] == '+') { char *k_new = tor_strdup(k+1); tor_free(k); k = k_new; command = CONFIG_LINE_APPEND; } else if (k[0] == '/') { char *k_new = tor_strdup(k+1); tor_free(k); k = k_new; tor_free(v); v = tor_strdup(""); command = CONFIG_LINE_CLEAR; } } if (allow_include && !strcmp(k, "%include")) { tor_free(k); include_used = 1; config_line_t *include_list; if (config_process_include(v, recursion_level, extended, &include_list, &list_last, opened_lst) < 0) { log_warn(LD_CONFIG, "Error reading included configuration " "file or directory: \"%s\".", v); config_free_lines(list); tor_free(v); return -1; } *next = include_list; if (list_last) next = &list_last->next; tor_free(v); } else { /* 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 * n^2 performance. */ *next = tor_malloc_zero(sizeof(**next)); (*next)->key = k; (*next)->value = v; (*next)->next = NULL; (*next)->command = command; list_last = *next; next = &((*next)->next); } } else { tor_free(k); tor_free(v); } } while (*string); if (last) { *last = list_last; } if (has_include) { *has_include = include_used; } *result = list; 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 result has values from * %included files. opened_lst will have a list of opened files if * provided. Return 0 on success, -1 on failure. Warn and ignore any * misformatted lines. * * If extended 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, smartlist_t *opened_lst) { return config_get_lines_aux(string, result, extended, 1, has_include, opened_lst, 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, NULL, 1, NULL); } /** Adds a list of configuration files present on path to * file_list. path can be a file or a directory. If it is a file, * only that file will be added to file_list. 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 file_list. * opened_files will have a list of files opened by this function * if provided. Return 0 on success, -1 on failure. Ignores empty files. */ static smartlist_t * config_get_file_list(const char *path, smartlist_t *opened_files) { smartlist_t *file_list = smartlist_new(); if (opened_files) { smartlist_add_strdup(opened_files, path); } 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 (opened_files) { smartlist_add_strdup(opened_files, fullname); } 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 path. * Set config to the list and config_last to the last element of * config. opened_lst will have a list of opened files if * provided. Return 0 on success, -1 on failure. */ static int config_get_included_config(const char *path, int recursion_level, int extended, config_line_t **config, config_line_t **config_last, smartlist_t *opened_lst) { char *included_conf = read_file_to_str(path, 0, NULL); if (!included_conf) { return -1; } if (config_get_lines_aux(included_conf, config, extended, 1, NULL, opened_lst, recursion_level+1, config_last) < 0) { tor_free(included_conf); return -1; } tor_free(included_conf); return 0; } /** Process an %include path in a config file. Set list to the * list of configuration settings obtained and list_last to the last * element of the same list. opened_lst will have a list of opened * files if provided. Return 0 on success, -1 on failure. */ static int config_process_include(const char *path, int recursion_level, int extended, config_line_t **list, config_line_t **list_last, smartlist_t *opened_lst) { config_line_t *ret_list = NULL; config_line_t **next = &ret_list; smartlist_t *config_files = config_get_file_list(path, opened_lst); if (!config_files) { return -1; } int rv = -1; SMARTLIST_FOREACH_BEGIN(config_files, const char *, config_file) { config_line_t *included_config = NULL; if (config_get_included_config(config_file, recursion_level, extended, &included_config, list_last, opened_lst) < 0) { goto done; } *next = included_config; if (*list_last) next = &(*list_last)->next; } SMARTLIST_FOREACH_END(config_file); *list = ret_list; rv = 0; done: SMARTLIST_FOREACH(config_files, char *, f, tor_free(f)); smartlist_free(config_files); return rv; } /** * Free all the configuration lines on the linked list front. */ void config_free_lines_(config_line_t *front) { config_line_t *tmp; while (front) { tmp = front; front = tmp->next; tor_free(tmp->key); tor_free(tmp->value); tor_free(tmp); } } /** Return a newly allocated deep copy of the lines in inp. */ config_line_t * config_lines_dup(const config_line_t *inp) { return config_lines_dup_and_filter(inp, NULL); } /** Return a newly allocated deep copy of the lines in inp, * but only the ones whose keys begin with key (case-insensitive). * If key is NULL, do not filter. */ config_line_t * config_lines_dup_and_filter(const config_line_t *inp, const char *key) { config_line_t *result = NULL; config_line_t **next_out = &result; while (inp) { if (key && strcasecmpstart(inp->key, key)) { inp = inp->next; continue; } *next_out = tor_malloc_zero(sizeof(config_line_t)); (*next_out)->key = tor_strdup(inp->key); (*next_out)->value = tor_strdup(inp->value); inp = inp->next; next_out = &((*next_out)->next); } (*next_out) = NULL; return result; } /** Return true iff a and b contain identical keys and values in identical * order. */ int config_lines_eq(config_line_t *a, config_line_t *b) { while (a && b) { if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value)) return 0; a = a->next; b = b->next; } if (a || b) return 0; return 1; } /** Return the number of lines in a whose key is key. */ int config_count_key(const config_line_t *a, const char *key) { int n = 0; while (a) { if (!strcasecmp(a->key, key)) { ++n; } a = a->next; } return n; } /** Given a string containing part of a configuration file or similar format, * advance past comments and whitespace and try to parse a single line. If we * parse a line successfully, set *key_out to a new string holding the * key portion and *value_out to a new string holding the value portion * of the line, and return a pointer to the start of the next line. If we run * out of data, return a pointer to the end of the string. If we encounter an * error, return NULL and set *err_out (if provided) to an error * message. */ const char * parse_config_line_from_str_verbose(const char *line, char **key_out, char **value_out, const char **err_out) { /* See torrc_format.txt for a description of the (silly) format this parses. */ const char *key, *val, *cp; int continuation = 0; tor_assert(key_out); tor_assert(value_out); *key_out = *value_out = NULL; key = val = NULL; /* Skip until the first keyword. */ while (1) { while (TOR_ISSPACE(*line)) ++line; if (*line == '#') { while (*line && *line != '\n') ++line; } else { break; } } if (!*line) { /* End of string? */ *key_out = *value_out = NULL; return line; } /* Skip until the next space or \ followed by newline. */ key = line; while (*line && !TOR_ISSPACE(*line) && *line != '#' && ! (line[0] == '\\' && line[1] == '\n')) ++line; *key_out = tor_strndup(key, line-key); /* Skip until the value. */ while (*line == ' ' || *line == '\t') ++line; val = line; /* Find the end of the line. */ if (*line == '\"') { // XXX No continuation handling is done here if (!(line = unescape_string(line, value_out, NULL))) { if (err_out) *err_out = "Invalid escape sequence in quoted string"; return NULL; } while (*line == ' ' || *line == '\t') ++line; if (*line == '\r' && *(++line) == '\n') ++line; if (*line && *line != '#' && *line != '\n') { if (err_out) *err_out = "Excess data after quoted string"; return NULL; } } else { /* Look for the end of the line. */ while (*line && *line != '\n' && (*line != '#' || continuation)) { if (*line == '\\' && line[1] == '\n') { continuation = 1; line += 2; } else if (*line == '#') { do { ++line; } while (*line && *line != '\n'); if (*line == '\n') ++line; } else { ++line; } } if (*line == '\n') { cp = line++; } else { cp = line; } /* Now back cp up to be the last nonspace character */ while (cp>val && TOR_ISSPACE(*(cp-1))) --cp; tor_assert(cp >= val); /* Now copy out and decode the value. */ *value_out = tor_strndup(val, cp-val); if (continuation) { char *v_out, *v_in; v_out = v_in = *value_out; while (*v_in) { if (*v_in == '#') { do { ++v_in; } while (*v_in && *v_in != '\n'); if (*v_in == '\n') ++v_in; } else if (v_in[0] == '\\' && v_in[1] == '\n') { v_in += 2; } else { *v_out++ = *v_in++; } } *v_out = '\0'; } } if (*line == '#') { do { ++line; } while (*line && *line != '\n'); } while (TOR_ISSPACE(*line)) ++line; return line; }