[tor-commits] [tor/master] Start on a command-parsing tool for controller commands.

dgoulet at torproject.org dgoulet at torproject.org
Tue Apr 30 15:57:51 UTC 2019


commit de70eebc65d40d50f877b0f82df4d05ce670faa5
Author: Nick Mathewson <nickm at torproject.org>
Date:   Tue Apr 2 08:17:22 2019 -0400

    Start on a command-parsing tool for controller commands.
    
    There _is_ an underlying logic to these commands, but it isn't
    wholly uniform, given years of tweaks and changes.  Fortunately I
    think there is a superset that will work.
    
    This commit adds a parser for some of the most basic cases -- the
    ones currently handled by getargs_helper() and some of the
    object-taking ones.  Soon will come initial tests; then I'll start using
    the parser.
    
    After that, I'll expand the parser to handle the other cases that come
    up in the controller protocol.
---
 src/core/include.am                       |   1 +
 src/feature/control/control_cmd.c         | 145 ++++++++++++++++++++++++++++--
 src/feature/control/control_cmd.h         |  39 ++++++++
 src/feature/control/control_cmd_args_st.h |  44 +++++++++
 4 files changed, 221 insertions(+), 8 deletions(-)

diff --git a/src/core/include.am b/src/core/include.am
index 982460172..b927f17a9 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -298,6 +298,7 @@ noinst_HEADERS +=					\
 	src/feature/control/control.h			\
 	src/feature/control/control_auth.h		\
 	src/feature/control/control_cmd.h		\
+	src/feature/control/control_cmd_args_st.h	\
 	src/feature/control/control_connection_st.h	\
 	src/feature/control/control_events.h	        \
 	src/feature/control/control_fmt.h		\
diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c
index 930276b10..aa0bef513 100644
--- a/src/feature/control/control_cmd.c
+++ b/src/feature/control/control_cmd.c
@@ -45,6 +45,7 @@
 #include "core/or/entry_connection_st.h"
 #include "core/or/origin_circuit_st.h"
 #include "core/or/socks_request_st.h"
+#include "feature/control/control_cmd_args_st.h"
 #include "feature/control/control_connection_st.h"
 #include "feature/nodelist/node_st.h"
 #include "feature/nodelist/routerinfo_st.h"
