Explorar el Código

Merge branch 'bug4647_squashed'

Nick Mathewson hace 10 años
padre
commit
e35c972851
Se han modificado 10 ficheros con 495 adiciones y 89 borrados
  1. 18 0
      changes/bug4647
  2. 1 1
      doc/tor.1.txt
  3. 149 71
      src/or/config.c
  4. 9 1
      src/or/config.h
  5. 17 2
      src/or/confparse.c
  6. 3 1
      src/or/confparse.h
  7. 1 1
      src/or/control.c
  8. 53 10
      src/or/main.c
  9. 2 2
      src/or/or.h
  10. 242 0
      src/test/test_cmdline_args.py

+ 18 - 0
changes/bug4647

@@ -0,0 +1,18 @@
+  o Minor bugfixes:
+
+    - Use a single command-line parser for parsing torrc options on the
+      command line and for finding special command-line options to avoid
+      inconsistent behavior for torrc option arguments that have the same
+      names as command-line options. Fixes bugs 4647 and 9578; bugfix on
+      0.0.9pre5.
+
+    - No longer allow 'tor --hash-password' with no arguments. Fixes bug
+      9573; bugfix on 0.0.9pre5.
+
+  o Minor features:
+
+    - Support a --dump-config optoin to dump some or all of the configured
+      options. Mainly useful for debugging the command-line option parsing
+      code.
+
+

+ 1 - 1
doc/tor.1.txt

@@ -46,7 +46,7 @@ COMMAND-LINE OPTIONS
     configuration file, and by those on the command line. (Default:
     @CONFDIR@/torrc-defaults.)
 
-**--hash-password**::
+**--hash-password** __PASSWORD__::
     Generates a hashed password for control port access.
 
 **--list-fingerprint**::

+ 149 - 71
src/or/config.c

@@ -550,6 +550,9 @@ static int parse_outbound_addresses(or_options_t *options, int validate_only,
                                     char **msg);
 static void config_maybe_load_geoip_files_(const or_options_t *options,
                                            const or_options_t *old_options);
+static int options_validate_cb(void *old_options, void *options,
+                               void *default_options,
+                               int from_setconf, char **msg);
 
 /** Magic value for or_options_t. */
 #define OR_OPTIONS_MAGIC 9090909
@@ -561,7 +564,7 @@ STATIC config_format_t options_format = {
   STRUCT_OFFSET(or_options_t, magic_),
   option_abbrevs_,
   option_vars_,
-  (validate_fn_t)options_validate,
+  options_validate_cb,
   NULL
 };
 
@@ -724,6 +727,7 @@ or_options_free(or_options_t *options)
     smartlist_free(options->NodeFamilySets);
   }
   tor_free(options->BridgePassword_AuthDigest_);
+  tor_free(options->command_arg);
   config_free(&options_format, options);
 }
 
@@ -1790,40 +1794,64 @@ options_act(const or_options_t *old_options)
   return 0;
 }
 
