Commit 1b491980 authored by Nick Mathewson's avatar Nick Mathewson 🦀
Browse files

Support code for resettable options, and option sets. Still needs validate-and-then-replace logic


svn:r2679
parent d9e0f3f9
Loading
Loading
Loading
Loading
+144 −37
Original line number Diff line number Diff line
@@ -23,6 +23,11 @@ typedef enum config_type_t {
  CONFIG_TYPE_CSV,          /**< A list of strings, separated by commas and optional
                              * whitespace. */
  CONFIG_TYPE_LINELIST,     /**< Uninterpreted config lines */
  CONFIG_TYPE_LINELIST_S,   /**< Uninterpreted, context-sensitive config lines,
                             * mixed with other keywords. */
  CONFIG_TYPE_LINELIST_V,   /**< Catch-all "virtual" option to summarize
                             * context-sensitive config lines when fetching.
                             */
  CONFIG_TYPE_OBSOLETE,     /**< Obsolete (ignored) option. */
} config_type_t;

@@ -106,14 +111,16 @@ static config_var_t config_vars[] = {
  VAR("Group",               STRING,   Group,                NULL),
  VAR("HashedControlPassword",STRING,  HashedControlPassword, NULL),
  VAR("HttpProxy",           STRING,   HttpProxy,            NULL),
  VAR("HiddenServiceDir",    LINELIST, RendConfigLines,      NULL),
  VAR("HiddenServicePort",   LINELIST, RendConfigLines,      NULL),
  VAR("HiddenServiceNodes",  LINELIST, RendConfigLines,      NULL),
  VAR("HiddenServiceExcludeNodes", LINELIST, RendConfigLines,NULL),
  VAR("HiddenServiceOptions",LINELIST_V, RendConfigLines,    NULL),
  VAR("HiddenServiceDir",    LINELIST_S, RendConfigLines,    NULL),
  VAR("HiddenServicePort",   LINELIST_S, RendConfigLines,    NULL),
  VAR("HiddenServiceNodes",  LINELIST_S, RendConfigLines,    NULL),
  VAR("HiddenServiceExcludeNodes", LINELIST_S, RendConfigLines, NULL),
  VAR("IgnoreVersion",       BOOL,     IgnoreVersion,        "0"),
  VAR("KeepalivePeriod",     UINT,     KeepalivePeriod,      "300"),
  VAR("LogLevel",            LINELIST, LogOptions,           NULL),
  VAR("LogFile",             LINELIST, LogOptions,           NULL),
  VAR("LogOptions",          LINELIST_V, LogOptions,         NULL),
  VAR("LogLevel",            LINELIST_S, LogOptions,         NULL),
  VAR("LogFile",             LINELIST_S, LogOptions,         NULL),
  OBSOLETE("LinkPadding"),
  VAR("MaxConn",             UINT,     MaxConn,              "1024"),
  VAR("MaxOnionsPending",    UINT,     MaxOnionsPending,     "100"),
@@ -137,7 +144,7 @@ static config_var_t config_vars[] = {
  VAR("SocksPort",           UINT,     SocksPort,            "9050"),
  VAR("SocksBindAddress",    LINELIST, SocksBindAddress,     NULL),
  VAR("SocksPolicy",         LINELIST, SocksPolicy,          NULL),
  VAR("SysLog",              LINELIST, LogOptions,           NULL),
  VAR("SysLog",              LINELIST_S, LogOptions,         NULL),
  OBSOLETE("TrafficShaping"),
  VAR("User",                STRING,   User,                 NULL),
  { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
@@ -151,13 +158,16 @@ static config_var_t config_vars[] = {
static struct config_line_t *config_get_commandlines(int argc, char **argv);
static int config_get_lines(FILE *f, struct config_line_t **result);
static void config_free_lines(struct config_line_t *front);
static int config_assign_line(or_options_t *options, struct config_line_t *c);
static int config_assign(or_options_t *options, struct config_line_t *list);
static int config_assign_line(or_options_t *options, struct config_line_t *c,
                              int reset);
static int config_assign(or_options_t *options, struct config_line_t *list,
                         int reset);
static int parse_dir_server_line(const char *line);
static int parse_redirect_line(or_options_t *options,
                               struct config_line_t *line);
static const char *expand_abbrev(const char *option, int commandline_only);
static config_var_t *config_find_option(const char *key);
static void reset_option(or_options_t *options, config_var_t *var);

/** If <b>option</b> is an official abbreviation for a longer option,
 * return the longer option.  Otherwise return <b>option</b>.
@@ -216,7 +226,7 @@ config_get_commandlines(int argc, char **argv)

/** Helper: allocate a new configuration option mapping 'key' to 'val',
 * prepend it to 'front', and return the newly allocated config_line_t */
static struct config_line_t *
struct config_line_t *
config_line_prepend(struct config_line_t *front,
                    const char *key,
                    const char *val)
@@ -298,9 +308,13 @@ static config_var_t *config_find_option(const char *key)
}

/** If <b>c</b> is a syntactically valid configuration line, update
 * <b>options</b> with its value and return 0.  Otherwise return -1. */
 * <b>options</b> with its value and return 0.  Otherwise return -1.
 *
 * If 'reset' is set, and we get a line containing no value, restore the
 * option to its default value.
 */
static int
config_assign_line(or_options_t *options, struct config_line_t *c)
config_assign_line(or_options_t *options, struct config_line_t *c, int reset)
{
  int i, ok;
  config_var_t *var;
@@ -311,13 +325,17 @@ config_assign_line(or_options_t *options, struct config_line_t *c)
    log_fn(LOG_WARN, "Unknown option '%s'.  Failing.", c->key);
    return -1;
  }

  /* Put keyword into canonical case. */
  if (strcmp(var->name, c->key)) {
    tor_free(c->key);
    c->key = tor_strdup(var->name);
  }

  if (reset && !strlen(c->value)) {
    reset_option(options, var);
    return 0;
  }

  lvalue = ((char*)options) + var->var_offset;
  switch(var->type) {

@@ -357,7 +375,9 @@ config_assign_line(or_options_t *options, struct config_line_t *c)
                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
    break;


  case CONFIG_TYPE_LINELIST:
  case CONFIG_TYPE_LINELIST_S:
    /* Note: this reverses the order that the lines appear in.  That's
     * just fine, since we build up the list of lines reversed in the
     * first place. */
@@ -368,11 +388,30 @@ config_assign_line(or_options_t *options, struct config_line_t *c)
  case CONFIG_TYPE_OBSOLETE:
    log_fn(LOG_WARN, "Skipping obsolete configuration option '%s'", c->key);
    break;
  case CONFIG_TYPE_LINELIST_V:
    log_fn(LOG_WARN, "Can't provide value for virtual option '%s'", c->key);
    return -1;
  default:
    tor_assert(0);
    break;
  }

  return 0;
}

/** restore the option named <b>key</b> in options to its default value. */
static void
config_reset_line(or_options_t *options, const char *key)
{
  config_var_t *var;

  var = config_find_option(key);
  if (!var)
    return; /* give error on next pass. */

  reset_option(options, var);
}

/** Return a canonicalized list of the options assigned for key.
 */
struct config_line_t *
@@ -387,10 +426,14 @@ config_get_assigned_option(or_options_t *options, const char *key)
  if (!var) {
    log_fn(LOG_WARN, "Unknown option '%s'.  Failing.", key);
    return NULL;
  } else if (var->type == CONFIG_TYPE_LINELIST_S) {
    log_fn(LOG_WARN, "Can't return context-sensitive '%s' on its own", key);
    return NULL;
  }
  value = ((char*)options) + var->var_offset;

  if (var->type == CONFIG_TYPE_LINELIST) {
  if (var->type == CONFIG_TYPE_LINELIST ||
      var->type == CONFIG_TYPE_LINELIST_V) {
    /* Linelist requires special handling: we just copy and return it. */
    const struct config_line_t *next_in = value;
    struct config_line_t **next_out = &result;
@@ -442,18 +485,36 @@ config_get_assigned_option(or_options_t *options, const char *key)
/** Iterate through the linked list of requested options <b>list</b>.
 * For each item, convert as appropriate and assign to <b>options</b>.
 * If an item is unrecognized, return -1 immediately,
 * else return 0 for success. */
 * else return 0 for success.
 *
 * If <b>reset</b>, then interpret empty lines as meaning "restore to
 * default value", and interpret LINELIST* options as replacing (not
 * extending) their previous values.
 */
static int
config_assign(or_options_t *options, struct config_line_t *list)
config_assign(or_options_t *options, struct config_line_t *list, int reset)
{
  while (list) {
    const char *full = expand_abbrev(list->key, 0);
    if (strcmp(full,list->key)) {
      tor_free(list->key);
      list->key = tor_strdup(full);
  struct config_line_t *p;

  /* pass 1: normalize keys */
  for (p = list; p; p = p->next) {
    const char *full = expand_abbrev(p->key, 0);
    if (strcmp(full,p->key)) {
      tor_free(p->key);
      p->key = tor_strdup(full);
    }
  }

  /* pass 2: if we're reading from a resetting souurce, clear all mentioned
   * linelists. */
  if (reset) {
    for (p = list; p; p = p->next)
      config_reset_line(options, p->key);
  }

    if (config_assign_line(options, list))
  /* pass 3: assign. */
  while (list) {
    if (config_assign_line(options, list, reset))
      return -1;
    list = list->next;
  }
@@ -604,6 +665,7 @@ free_options(or_options_t *options)
        tor_free(*(char **)lvalue);
        break;
      case CONFIG_TYPE_LINELIST:
      case CONFIG_TYPE_LINELIST_V:
        config_free_lines(*(struct config_line_t**)lvalue);
        *(struct config_line_t**)lvalue = NULL;
        break;
@@ -614,6 +676,9 @@ free_options(or_options_t *options)
          *(smartlist_t**)lvalue = NULL;
        }
        break;
      case CONFIG_TYPE_LINELIST_S:
        /* will be freed by corresponding LINELIST_V. */
        break;
    }
  }
  /* XXX this last part is an exception. can we make it not needed? */
@@ -625,13 +690,59 @@ free_options(or_options_t *options)
  }
}

/** Replace the option indexed by <b>var</b> in <b>options</b> with its
 * default value. */
static void
reset_option(or_options_t *options, config_var_t *var)
{
  struct config_line_t *c;
  void *lvalue;

  lvalue = ((char*)options) + var->var_offset;
  switch (var->type) {
    case CONFIG_TYPE_STRING:
      tor_free(*(char**)lvalue);
      break;
    case CONFIG_TYPE_DOUBLE:
      *(double*)lvalue = 0.0;
      break;
    case CONFIG_TYPE_UINT:
    case CONFIG_TYPE_BOOL:
      *(int*)lvalue = 0;
      break;
    case CONFIG_TYPE_CSV:
      if (*(smartlist_t**)lvalue) {
        SMARTLIST_FOREACH(*(smartlist_t **)lvalue, char *, cp, tor_free(cp));
        smartlist_free(*(smartlist_t **)lvalue);
        *(smartlist_t **)lvalue = NULL;
      }
      break;
    case CONFIG_TYPE_LINELIST:
    case CONFIG_TYPE_LINELIST_S:
      config_free_lines(*(struct config_line_t **)lvalue);
      *(struct config_line_t **)lvalue = NULL;
      break;
    case CONFIG_TYPE_LINELIST_V:
      /* handled by linelist_s. */
      break;
    case CONFIG_TYPE_OBSOLETE:
      break;
  }
  if (var->initvalue) {
    c = tor_malloc(sizeof(struct config_line_t));
    c->key = tor_strdup(var->name);
    c->value = tor_strdup(var->initvalue);
    config_assign_line(options,c,0);
    config_free_lines(c);
  }
}

/** Set <b>options</b> to hold reasonable defaults for most options.
 * Each option defaults to zero. */
static void
init_options(or_options_t *options)
{
  int i;
  struct config_line_t *c;
  config_var_t *var;

  memset(options,0,sizeof(or_options_t));
@@ -639,11 +750,7 @@ init_options(or_options_t *options)
    var = &config_vars[i];
    if(!var->initvalue)
      continue; /* defaults to NULL or 0 */
    c = tor_malloc(sizeof(struct config_line_t));
    c->key = tor_strdup(var->name);
    c->value = tor_strdup(var->initvalue);
    config_assign_line(options,c);
    config_free_lines(c);
    reset_option(options, var);
  }
}

