|  | @@ -395,7 +395,7 @@ static config_var_t option_vars_[] = {
 | 
	
		
			
				|  |  |    V(MaxOnionQueueDelay,          MSEC_INTERVAL, "1750 msec"),
 | 
	
		
			
				|  |  |    V(MaxUnparseableDescSizeToLog, MEMUNIT, "10 MB"),
 | 
	
		
			
				|  |  |    V(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, "500"),
 | 
	
		
			
				|  |  | -  V(MyFamily,                    STRING,   NULL),
 | 
	
		
			
				|  |  | +  V(MyFamily,                    LINELIST,   NULL),
 | 
	
		
			
				|  |  |    V(NewCircuitPeriod,            INTERVAL, "30 seconds"),
 | 
	
		
			
				|  |  |    OBSOLETE("NamingAuthoritativeDirectory"),
 | 
	
		
			
				|  |  |    V(NATDListenAddress,           LINELIST, NULL),
 | 
	
	
		
			
				|  | @@ -707,7 +707,8 @@ static int options_transition_affects_workers(
 | 
	
		
			
				|  |  |        const or_options_t *old_options, const or_options_t *new_options);
 | 
	
		
			
				|  |  |  static int options_transition_affects_descriptor(
 | 
	
		
			
				|  |  |        const or_options_t *old_options, const or_options_t *new_options);
 | 
	
		
			
				|  |  | -static int check_nickname_list(char **lst, const char *name, char **msg);
 | 
	
		
			
				|  |  | +static int normalize_nickname_list(config_line_t **lst, const char *name,
 | 
	
		
			
				|  |  | +      char **msg);
 | 
	
		
			
				|  |  |  static char *get_bindaddr_from_transport_listen_line(const char *line,
 | 
	
		
			
				|  |  |                                                       const char *transport);
 | 
	
		
			
				|  |  |  static int parse_ports(or_options_t *options, int validate_only,
 | 
	
	
		
			
				|  | @@ -3885,7 +3886,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
 | 
	
		
			
				|  |  |               "You should also make sure you aren't listing this bridge's "
 | 
	
		
			
				|  |  |               "fingerprint in any other MyFamily.");
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (check_nickname_list(&options->MyFamily, "MyFamily", msg))
 | 
	
		
			
				|  |  | +  if (normalize_nickname_list(&options->MyFamily, "MyFamily", msg))
 | 
	
		
			
				|  |  |      return -1;
 | 
	
		
			
				|  |  |    for (cl = options->NodeFamilies; cl; cl = cl->next) {
 | 
	
		
			
				|  |  |      routerset_t *rs = routerset_new();
 | 
	
	
		
			
				|  | @@ -4593,7 +4594,7 @@ options_transition_affects_descriptor(const or_options_t *old_options,
 | 
	
		
			
				|  |  |        get_effective_bwburst(old_options) !=
 | 
	
		
			
				|  |  |          get_effective_bwburst(new_options) ||
 | 
	
		
			
				|  |  |        !opt_streq(old_options->ContactInfo, new_options->ContactInfo) ||
 | 
	
		
			
				|  |  | -      !opt_streq(old_options->MyFamily, new_options->MyFamily) ||
 | 
	
		
			
				|  |  | +      !config_lines_eq(old_options->MyFamily, new_options->MyFamily) ||
 | 
	
		
			
				|  |  |        !opt_streq(old_options->AccountingStart, new_options->AccountingStart) ||
 | 
	
		
			
				|  |  |        old_options->AccountingMax != new_options->AccountingMax ||
 | 
	
		
			
				|  |  |        old_options->AccountingRule != new_options->AccountingRule ||
 | 
	
	
		
			
				|  | @@ -4689,27 +4690,33 @@ get_default_conf_file(int defaults_file)
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/** Verify whether lst is a string containing valid-looking comma-separated
 | 
	
		
			
				|  |  | - * nicknames, or NULL. Will normalise <b>lst</b> to prefix '$' to any nickname
 | 
	
		
			
				|  |  | - * or fingerprint that needs it. Return 0 on success.
 | 
	
		
			
				|  |  | +/** Verify whether lst is a list of strings containing valid-looking
 | 
	
		
			
				|  |  | + * comma-separated nicknames, or NULL. Will normalise <b>lst</b> to prefix '$'
 | 
	
		
			
				|  |  | + * to any nickname or fingerprint that needs it. Also splits comma-separated
 | 
	
		
			
				|  |  | + * list elements into multiple elements. Return 0 on success.
 | 
	
		
			
				|  |  |   * Warn and return -1 on failure.
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  static int
 | 
	
		
			
				|  |  | -check_nickname_list(char **lst, const char *name, char **msg)
 | 
	
		
			
				|  |  | +normalize_nickname_list(config_line_t **lst, const char *name, char **msg)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -  int r = 0;
 | 
	
		
			
				|  |  | -  smartlist_t *sl;
 | 
	
		
			
				|  |  | -  int changes = 0;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |    if (!*lst)
 | 
	
		
			
				|  |  |      return 0;
 | 
	
		
			
				|  |  | -  sl = smartlist_new();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  smartlist_split_string(sl, *lst, ",",
 | 
	
		
			
				|  |  | -    SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
 | 
	
		
			
				|  |  | +  config_line_t *new_nicknames = NULL;
 | 
	
		
			
				|  |  | +  config_line_t *new_nicknames_last = NULL;
 | 
	
		
			
				|  |  | +  config_line_t *cl;
 | 
	
		
			
				|  |  | +  for (cl = *lst; cl; cl = cl->next) {
 | 
	
		
			
				|  |  | +    char *line = cl->value;
 | 
	
		
			
				|  |  | +    if (!line)
 | 
	
		
			
				|  |  | +      continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  SMARTLIST_FOREACH_BEGIN(sl, char *, s)
 | 
	
		
			
				|  |  | +    int valid_line = 1;
 | 
	
		
			
				|  |  | +    smartlist_t *sl = smartlist_new();
 | 
	
		
			
				|  |  | +    smartlist_split_string(sl, line, ",",
 | 
	
		
			
				|  |  | +      SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
 | 
	
		
			
				|  |  | +    SMARTLIST_FOREACH_BEGIN(sl, char *, s)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | +      char *normalized = NULL;
 | 
	
		
			
				|  |  |        if (!is_legal_nickname_or_hexdigest(s)) {
 | 
	
		
			
				|  |  |          // check if first char is dollar
 | 
	
		
			
				|  |  |          if (s[0] != '$') {
 | 
	
	
		
			
				|  | @@ -4718,36 +4725,53 @@ check_nickname_list(char **lst, const char *name, char **msg)
 | 
	
		
			
				|  |  |            tor_asprintf(&prepended, "$%s", s);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |            if (is_legal_nickname_or_hexdigest(prepended)) {
 | 
	
		
			
				|  |  | -            // The nickname is valid when it's prepended, swap the current
 | 
	
		
			
				|  |  | -            // version with a prepended one
 | 
	
		
			
				|  |  | -            tor_free(s);
 | 
	
		
			
				|  |  | -            SMARTLIST_REPLACE_CURRENT(sl, s, prepended);
 | 
	
		
			
				|  |  | -            changes = 1;
 | 
	
		
			
				|  |  | -            continue;
 | 
	
		
			
				|  |  | +            // The nickname is valid when it's prepended, set it as the
 | 
	
		
			
				|  |  | +            // normalized version
 | 
	
		
			
				|  |  | +            normalized = prepended;
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            // Still not valid, free and fallback to error message
 | 
	
		
			
				|  |  | +            tor_free(prepended);
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -          // Still not valid, free and fallback to error message
 | 
	
		
			
				|  |  | -          tor_free(prepended);
 | 
	
		
			
				|  |  | +        if (!normalized) {
 | 
	
		
			
				|  |  | +          tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
 | 
	
		
			
				|  |  | +          valid_line = 0;
 | 
	
		
			
				|  |  | +          break;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        normalized = tor_strdup(s);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
 | 
	
		
			
				|  |  | -        r = -1;
 | 
	
		
			
				|  |  | -        break;
 | 
	
		
			
				|  |  | +      config_line_t *next = tor_malloc_zero(sizeof(*next));
 | 
	
		
			
				|  |  | +      next->key = tor_strdup(cl->key);
 | 
	
		
			
				|  |  | +      next->value = normalized;
 | 
	
		
			
				|  |  | +      next->next = NULL;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      if (!new_nicknames) {
 | 
	
		
			
				|  |  | +        new_nicknames = next;
 | 
	
		
			
				|  |  | +        new_nicknames_last = next;
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        new_nicknames_last->next = next;
 | 
	
		
			
				|  |  | +        new_nicknames_last = next;
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -  SMARTLIST_FOREACH_END(s);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // Replace the caller's nickname list with a fixed one
 | 
	
		
			
				|  |  | -  if (changes && r == 0) {
 | 
	
		
			
				|  |  | -    char *newNicknames = smartlist_join_strings(sl, ", ", 0, NULL);
 | 
	
		
			
				|  |  | -    tor_free(*lst);
 | 
	
		
			
				|  |  | -    *lst = newNicknames;
 | 
	
		
			
				|  |  | +    } SMARTLIST_FOREACH_END(s);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    SMARTLIST_FOREACH(sl, char *, s, tor_free(s));
 | 
	
		
			
				|  |  | +    smartlist_free(sl);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (!valid_line) {
 | 
	
		
			
				|  |  | +      config_free_lines(new_nicknames);
 | 
	
		
			
				|  |  | +      return -1;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  SMARTLIST_FOREACH(sl, char *, s, tor_free(s));
 | 
	
		
			
				|  |  | -  smartlist_free(sl);
 | 
	
		
			
				|  |  | +  // Replace the caller's nickname list with the normalized one
 | 
	
		
			
				|  |  | +  config_free_lines(*lst);
 | 
	
		
			
				|  |  | +  *lst = new_nicknames;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  return r;
 | 
	
		
			
				|  |  | +  return 0;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /** Learn config file name from command line arguments, or use the default.
 |