@@ -60,6 +61,87 @@ static int control_setconf_helper(control_connection_t *conn, uint32_t len,
  * finished authentication and is accepting commands. */
 #define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN)
 
+/**
+ * Release all storage held in <b>args</b>
+ **/
+void
+control_cmd_args_free_(control_cmd_args_t *args)
+{
+  if (! args)
+    return;
+
+  if (args->args) {
+    SMARTLIST_FOREACH(args->args, char *, c, tor_free(c));
+    smartlist_free(args->args);
+  }
+  tor_free(args->object);
+
+  tor_free(args);
+}
+
+/**
+ * Helper: parse the arguments to a command according to <b>syntax</b>.  On
+ * success, set *<b>error_out</b> to NULL and return a newly allocated
+ * control_cmd_args_t.  On failure, set *<b>error_out</b> to newly allocated
+ * error string, and return NULL.
+ **/
+STATIC control_cmd_args_t *
+control_cmd_parse_args(const char *command,
+                       const control_cmd_syntax_t *syntax,
+                       size_t body_len,
+                       const char *body,
+                       char **error_out)
+{
+  *error_out = NULL;
+  control_cmd_args_t *result = tor_malloc_zero(sizeof(control_cmd_args_t));
+  const char *cmdline;
+  char *cmdline_alloc = NULL;
+
+  result->command = command;
+
+  const char *eol = memchr(body, '\n', body_len);
+  if (syntax->want_object) {
+    if (! eol || (eol+1) == body+body_len) {
+      *error_out = tor_strdup("Empty body");
+      goto err;
+    }
+    cmdline_alloc = tor_memdup_nulterm(body, eol-body);
+    cmdline = cmdline_alloc;
+    ++eol;
+    result->object_len = read_escaped_data(eol, (body+body_len)-eol,
+                                           &result->object);
+  } else {
+    if (eol && (eol+1) != body+body_len) {
+      *error_out = tor_strdup("Unexpected body");
+      goto err;
+    }
+    cmdline = body;
+  }
+
+  result->args = smartlist_new();
+  smartlist_split_string(result->args, cmdline, " ",
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  size_t n_args = smartlist_len(result->args);
+  if (n_args < syntax->min_args) {
+    tor_asprintf(error_out, "Need at least %u argument(s)",
+                 syntax->min_args);
+    goto err;
+  } else if (n_args > syntax->max_args) {
+    tor_asprintf(error_out, "Cannot accept more than %u argument(s)",
+                 syntax->max_args);
+    goto err;
+  }
+
+  tor_assert_nonfatal(*error_out == NULL);
+  goto done;
+ err:
+  tor_assert_nonfatal(*error_out != NULL);
+  control_cmd_args_free(result);
+ done:
+  tor_free(cmdline_alloc);
+  return result;
+}
+
 /** Called when we receive a SETCONF message: parse the body and try
  * to update our configuration.  Reply with a DONE or ERROR message.
  * Modifies the contents of body.*/
@@ -2230,7 +2312,8 @@ handle_control_obsolete(control_connection_t *conn,
  **/
 typedef enum handler_type_t {
   hnd_legacy,
-  hnd_legacy_mut
+  hnd_legacy_mut,
+  hnd_parsed,
 } handler_type_t;
 
 /**
@@ -2257,6 +2340,13 @@ typedef union handler_fn_t {
   int (*legacy_mut)(control_connection_t *conn,
                     uint32_t arg_len,
                     char *args);
+
+  /**
+   * A "parsed" handler expects its arguments in a pre-parsed format, in
+   * an immutable control_cmd_args_t *object.
+   **/
+  int (*parsed)(control_connection_t *conn,
+                const control_cmd_args_t *args);
 } handler_fn_t;
 
 /**
@@ -2279,6 +2369,10 @@ typedef struct control_cmd_def_t {
    * Zero or more CMD_FL_* flags, or'd together.
    */
   unsigned flags;
+  /**
+   * For parsed command: a syntax description.
+   */
+  const control_cmd_syntax_t *syntax;
 } control_cmd_def_t;
 
 /**
@@ -2287,16 +2381,27 @@ typedef struct control_cmd_def_t {
  */
 #define CMD_FL_WIPE (1u<<0)
 
-/**
- * Macro: declare a command with a one-line argument and a given set of
- * flags.
+#define SYNTAX_IGNORE { 0, UINT_MAX, false }
+
+/** Macro: declare a command with a one-line argument, a given set of flags,
+ * and a syntax definition.
  **/
-#define ONE_LINE(name, htype, flags)                            \
+#define ONE_LINE_(name, htype, flags, syntax)                   \
   { #name,                                                      \
       hnd_ ##htype,                                             \
       { .htype = handle_control_ ##name },                      \
-      flags                                                     \
+      flags,                                                    \
+      syntax,                                                   \
   }
+
+/** Macro: declare a parsed command with a one-line argument, a given set of
+ * flags, and a syntax definition.
+ **/
+#define ONE_LINE(name, htype, flags) \
+  ONE_LINE_(name, htype, flags, NULL)
+#define ONE_LINE_PARSED(name, flags, syntax) \
+  ONE_LINE_(name, parsed, flags, syntax)
+
 /**
  * Macro: declare a command with a multi-line argument and a given set of
  * flags.
@@ -2305,7 +2410,8 @@ typedef struct control_cmd_def_t {
   { "+"#name,                                                   \
       hnd_ ##htype,                                             \
       { .htype = handle_control_ ##name },                      \
-      flags                                                     \
+      flags,                                                    \
+      NULL                                                      \
   }
 /**
  * Macro: declare an obsolete command. (Obsolete commands give a different
@@ -2315,7 +2421,8 @@ typedef struct control_cmd_def_t {
   { #name,                                      \
       hnd_legacy,                               \
       { .legacy = handle_control_obsolete },    \
-      0                                         \
+      0,                                        \
+      NULL,                                     \
   }
 
 /**
@@ -2379,6 +2486,28 @@ handle_single_control_command(const control_cmd_def_t *def,
       if (def->handler.legacy_mut(conn, cmd_data_len, args))
         rv = -1;
       break;
+    case hnd_parsed: {
+      control_cmd_args_t *parsed_args;
+      char *err=NULL;
+      tor_assert(def->syntax);
+      parsed_args = control_cmd_parse_args(conn->incoming_cmd,
+                                           def->syntax,
+                                           cmd_data_len, args,
+                                           &err);
+      if (!parsed_args) {
+        connection_printf_to_buf(conn,
+                                 "512 Bad arguments to %s: %s\r\n",
+                                 conn->incoming_cmd, err?err:"");
+        tor_free(err);
+      } else {
+        if (BUG(err))
+          tor_free(err);
+        if (def->handler.parsed(conn, parsed_args))
+          rv = 0;
+        control_cmd_args_free(parsed_args);
+      }
+      break;
+    }
     default:
       tor_assert_unreached();
   }
diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h
index a417e10da..1070a9edb 100644
--- a/src/feature/control/control_cmd.h
+++ b/src/feature/control/control_cmd.h
@@ -12,11 +12,19 @@
 #ifndef TOR_CONTROL_CMD_H
 #define TOR_CONTROL_CMD_H
 
+#include "lib/malloc/malloc.h"
+
 int handle_control_command(control_connection_t *conn,
                            uint32_t cmd_data_len,
                            char *args);
 void control_cmd_free_all(void);
 
+typedef struct control_cmd_args_t control_cmd_args_t;
+void control_cmd_args_free_(control_cmd_args_t *args);
+
+#define control_cmd_args_free(v) \
+  FREE_AND_NULL(control_cmd_args_t, control_cmd_args_free_, (v))
+
 #ifdef CONTROL_CMD_PRIVATE
 #include "lib/crypt_ops/crypto_ed25519.h"
 
@@ -39,6 +47,37 @@ STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk,
 STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg,
                                    int *created, char **err_msg_out);
 
+/**
+ * Definition for the syntax of a controller command, as parsed by
+ * control_cmd_parse_args.
+ *
+ * WORK IN PROGRESS: This structure is going to get more complex as this
+ * branch goes on.
+ **/
+typedef struct control_cmd_syntax_t {
+  /**
+   * Lowest number of positional arguments that this command accepts.
+   * 0 for "it's okay not to have positional arguments."
+   **/
+  unsigned int min_args;
+  /**
+   * Highest number of positional arguments that this command accepts.
+   * UINT_MAX for no limit.
+   **/
+  unsigned int max_args;
+  /**
+   * True iff this command wants to be followed by a multiline object.
+   **/
+  bool want_object;
+} control_cmd_syntax_t;
+
+STATIC control_cmd_args_t *control_cmd_parse_args(
+                                   const char *command,
+                                   const control_cmd_syntax_t *syntax,
+                                   size_t body_len,
+                                   const char *body,
+                                   char **error_out);
+
 #endif /* defined(CONTROL_CMD_PRIVATE) */
 
 #ifdef CONTROL_MODULE_PRIVATE
diff --git a/src/feature/control/control_cmd_args_st.h b/src/feature/control/control_cmd_args_st.h
new file mode 100644
index 000000000..9ecc5d750
--- /dev/null
+++ b/src/feature/control/control_cmd_args_st.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file control_cmd_args_st.h
+ * \brief Definition for control_cmd_args_t
+ **/
+
+#ifndef TOR_CONTROL_CMD_ST_H
+#define TOR_CONTROL_CMD_ST_H
+
+struct smartlist_t;
+struct config_line_t;
+
+/**
+ * Parsed arguments for a control command.
+ *
+ * WORK IN PROGRESS: This structure is going to get more complex as this
+ * branch goes on.
+ **/
+struct control_cmd_args_t {
+  /**
+   * The command itself, as provided by the controller.  Not owned by this
+   * structure.
+   **/
+  const char *command;
+  /**
+   * Positional arguments to the command.
+   **/
+  struct smartlist_t *args;
+  /**
+   * Number of bytes in <b>object</b>; 0 if <b>object</b> is not set.
+   **/
+  size_t object_len;
+  /**
+   * A multiline object passed with this command.
+   **/
+  char *object;
+};
+
+#endif /* !defined(TOR_CONTROL_CMD_ST_H) */





More information about the tor-commits mailing list