@@ -1048,7 +1155,7 @@ getconfig(int argc, char **argv, or_options_t *options)
    tor_free(fname);
    if (config_get_lines(cf, &cl)<0)
      return -1;
    if (config_assign(options,cl) < 0)
    if (config_assign(options,cl, 0) < 0)
      return -1;
    config_free_lines(cl);
    fclose(cf);
@@ -1056,7 +1163,7 @@ getconfig(int argc, char **argv, or_options_t *options)

/* go through command-line variables too */
  cl = config_get_commandlines(argc,argv);
  if (config_assign(options,cl) < 0)
  if (config_assign(options,cl,0) < 0)
    return -1;
  config_free_lines(cl);

+47 −23
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ static void send_control_error(connection_t *conn, uint16_t error,
                               const char *message);
static void send_control_event(uint16_t event, uint16_t len, const char *body);
static int handle_control_setconf(connection_t *conn, uint16_t len,
                                  const char *body);
                                  char *body);
static int handle_control_getconf(connection_t *conn, uint16_t len,
                                  const char *body);
static int handle_control_setevents(connection_t *conn, uint16_t len,
@@ -139,58 +139,81 @@ send_control_event(uint16_t event, uint16_t len, const char *body)

static int
handle_control_setconf(connection_t *conn, uint16_t len,
                                  const char *body)
                       char *body)
{
  char *k, *v;
  struct config_line_t *lines = NULL;

  /* XXXX009 move this logic into config.c someplace. */

  do {
    body = parse_line_from_str(body, &k, &v);
    if (!body) {
      goto err;
    }
    if (k && v)
      lines = config_line_prepend(lines, k, v);
  } while (*body);

  /* XXXX009 NM */

  return 0;
 err:
  send_control_error(conn, ERR_UNSPECIFIED, "Couldn't parse configuration");
  /* config_free_lines(lines); */
  return 0;
}