-/** Helper: Read a list of configuration options from the command line.
- * If successful, put them in *<b>result</b> and return 0, and return
- * -1 and leave *<b>result</b> alone. */
-static int
-config_get_commandlines(int argc, char **argv, config_line_t **result)
+static const struct {
+  const char *name;
+  int takes_argument;
+} CMDLINE_ONLY_OPTIONS[] = {
+  { "-f",                     1 },
+  { "--defaults-torrc",       1 },
+  { "--hash-password",        1 },
+  { "--dump-config",          1 },
+  { "--list-fingerprint",     0 },
+  { "--verify-config",        0 },
+  { "--ignore-missing-torrc", 0 },
+  { "--quiet",                0 },
+  { "--hush",                 0 },
+  { "--version",              0 },
+  { "-h",                     0 },
+  { "--help",                 0 },
+  { "--list-torrc-options",   0 },
+  { "--digests",              0 },
+  { "--nt-service",           0 },
+  { "-nt-service",            0 },
+  { NULL, 0 },
+};
+
+/** Helper: Read a list of configuration options from the command line.  If
+ * successful, or if ignore_errors is set, put them in *<b>result</b>, put the
+ * commandline-only options in *<b>cmdline_result</b>, and return 0;
+ * otherwise, return -1 and leave *<b>result</b> and <b>cmdline_result</b>
+ * alone. */
+int
+config_parse_commandline(int argc, char **argv, int ignore_errors,
+                         config_line_t **result,
+                         config_line_t **cmdline_result)
 {
+  config_line_t *param = NULL;
+
   config_line_t *front = NULL;
   config_line_t **new = &front;
-  char *s;
+
+  config_line_t *front_cmdline = NULL;
+  config_line_t **new_cmdline = &front_cmdline;
+
+  char *s, *arg;
   int i = 1;
 
   while (i < argc) {
     unsigned command = CONFIG_LINE_NORMAL;
     int want_arg = 1;
+    int is_cmdline = 0;
+    int j;
 
-    if (!strcmp(argv[i],"-f") ||
-        !strcmp(argv[i],"--defaults-torrc") ||
-        !strcmp(argv[i],"--hash-password")) {
-      i += 2; /* command-line option with argument. ignore them. */
-      continue;
-    } else if (!strcmp(argv[i],"--list-fingerprint") ||
-               !strcmp(argv[i],"--verify-config") ||
-               !strcmp(argv[i],"--ignore-missing-torrc") ||
-               !strcmp(argv[i],"--quiet") ||
-               !strcmp(argv[i],"--hush")) {
-      i += 1; /* command-line option. ignore it. */
-      continue;
-    } else if (!strcmp(argv[i],"--nt-service") ||
-               !strcmp(argv[i],"-nt-service")) {
-      i += 1;
-      continue;
+    for (j = 0; CMDLINE_ONLY_OPTIONS[j].name != NULL; ++j) {
+      if (!strcmp(argv[i], CMDLINE_ONLY_OPTIONS[j].name)) {
+        is_cmdline = 1;
+        want_arg = CMDLINE_ONLY_OPTIONS[j].takes_argument;
+        break;
+      }
     }
 
-    *new = tor_malloc_zero(sizeof(config_line_t));
     s = argv[i];
 
     /* Each keyword may be prefixed with one or two dashes. */
@@ -1843,22 +1871,38 @@ config_get_commandlines(int argc, char **argv, config_line_t **result)
     }
 
     if (want_arg && i == argc-1) {
-      log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.",
-               argv[i]);
-      config_free_lines(front);
-      return -1;
+      if (ignore_errors) {
+        arg = strdup("");
+      } else {
+        log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.",
+            argv[i]);
+        config_free_lines(front);
+        config_free_lines(front_cmdline);
+        return -1;
+      }
+    } else {
+      arg = want_arg ? tor_strdup(argv[i+1]) : strdup("");
     }
 
-    (*new)->key = tor_strdup(config_expand_abbrev(&options_format, s, 1, 1));
-    (*new)->value = want_arg ? tor_strdup(argv[i+1]) : tor_strdup("");
-    (*new)->command = command;
-    (*new)->next = NULL;
+    param = tor_malloc_zero(sizeof(config_line_t));
+    param->key = is_cmdline ? tor_strdup(argv[i]) : tor_strdup(s);
+    param->value = arg;
+    param->command = command;
+    param->next = NULL;
     log_debug(LD_CONFIG, "command line: parsed keyword '%s', value '%s'",
-        (*new)->key, (*new)->value);
+        param->key, param->value);
+
+    if (is_cmdline) {
+      *new_cmdline = param;
+      new_cmdline = &((*new_cmdline)->next);
+    } else {
+      *new = param;
+      new = &((*new)->next);
+    }
 
-    new = &((*new)->next);
     i += want_arg ? 2 : 1;
   }
+  *cmdline_result = front_cmdline;
   *result = front;
   return 0;
 }
@@ -2225,10 +2269,29 @@ options_init(or_options_t *options)
  * include options that are the same as Tor's defaults.
  */
 char *
-options_dump(const or_options_t *options, int minimal)
+options_dump(const or_options_t *options, int how_to_dump)
 {
-  return config_dump(&options_format, global_default_options,
-                     options, minimal, 0);
+  const or_options_t *use_defaults;
+  int minimal;
+  switch (how_to_dump) {
+    case OPTIONS_DUMP_MINIMAL:
+      use_defaults = global_default_options;
+      minimal = 1;
+      break;
+    case OPTIONS_DUMP_DEFAULTS:
+      use_defaults = NULL;
+      minimal = 1;
+      break;
+    case OPTIONS_DUMP_ALL:
+      use_defaults = NULL;
+      minimal = 0;
+      break;
+    default:
+      log_warn(LD_BUG, "Bogus value for how_to_dump==%d", how_to_dump);
+      return NULL;
+  }
+
+  return config_dump(&options_format, use_defaults, options, minimal, 0);
 }
 
 /** Return 0 if every element of sl is a string holding a decimal
@@ -2345,6 +2408,14 @@ compute_publishserverdescriptor(or_options_t *options)
  * */
 #define RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT (10)
 
