tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

confline.c (12249B)


      1 /* Copyright (c) 2001 Matej Pfajfar.
      2 * Copyright (c) 2001-2004, Roger Dingledine.
      3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      5 /* See LICENSE for licensing information */
      6 
      7 /**
      8 * \file confline.c
      9 *
     10 * \brief Functions to manipulate a linked list of key-value pairs, of the
     11 *   type used in Tor's configuration files.
     12 *
     13 * Tor uses the config_line_t type and its associated serialized format for
     14 * human-readable key-value pairs in many places, including its configuration,
     15 * its state files, its consensus cache, and so on.
     16 **/
     17 
     18 #include "lib/encoding/confline.h"
     19 #include "lib/encoding/cstring.h"
     20 #include "lib/log/log.h"
     21 #include "lib/log/util_bug.h"
     22 #include "lib/malloc/malloc.h"
     23 #include "lib/string/compat_ctype.h"
     24 #include "lib/string/compat_string.h"
     25 #include "lib/string/util_string.h"
     26 
     27 #include <string.h>
     28 
     29 /** Helper: allocate a new configuration option mapping 'key' to 'val',
     30 * append it to *<b>lst</b>. */
     31 void
     32 config_line_append(config_line_t **lst,
     33                   const char *key,
     34                   const char *val)
     35 {
     36  tor_assert(lst);
     37 
     38  config_line_t *newline;
     39 
     40  newline = tor_malloc_zero(sizeof(config_line_t));
     41  newline->key = tor_strdup(key);
     42  newline->value = tor_strdup(val);
     43  newline->next = NULL;
     44  while (*lst)
     45    lst = &((*lst)->next);
     46 
     47  (*lst) = newline;
     48 }
     49 
     50 /** Helper: allocate a new configuration option mapping 'key' to 'val',
     51 * and prepend it to *<b>lst</b> */
     52 void
     53 config_line_prepend(config_line_t **lst,
     54                    const char *key,
     55                    const char *val)
     56 {
     57  tor_assert(lst);
     58 
     59  config_line_t *newline;
     60 
     61  newline = tor_malloc_zero(sizeof(config_line_t));
     62  newline->key = tor_strdup(key);
     63  newline->value = tor_strdup(val);
     64  newline->next = *lst;
     65  *lst = newline;
     66 }
     67 
     68 /** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or
     69 * NULL if no such key exists.
     70 *
     71 * (In options parsing, this is for handling commandline-only options only;
     72 * other options should be looked up in the appropriate data structure.) */
     73 const config_line_t *
     74 config_line_find(const config_line_t *lines,
     75                 const char *key)
     76 {
     77  const config_line_t *cl;
     78  for (cl = lines; cl; cl = cl->next) {
     79    if (!strcmp(cl->key, key))
     80      return cl;
     81  }
     82  return NULL;
     83 }
     84 
     85 /** As config_line_find(), but perform a case-insensitive comparison. */
     86 const config_line_t *
     87 config_line_find_case(const config_line_t *lines,
     88                      const char *key)
     89 {
     90  const config_line_t *cl;
     91  for (cl = lines; cl; cl = cl->next) {
     92    if (!strcasecmp(cl->key, key))
     93      return cl;
     94  }
     95  return NULL;
     96 }
     97 
     98 /** Auxiliary function that does all the work of config_get_lines.
     99 * <b>recursion_level</b> is the count of how many nested %includes we have.
    100 * <b>opened_lst</b> will have a list of opened files if provided.
    101 * Returns the a pointer to the last element of the <b>result</b> in
    102 * <b>last</b>. */
    103 int
    104 config_get_lines_aux(const char *string, config_line_t **result, int extended,
    105                     int allow_include, int *has_include,
    106                     struct smartlist_t *opened_lst, int recursion_level,
    107                     config_line_t **last,
    108                     include_handler_fn handle_include)
    109 {
    110  config_line_t *list = NULL, **next, *list_last = NULL;
    111  char *k, *v;
    112  const char *parse_err;
    113  int include_used = 0;
    114 
    115  if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
    116    log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
    117             "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
    118    return -1;
    119  }
    120 
    121  next = &list;
    122  do {
    123    k = v = NULL;
    124    string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
    125    if (!string) {
    126      log_warn(LD_CONFIG, "Error while parsing configuration: %s",
    127               parse_err?parse_err:"<unknown>");
    128      config_free_lines(list);
    129      tor_free(k);
    130      tor_free(v);
    131      return -1;
    132    }
    133    if (k && v) {
    134      unsigned command = CONFIG_LINE_NORMAL;
    135      if (extended) {
    136        if (k[0] == '+') {
    137          char *k_new = tor_strdup(k+1);
    138          tor_free(k);
    139          k = k_new;
    140          command = CONFIG_LINE_APPEND;
    141        } else if (k[0] == '/') {
    142          char *k_new = tor_strdup(k+1);
    143          tor_free(k);
    144          k = k_new;
    145          tor_free(v);
    146          v = tor_strdup("");
    147          command = CONFIG_LINE_CLEAR;
    148        }
    149      }
    150 
    151      if (allow_include && !strcmp(k, "%include") && handle_include) {
    152        tor_free(k);
    153        include_used = 1;
    154        log_notice(LD_CONFIG, "Processing configuration path \"%s\" at "
    155                   "recursion level %d.", v, recursion_level);
    156 
    157        config_line_t *include_list;
    158        if (handle_include(v, recursion_level, extended, &include_list,
    159                           &list_last, opened_lst) < 0) {
    160          log_warn(LD_CONFIG, "Error reading included configuration "
    161                   "file or directory: \"%s\".", v);
    162          config_free_lines(list);
    163          tor_free(v);
    164          return -1;
    165        }
    166        *next = include_list;
    167        if (list_last)
    168          next = &list_last->next;
    169        tor_free(v);
    170      } else {
    171        /* This list can get long, so we keep a pointer to the end of it
    172         * rather than using config_line_append over and over and getting
    173         * n^2 performance. */
    174        *next = tor_malloc_zero(sizeof(**next));
    175        (*next)->key = k;
    176        (*next)->value = v;
    177        (*next)->next = NULL;
    178        (*next)->command = command;
    179        list_last = *next;
    180        next = &((*next)->next);
    181      }
    182    } else {
    183      tor_free(k);
    184      tor_free(v);
    185    }
    186  } while (*string);
    187 
    188  if (last) {
    189    *last = list_last;
    190  }
    191  if (has_include) {
    192    *has_include = include_used;
    193  }
    194  *result = list;
    195  return 0;
    196 }
    197 
    198 /** Same as config_get_lines_include but does not allow %include */
    199 int
    200 config_get_lines(const char *string, config_line_t **result, int extended)
    201 {
    202  return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1,
    203                              NULL, NULL);
    204 }
    205 
    206 /**
    207 * Free all the configuration lines on the linked list <b>front</b>.
    208 */
    209 void
    210 config_free_lines_(config_line_t *front)
    211 {
    212  config_line_t *tmp;
    213 
    214  while (front) {
    215    tmp = front;
    216    front = tmp->next;
    217 
    218    tor_free(tmp->key);
    219    tor_free(tmp->value);
    220    tor_free(tmp);
    221  }
    222 }
    223 
    224 /** Return a newly allocated deep copy of the lines in <b>inp</b>. */
    225 config_line_t *
    226 config_lines_dup(const config_line_t *inp)
    227 {
    228  return config_lines_dup_and_filter(inp, NULL);
    229 }
    230 
    231 /** Return a newly allocated deep copy of the lines in <b>inp</b>,
    232 * but only the ones whose keys begin with <b>key</b> (case-insensitive).
    233 * If <b>key</b> is NULL, do not filter. */
    234 config_line_t *
    235 config_lines_dup_and_filter(const config_line_t *inp,
    236                            const char *key)
    237 {
    238  config_line_t *result = NULL;
    239  config_line_t **next_out = &result;
    240  while (inp) {
    241    if (key && strcasecmpstart(inp->key, key)) {
    242      inp = inp->next;
    243      continue;
    244    }
    245    *next_out = tor_malloc_zero(sizeof(config_line_t));
    246    (*next_out)->key = tor_strdup(inp->key);
    247    (*next_out)->value = tor_strdup(inp->value);
    248    inp = inp->next;
    249    next_out = &((*next_out)->next);
    250  }
    251  (*next_out) = NULL;
    252  return result;
    253 }
    254 
    255 /**
    256 * Given a linelist <b>inp</b> beginning with the key <b>header</b>, find the
    257 * next line with that key, and remove that instance and all following lines
    258 * from the list.  Return the lines that were removed.  Operate
    259 * case-insensitively.
    260 *
    261 * For example, if the header is "H", and <b>inp</b> contains "H, A, B, H, C,
    262 * H, D", this function will alter <b>inp</b> to contain only "H, A, B", and
    263 * return the elements "H, C, H, D" as a separate list.
    264 **/
    265 config_line_t *
    266 config_lines_partition(config_line_t *inp, const char *header)
    267 {
    268  if (BUG(inp == NULL))
    269    return NULL;
    270  if (BUG(strcasecmp(inp->key, header)))
    271    return NULL;
    272 
    273  /* Advance ptr until it points to the link to the next segment of this
    274     list. */
    275  config_line_t **ptr = &inp->next;
    276  while (*ptr && strcasecmp((*ptr)->key, header)) {
    277    ptr = &(*ptr)->next;
    278  }
    279  config_line_t *remainder = *ptr;
    280  *ptr = NULL;
    281  return remainder;
    282 }
    283 
    284 /** Return true iff a and b contain identical keys and values in identical
    285 * order. */
    286 int
    287 config_lines_eq(const config_line_t *a, const config_line_t *b)
    288 {
    289  while (a && b) {
    290    if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
    291      return 0;
    292    a = a->next;
    293    b = b->next;
    294  }
    295  if (a || b)
    296    return 0;
    297  return 1;
    298 }
    299 
    300 /** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
    301 int
    302 config_count_key(const config_line_t *a, const char *key)
    303 {
    304  int n = 0;
    305  while (a) {
    306    if (!strcasecmp(a->key, key)) {
    307      ++n;
    308    }
    309    a = a->next;
    310  }
    311  return n;
    312 }
    313 
    314 /** Given a string containing part of a configuration file or similar format,
    315 * advance past comments and whitespace and try to parse a single line.  If we
    316 * parse a line successfully, set *<b>key_out</b> to a new string holding the
    317 * key portion and *<b>value_out</b> to a new string holding the value portion
    318 * of the line, and return a pointer to the start of the next line.  If we run
    319 * out of data, return a pointer to the end of the string.  If we encounter an
    320 * error, return NULL and set *<b>err_out</b> (if provided) to an error
    321 * message.
    322 */
    323 const char *
    324 parse_config_line_from_str_verbose(const char *line, char **key_out,
    325                                   char **value_out,
    326                                   const char **err_out)
    327 {
    328  /*
    329    See torrc_format.txt for a description of the (silly) format this parses.
    330   */
    331  const char *key, *val, *cp;
    332  int continuation = 0;
    333 
    334  tor_assert(key_out);
    335  tor_assert(value_out);
    336 
    337  *key_out = *value_out = NULL;
    338  key = val = NULL;
    339  /* Skip until the first keyword. */
    340  while (1) {
    341    while (TOR_ISSPACE(*line))
    342      ++line;
    343    if (*line == '#') {
    344      while (*line && *line != '\n')
    345        ++line;
    346    } else {
    347      break;
    348    }
    349  }
    350 
    351  if (!*line) { /* End of string? */
    352    *key_out = *value_out = NULL;
    353    return line;
    354  }
    355 
    356  /* Skip until the next space or \ followed by newline. */
    357  key = line;
    358  while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
    359         ! (line[0] == '\\' && line[1] == '\n'))
    360    ++line;
    361  *key_out = tor_strndup(key, line-key);
    362 
    363  /* Skip until the value. */
    364  while (*line == ' ' || *line == '\t')
    365    ++line;
    366 
    367  val = line;
    368 
    369  /* Find the end of the line. */
    370  if (*line == '\"') { // XXX No continuation handling is done here
    371    if (!(line = unescape_string(line, value_out, NULL))) {
    372      if (err_out)
    373        *err_out = "Invalid escape sequence in quoted string";
    374      return NULL;
    375    }
    376    while (*line == ' ' || *line == '\t')
    377      ++line;
    378    if (*line == '\r' && *(++line) == '\n')
    379      ++line;
    380    if (*line && *line != '#' && *line != '\n') {
    381      if (err_out)
    382        *err_out = "Excess data after quoted string";
    383      return NULL;
    384    }
    385  } else {
    386    /* Look for the end of the line. */
    387    while (*line && *line != '\n' && (*line != '#' || continuation)) {
    388      if (*line == '\\' && line[1] == '\n') {
    389        continuation = 1;
    390        line += 2;
    391      } else if (*line == '#') {
    392        do {
    393          ++line;
    394        } while (*line && *line != '\n');
    395        if (*line == '\n')
    396          ++line;
    397      } else {
    398        ++line;
    399      }
    400    }
    401 
    402    if (*line == '\n') {
    403      cp = line++;
    404    } else {
    405      cp = line;
    406    }
    407    /* Now back cp up to be the last nonspace character */
    408    while (cp>val && TOR_ISSPACE(*(cp-1)))
    409      --cp;
    410 
    411    tor_assert(cp >= val);
    412 
    413    /* Now copy out and decode the value. */
    414    *value_out = tor_strndup(val, cp-val);
    415    if (continuation) {
    416      char *v_out, *v_in;
    417      v_out = v_in = *value_out;
    418      while (*v_in) {
    419        if (*v_in == '#') {
    420          do {
    421            ++v_in;
    422          } while (*v_in && *v_in != '\n');
    423          if (*v_in == '\n')
    424            ++v_in;
    425        } else if (v_in[0] == '\\' && v_in[1] == '\n') {
    426          v_in += 2;
    427        } else {
    428          *v_out++ = *v_in++;
    429        }
    430      }
    431      *v_out = '\0';
    432    }
    433  }
    434 
    435  if (*line == '#') {
    436    do {
    437      ++line;
    438    } while (*line && *line != '\n');
    439  }
    440  while (TOR_ISSPACE(*line)) ++line;
    441 
    442  return line;
    443 }