static int handle_control_getconf(connection_t *conn, uint16_t body_len,
static int
handle_control_getconf(connection_t *conn, uint16_t body_len,
                                  const char *body)
{
  smartlist_t *answer_elements = NULL;
  smartlist_t *questions = NULL;
  smartlist_t *answers = NULL;
  char *msg = NULL;
  size_t msg_len;

  if (body[body_len-1] != '\0') {
    send_control_error(conn, ERR_UNSPECIFIED,
                       "getconf message body not nul-terminated.");
    return 0;
  }
  /* Now we can be sure that body will end in a nul-terminated string. */

  answer_elements = smartlist_create();
  while (body_len) {
    size_t question_len = strlen(body);
    struct config_line_t *answer = config_get_assigned_option(&options,body);
  questions = smartlist_create();
  smartlist_split_string(questions, body, "\n",
                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
  answers = smartlist_create();
  SMARTLIST_FOREACH(questions, const char *, q,
  {
    struct config_line_t *answer = config_get_assigned_option(&options,q);
    if (!answer) {
      send_control_error(conn, ERR_UNRECOGNIZED_CONFIG_KEY, body);
      goto done;
    } else {
      while (answer) {
        struct config_line_t *next;
        smartlist_add(answer_elements, answer->key);
        smartlist_add(answer_elements, answer->value);
        size_t alen = strlen(answer->key)+strlen(answer->value)+2;
        char *astr = tor_malloc(alen);
        tor_snprintf(astr, alen, "%s %s\n", answer->key, answer->value);
        smartlist_add(answers, astr);

        next = answer->next;
        tor_free(answer->key);
        tor_free(answer->value);
        tor_free(answer);
        answer = next;
      }
    }
    body += question_len+1;
    body_len -= question_len+1;
  }
  });

  msg = smartlist_join_strings2(answer_elements, "\0", 1, 0, &msg_len);
  msg = smartlist_join_strings(answers, "", 0, &msg_len);
  send_control_message(conn, CONTROL_CMD_CONFVALUE,
                       (uint16_t)msg_len, msg);

 done:
  SMARTLIST_FOREACH(answer_elements, char *, cp, tor_free(cp));
  smartlist_free(answer_elements);
  if (answers) SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp));
  if (questions) SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
  smartlist_free(answers);
  smartlist_free(questions);
  tor_free(msg);

  return 0;
}

static int handle_control_setevents(connection_t *conn, uint16_t len,
                                    const char *body)
{
@@ -218,6 +241,7 @@ static int handle_control_setevents(connection_t *conn, uint16_t len,
  send_control_done(conn);
  return 0;
}

static int handle_control_authenticate(connection_t *conn, uint16_t len,
                                       const char *body)
{