+static int
+options_validate_cb(void *old_options, void *options, void *default_options,
+                    int from_setconf, char **msg)
+{
+  return options_validate(old_options, options, default_options,
+                          from_setconf, msg);
+}
+
 /** Return 0 if every setting in <b>options</b> is reasonable, is a
  * permissible transition from <b>old_options</b>, and none of the
  * testing-only settings differ from <b>default_options</b> unless in
@@ -3703,26 +3774,26 @@ check_nickname_list(char **lst, const char *name, char **msg)
  * filename if it doesn't exist.
  */
 static char *
-find_torrc_filename(int argc, char **argv,
+find_torrc_filename(config_line_t *cmd_arg,
                     int defaults_file,
                     int *using_default_fname, int *ignore_missing_torrc)
 {
   char *fname=NULL;
-  int i;
+  config_line_t *p_index;
   const char *fname_opt = defaults_file ? "--defaults-torrc" : "-f";
   const char *ignore_opt = defaults_file ? NULL : "--ignore-missing-torrc";
 
   if (defaults_file)
     *ignore_missing_torrc = 1;
 
-  for (i = 1; i < argc; ++i) {
-    if (i < argc-1 && !strcmp(argv[i],fname_opt)) {
+  for (p_index = cmd_arg; p_index; p_index = p_index->next) {
+    if (!strcmp(p_index->key, fname_opt)) {
       if (fname) {
         log_warn(LD_CONFIG, "Duplicate %s options on command line.",
             fname_opt);
         tor_free(fname);
       }
-      fname = expand_filename(argv[i+1]);
+      fname = expand_filename(p_index->value);
 
       {
         char *absfname;
@@ -3732,8 +3803,7 @@ find_torrc_filename(int argc, char **argv,
       }
 
       *using_default_fname = 0;
-      ++i;
-    } else if (ignore_opt && !strcmp(argv[i],ignore_opt)) {
+    } else if (ignore_opt && !strcmp(p_index->key,ignore_opt)) {
       *ignore_missing_torrc = 1;
     }
   }
@@ -3770,7 +3840,7 @@ find_torrc_filename(int argc, char **argv,
  * Return the contents of the file on success, and NULL on failure.
  */
 static char *
-load_torrc_from_disk(int argc, char **argv, int defaults_file)
+load_torrc_from_disk(config_line_t *cmd_arg, int defaults_file)
 {
   char *fname=NULL;
   char *cf = NULL;
@@ -3778,7 +3848,7 @@ load_torrc_from_disk(int argc, char **argv, int defaults_file)
   int ignore_missing_torrc = 0;
   char **fname_var = defaults_file ? &torrc_defaults_fname : &torrc_fname;
 
-  fname = find_torrc_filename(argc, argv, defaults_file,
+  fname = find_torrc_filename(cmd_arg, defaults_file,
                               &using_default_torrc, &ignore_missing_torrc);
   tor_assert(fname);
   log_debug(LD_CONFIG, "Opening config file \"%s\"", fname);
@@ -3820,12 +3890,14 @@ int
 options_init_from_torrc(int argc, char **argv)
 {
   char *cf=NULL, *cf_defaults=NULL;
-  int i, command;
+  int command;
   int retval = -1;
   static char **backup_argv;
   static int backup_argc;
   char *command_arg = NULL;
   char *errmsg=NULL;
+  config_line_t *cmdline_only_options = NULL;
+  config_line_t *p_index = NULL;
 
   if (argv) { /* first time we're called. save command line args */
     backup_argv = argv;
@@ -3834,45 +3906,50 @@ options_init_from_torrc(int argc, char **argv)
     argv = backup_argv;
     argc = backup_argc;
   }
-  if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1],"--help"))) {
+
+  /* Go through command-line variables */
+  if (!global_cmdline_options) {
+    /* Or we could redo the list every time we pass this place.
+     * It does not really matter */
+    if (config_parse_commandline(argc, argv, 0, &global_cmdline_options,
+                                 &cmdline_only_options) < 0) {
+      goto err;
+    }
+  }
+
+  if (config_line_find(cmdline_only_options, "-h") ||
+      config_line_find(cmdline_only_options, "--help")) {
     print_usage();
     exit(0);
   }
-  if (argc > 1 && !strcmp(argv[1], "--list-torrc-options")) {
+  if (config_line_find(cmdline_only_options, "--list-torrc-options")) {
     /* For documenting validating whether we've documented everything. */
     list_torrc_options();
     exit(0);
   }
 
-  if (argc > 1 && (!strcmp(argv[1],"--version"))) {
+  if (config_line_find(cmdline_only_options, "--version")) {
     printf("Tor version %s.\n",get_version());
     exit(0);
   }
-  if (argc > 1 && (!strcmp(argv[1],"--digests"))) {
+  if (config_line_find(cmdline_only_options, "--digests")) {
     printf("Tor version %s.\n",get_version());
     printf("%s", libor_get_digests());
     printf("%s", tor_get_digests());
     exit(0);
   }
 
-  /* Go through command-line variables */
-  if (!global_cmdline_options) {
-    /* Or we could redo the list every time we pass this place.
-     * It does not really matter */
-    if (config_get_commandlines(argc, argv, &global_cmdline_options) < 0) {
-      goto err;
-    }
-  }
-
   command = CMD_RUN_TOR;
-  for (i = 1; i < argc; ++i) {
-    if (!strcmp(argv[i],"--list-fingerprint")) {
+  for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
+    if (!strcmp(p_index->key,"--list-fingerprint")) {
       command = CMD_LIST_FINGERPRINT;
-    } else if (!strcmp(argv[i],"--hash-password")) {
+    } else if (!strcmp(p_index->key, "--hash-password")) {
       command = CMD_HASH_PASSWORD;
-      command_arg = tor_strdup( (i < argc-1) ? argv[i+1] : "");
-      ++i;
-    } else if (!strcmp(argv[i],"--verify-config")) {
+      command_arg = p_index->value;
+    } else if (!strcmp(p_index->key, "--dump-config")) {
+      command = CMD_DUMP_CONFIG;
+      command_arg = p_index->value;
+    } else if (!strcmp(p_index->key, "--verify-config")) {
       command = CMD_VERIFY_CONFIG;
     }
   }
@@ -3881,8 +3958,8 @@ options_init_from_torrc(int argc, char **argv)
     cf_defaults = tor_strdup("");
     cf = tor_strdup("");
   } else {
-    cf_defaults = load_torrc_from_disk(argc, argv, 1);
-    cf = load_torrc_from_disk(argc, argv, 0);
+    cf_defaults = load_torrc_from_disk(cmdline_only_options, 1);
+    cf = load_torrc_from_disk(cmdline_only_options, 0);
     if (!cf)
       goto err;
   }
@@ -3894,6 +3971,7 @@ options_init_from_torrc(int argc, char **argv)
 
   tor_free(cf);
   tor_free(cf_defaults);
+  config_free_lines(cmdline_only_options);
   if (errmsg) {
     log_warn(LD_CONFIG,"%s", errmsg);
     tor_free(errmsg);
@@ -3928,7 +4006,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
   newoptions->magic_ = OR_OPTIONS_MAGIC;
   options_init(newoptions);
   newoptions->command = command;
-  newoptions->command_arg = command_arg;
+  newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
 
   for (i = 0; i < 2; ++i) {
     const char *body = i==0 ? cf_defaults : cf;
@@ -3992,7 +4070,7 @@ options_init_from_string(const char *cf_defaults, const char *cf,
     newoptions->magic_ = OR_OPTIONS_MAGIC;
     options_init(newoptions);
     newoptions->command = command;
-    newoptions->command_arg = command_arg;
+    newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
 
     /* Assign all options a second time. */
     for (i = 0; i < 2; ++i) {
@@ -6096,7 +6174,7 @@ write_configuration_file(const char *fname, const or_options_t *options)
       return -1;
   }
 
-  if (!(new_conf = options_dump(options, 1))) {
+  if (!(new_conf = options_dump(options, OPTIONS_DUMP_MINIMAL))) {
     log_warn(LD_BUG, "Couldn't get configuration string");
     goto err;
   }

+ 9 - 1
src/or/config.h

@@ -32,7 +32,11 @@ int resolve_my_address(int warn_severity, const or_options_t *options,
                        const char **method_out, char **hostname_out);
 int is_local_addr(const tor_addr_t *addr);
 void options_init(or_options_t *options);
-char *options_dump(const or_options_t *options, int minimal);
+
+#define OPTIONS_DUMP_MINIMAL 1
+#define OPTIONS_DUMP_DEFAULTS 2
+#define OPTIONS_DUMP_ALL 3
+char *options_dump(const or_options_t *options, int how_to_dump);
 int options_init_from_torrc(int argc, char **argv);
 setopt_err_t options_init_from_string(const char *cf_defaults, const char *cf,
                             int command, const char *command_arg, char **msg);
@@ -96,6 +100,10 @@ int init_cookie_authentication(const char *fname, const char *header,
 
 or_options_t *options_new(void);
 
+int config_parse_commandline(int argc, char **argv, int ignore_errors,
+                             config_line_t **result,
+                             config_line_t **cmdline_result);
+
 void config_register_addressmaps(const or_options_t *options);
 /* XXXX024 move to connection_edge.h */
 int addressmap_register_auto(const char *from, const char *to,

+ 17 - 2
src/or/confparse.c

@@ -79,6 +79,21 @@ config_line_append(config_line_t **lst,
   (*lst) = newline;
 }
 
+/** Return the line in <b>lines</b> whose key is exactly <b>key</b>, or NULL
+ * if no such key exists. 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;
+}
+
 /** Helper: parse the config string and strdup into key/value
  * strings. Set *result to the list, or NULL if parsing the string
  * failed.  Return 0 on success, -1 on failure. Warn and ignore any
@@ -1059,8 +1074,8 @@ config_dump(const config_format_t *fmt, const void *default_options,
 
   /* XXX use a 1 here so we don't add a new log line while dumping */
   if (default_options == NULL) {
-    if (fmt->validate_fn(NULL, defaults_tmp, 1, &msg) < 0) {
-      log_err(LD_BUG, "Failed to validate default config.");
+    if (fmt->validate_fn(NULL, defaults_tmp, defaults_tmp, 1, &msg) < 0) {
+      log_err(LD_BUG, "Failed to validate default config: %s", msg);
       tor_free(msg);
       tor_assert(0);
     }

+ 3 - 1
src/or/confparse.h

@@ -71,7 +71,7 @@ typedef struct config_var_description_t {
 /** Type of a callback to validate whether a given configuration is
  * well-formed and consistent. See options_trial_assign() for documentation
  * of arguments. */
-typedef int (*validate_fn_t)(void*,void*,int,char**);
+typedef int (*validate_fn_t)(void*,void*,void*,int,char**);
 
 /** Information on the keys, value types, key-to-struct-member mappings,
  * variable descriptions, validation functions, and abbreviations for a
@@ -103,6 +103,8 @@ void *config_new(const config_format_t *fmt);
 void config_line_append(config_line_t **lst,
                         const char *key, const char *val);
 config_line_t *config_lines_dup(const config_line_t *inp);
+const config_line_t *config_line_find(const config_line_t *lines,
+                                      const char *key);
 void config_free(const config_format_t *fmt, void *options);
 int config_lines_eq(config_line_t *a, config_line_t *b);
 int config_count_key(const config_line_t *a, const char *key);

+ 1 - 1
src/or/control.c

@@ -1406,7 +1406,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question,
   } else if (!strcmp(question, "config-defaults-file")) {
     *answer = tor_strdup(get_torrc_fname(1));
   } else if (!strcmp(question, "config-text")) {
-    *answer = options_dump(get_options(), 1);
+    *answer = options_dump(get_options(), OPTIONS_DUMP_MINIMAL);
   } else if (!strcmp(question, "info/names")) {
     *answer = list_getinfo_options();
   } else if (!strcmp(question, "dormant")) {

+ 53 - 10
src/or/main.c

@@ -21,6 +21,7 @@
 #include "circuituse.h"
 #include "command.h"
 #include "config.h"
+#include "confparse.h"
 #include "connection.h"
 #include "connection_edge.h"
 #include "connection_or.h"
@@ -2325,7 +2326,7 @@ int
 tor_init(int argc, char *argv[])
 {
   char buf[256];
-  int i, quiet = 0;
+  int quiet = 0;
   time_of_process_start = time(NULL);
   init_connection_lists();
   /* Have the log set up with our application name. */
@@ -2338,17 +2339,28 @@ tor_init(int argc, char *argv[])
   addressmap_init(); /* Init the client dns cache. Do it always, since it's
                       * cheap. */
 
+  {
   /* We search for the "quiet" option first, since it decides whether we
    * will log anything at all to the command line. */
-  for (i=1;i<argc;++i) {
-    if (!strcmp(argv[i], "--hush"))
-      quiet = 1;
-    if (!strcmp(argv[i], "--quiet"))
-      quiet = 2;
-    /* --version implies --quiet */
-    if (!strcmp(argv[i], "--version"))
-      quiet = 2;
+    config_line_t *opts = NULL, *cmdline_opts = NULL;
+    const config_line_t *cl;
+    (void) config_parse_commandline(argc, argv, 1, &opts, &cmdline_opts);
+    for (cl = cmdline_opts; cl; cl = cl->next) {
+      if (!strcmp(cl->key, "--hush"))
+        quiet = 1;
+      if (!strcmp(cl->key, "--quiet") ||
+          !strcmp(cl->key, "--dump-config"))
+        quiet = 2;
+      /* --version, --digests, and --help imply --husth */
+      if (!strcmp(cl->key, "--version") || !strcmp(cl->key, "--digests") ||
+          !strcmp(cl->key, "--list-torrc-options") ||
+          !strcmp(cl->key, "-h") || !strcmp(cl->key, "--help"))
+        quiet = 1;
+    }
+    config_free_lines(opts);
+    config_free_lines(cmdline_opts);
   }
+
  /* give it somewhere to log to initially */
   switch (quiet) {
     case 2:
@@ -2592,7 +2604,7 @@ do_list_fingerprint(void)
   const char *nickname = get_options()->Nickname;
   if (!server_mode(get_options())) {
     log_err(LD_GENERAL,
-            "Clients don't have long-term identity keys. Exiting.\n");
+            "Clients don't have long-term identity keys. Exiting.");
     return -1;
   }
   tor_assert(nickname);
@@ -2630,6 +2642,34 @@ do_hash_password(void)
   printf("16:%s\n",output);
 }
 
+/** Entry point for configuration dumping: write the configuration to
+ * stdout. */
+static int
+do_dump_config(void)
+{
+  const or_options_t *options = get_options();
+  const char *arg = options->command_arg;
+  int how;
+  char *opts;
+  if (!strcmp(arg, "short")) {
+    how = OPTIONS_DUMP_MINIMAL;
+  } else if (!strcmp(arg, "non-builtin")) {
+    how = OPTIONS_DUMP_DEFAULTS;
+  } else if (!strcmp(arg, "full")) {
+    how = OPTIONS_DUMP_ALL;
+  } else {
+    printf("%s is not a recognized argument to --dump-config. "
+           "Please select 'short', 'non-builtin', or 'full'", arg);
+    return -1;
+  }
+
+  opts = options_dump(options, how);
+  printf("%s", opts);
+  tor_free(opts);
+
+  return 0;
+}
+
 #if defined (WINCE)
 int
 find_flashcard_path(PWCHAR path, size_t size)
@@ -2844,6 +2884,9 @@ tor_main(int argc, char *argv[])
     printf("Configuration was valid\n");
     result = 0;
     break;
+  case CMD_DUMP_CONFIG:
+    result = do_dump_config();
+    break;
   case CMD_RUN_UNITTESTS: /* only set by test.c */
   default:
     log_warn(LD_BUG,"Illegal command number %d: internal error.",

+ 2 - 2
src/or/or.h

@@ -3398,9 +3398,9 @@ typedef struct {
   /** What should the tor process actually do? */
   enum {
     CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD,
-    CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS
+    CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG
   } command;
-  const char *command_arg; /**< Argument for command-line option. */
+  char *command_arg; /**< Argument for command-line option. */
 
   config_line_t *Logs; /**< New-style list of configuration lines
                         * for logs */

+ 242 - 0
src/test/test_cmdline_args.py

@@ -0,0 +1,242 @@
+#!/usr/bin/python
+
+import binascii
+import hashlib
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+import unittest
+
+TOR = "./src/or/tor-cov"
+TOPDIR = "."
+
+class UnexpectedSuccess(Exception):
+    pass
+
+class UnexpectedFailure(Exception):
+    pass
+
+def contents(fn):
+    f = open(fn)
+    try:
+        return f.read()
+    finally:
+        f.close()
+
+def run_tor(args, failure=False):
+    p = subprocess.Popen([TOR] + args, stdout=subprocess.PIPE)
+    output, _ = p.communicate()
+    result = p.poll()
+    if result and not failure:
+        raise UnexpectedFailure()
+    elif not result and failure:
+        raise UnexpectedSuccess()
+    return output
+
+def spaceify_fp(fp):
+    for i in xrange(0, len(fp), 4):
+        yield fp[i:i+4]
+
+def lines(s):
+    out = s.split("\n")
+    if out and out[-1] == '':
+        del out[-1]
+    return out
+
+def strip_log_junk(line):
+    m = re.match(r'([^\[]+\[[a-z]*\] *)(.*)', line)
+    if not m:
+        return ""+line
+    return m.group(2).strip()
+
+class CmdlineTests(unittest.TestCase):
+
+    def test_version(self):
+        out = run_tor(["--version"])
+        self.failUnless(out.startswith("Tor version "))
+        self.assertEquals(len(lines(out)), 1)
+
+    def test_quiet(self):
+        out = run_tor(["--quiet", "--quumblebluffin", "1"], failure=True)
+        self.assertEquals(out, "")
+
+    def test_help(self):
+        out = run_tor(["--help"], failure=False)
+        out2 = run_tor(["-h"], failure=False)
+        self.assert_(out.startswith("Copyright (c) 2001"))
+        self.assert_(out.endswith(
+            "tor -f <torrc> [args]\n"
+            "See man page for options, or https://www.torproject.org/ for documentation.\n"))
+        self.assert_(out == out2)
+
+    def test_hush(self):
+        torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc.close()
+        try:
+            out = run_tor(["--hush", "-f", torrc.name,
+                           "--quumblebluffin", "1"], failure=True)
+        finally:
+            os.unlink(torrc.name)
+        self.assertEquals(len(lines(out)), 2)
+        ln = [ strip_log_junk(l) for l in lines(out) ]
+        self.assertEquals(ln[0], "Failed to parse/validate config: Unknown option 'quumblebluffin'.  Failing.")
+        self.assertEquals(ln[1], "Reading config failed--see warnings above.")
+
+    def test_missing_argument(self):
+        out = run_tor(["--hush", "--hash-password"], failure=True)
+        self.assertEquals(len(lines(out)), 2)
+        ln = [ strip_log_junk(l) for l in lines(out) ]
+        self.assertEquals(ln[0], "Command-line option '--hash-password' with no value. Failing.")
+
+    def test_hash_password(self):
+        out = run_tor(["--hash-password", "woodwose"])
+        result = lines(out)[-1]
+        self.assertEquals(result[:3], "16:")
+        self.assertEquals(len(result), 61)
+        r = binascii.a2b_hex(result[3:])
+        self.assertEquals(len(r), 29)
+
+        salt, how, hashed = r[:8], r[8], r[9:]
+        self.assertEquals(len(hashed), 20)
+
+        count = (16 + (ord(how) & 15)) << ((ord(how) >> 4) + 6)
+        stuff = salt + "woodwose"
+        repetitions = count // len(stuff) + 1
+        inp = stuff * repetitions
+        inp = inp[:count]
+
+        self.assertEquals(hashlib.sha1(inp).digest(), hashed)
+
+    def test_digests(self):
+        main_c = os.path.join(TOPDIR, "src", "or", "main.c")
+
+        if os.stat(TOR).st_mtime < os.stat(main_c).st_mtime:
+            self.skipTest(TOR+" not up to date")
+        out = run_tor(["--digests"])
+        main_line = [ l for l in lines(out) if l.endswith("/main.c") ]
+        digest, name = main_line[0].split()
+        actual = hashlib.sha1(open(main_c).read()).hexdigest()
+        self.assertEquals(digest, actual)
+
+    def test_dump_options(self):
+        default_torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc.write("SocksPort 9999")
+        torrc.close()
+        default_torrc.write("SafeLogging 0")
+        default_torrc.close()
+        out_sh = out_nb = out_fl = None
+        opts = [ "-f", torrc.name,
+                 "--defaults-torrc", default_torrc.name ]
+        try:
+            out_sh = run_tor(["--dump-config", "short"]+opts)
+            out_nb = run_tor(["--dump-config", "non-builtin"]+opts)
+            out_fl = run_tor(["--dump-config", "full"]+opts)
+            out_nr = run_tor(["--dump-config", "bliznert"]+opts,
+                             failure=True)
+
+            out_verif = run_tor(["--verify-config"]+opts)
+        finally:
+            os.unlink(torrc.name)
+            os.unlink(default_torrc.name)
+
+        self.assertEquals(len(lines(out_sh)), 2)
+        self.assert_(lines(out_sh)[0].startswith("DataDirectory "))
+        self.assertEquals(lines(out_sh)[1:],
+            [ "SocksPort 9999" ])
+
+        self.assertEquals(len(lines(out_nb)), 2)
+        self.assertEquals(lines(out_nb),
+            [ "SafeLogging 0",
+              "SocksPort 9999" ])
+
+        out_fl = lines(out_fl)
+        self.assert_(len(out_fl) > 100)
+        self.assertIn("SocksPort 9999", out_fl)
+        self.assertIn("SafeLogging 0", out_fl)
+        self.assertIn("ClientOnly 0", out_fl)
+
+        self.assert_(out_verif.endswith("Configuration was valid\n"))
+
+    def test_list_fingerprint(self):
+        tmpdir = tempfile.mkdtemp(prefix='ttca_')
+        torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc.write("ORPort 9999\n")
+        torrc.write("DataDirectory %s\n"%tmpdir)
+        torrc.write("Nickname tippi")
+        torrc.close()
+        opts = ["-f", torrc.name]
+        try:
+            out = run_tor(["--list-fingerprint"]+opts)
+            fp = contents(os.path.join(tmpdir, "fingerprint"))
+        finally:
+            os.unlink(torrc.name)
+            shutil.rmtree(tmpdir)
+
+        out = lines(out)
+        lastlog = strip_log_junk(out[-2])
+        lastline = out[-1]
+        fp = fp.strip()
+        nn_fp = fp.split()[0]
+        space_fp = " ".join(spaceify_fp(fp.split()[1]))
+        self.assertEquals(lastlog,
+              "Your Tor server's identity key fingerprint is '%s'"%fp)
+        self.assertEquals(lastline, "tippi %s"%space_fp)
+        self.assertEquals(nn_fp, "tippi")
+
+    def test_list_options(self):
+        out = lines(run_tor(["--list-torrc-options"]))
+        self.assert_(len(out)>100)
+        self.assert_(out[0] <= 'AccountingMax')
+        self.assert_("UseBridges" in out)
+        self.assert_("SocksPort" in out)
+
+    def test_cmdline_args(self):
+        default_torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc = tempfile.NamedTemporaryFile(delete=False)
+        torrc.write("SocksPort 9999\n")
+        torrc.write("SocksPort 9998\n")
+        torrc.write("ORPort 9000\n")
+        torrc.write("ORPort 9001\n")
+        torrc.write("Nickname eleventeen\n")
+        torrc.write("ControlPort 9500\n")
+        torrc.close()
+        default_torrc.write("")
+        default_torrc.close()
+        out_sh = out_nb = out_fl = None
+        opts = [ "-f", torrc.name,
+                 "--defaults-torrc", default_torrc.name,
+                 "--dump-config", "short" ]
+        try:
+            out_1 = run_tor(opts)
+            out_2 = run_tor(opts+["+ORPort", "9003",
+                                  "SocksPort", "9090",
+                                  "/ControlPort",
+                                  "/TransPort",
+                                  "+ExtORPort", "9005"])
+        finally:
+            os.unlink(torrc.name)
+            os.unlink(default_torrc.name)
+
+        out_1 = [ l for l in lines(out_1) if not l.startswith("DataDir") ]
+        out_2 = [ l for l in lines(out_2) if not l.startswith("DataDir") ]
+
+        self.assertEquals(out_1,
+                          ["ControlPort 9500",
+                           "Nickname eleventeen",
+                           "ORPort 9000",
+                           "ORPort 9001",
+                           "SocksPort 9999",
+                           "SocksPort 9998"])
+        self.assertEquals(out_2,
+                          ["ExtORPort 9005",
+                           "Nickname eleventeen",
+                           "ORPort 9000",
+                           "ORPort 9001",
+                           "ORPort 9003",
+                           "SocksPort 9090"])
+
+if __name__ == '__main__':
+    unittest.main()