Bump version to upstream-1.4.1
authorSangwan Kwon <sangwan.kwon@samsung.com>
Tue, 10 Feb 2015 02:18:22 +0000 (18:18 -0800)
committerSangwan Kwon <sangwan.kwon@samsung.com>
Wed, 12 Jun 2019 02:32:37 +0000 (11:32 +0900)
- Add distributed query feature

Signed-off-by: Sangwan Kwon <sangwan.kwon@samsung.com>
57 files changed:
include/osquery/dispatcher.h
include/osquery/events.h
include/osquery/extensions.h
include/osquery/filesystem.h
include/osquery/registry.h
include/osquery/sql.h
include/osquery/tables.h
osquery.thrift
osquery/CMakeLists.txt
osquery/core/system.cpp
osquery/core/tables.cpp
osquery/dispatcher/dispatcher.cpp
osquery/distributed/CMakeLists.txt [new file with mode: 0644]
osquery/distributed/distributed.cpp [new file with mode: 0644]
osquery/distributed/distributed.h [new file with mode: 0644]
osquery/distributed/distributed_tests.cpp [new file with mode: 0644]
osquery/events/events.cpp
osquery/events/linux/inotify.cpp
osquery/events/linux/inotify.h
osquery/events/linux/udev.cpp
osquery/events/linux/udev.h
osquery/extensions/extensions.cpp
osquery/extensions/extensions_tests.cpp
osquery/filesystem/filesystem.cpp
osquery/filesystem/linux/proc.cpp
osquery/main/run.cpp
osquery/registry/registry.cpp
osquery/registry/registry_tests.cpp
osquery/sql/sql.cpp
osquery/sql/sql_tests.cpp
osquery/sql/sqlite_util.cpp
osquery/sql/sqlite_util.h
osquery/sql/sqlite_util_tests.cpp
osquery/sql/virtual_table.cpp
osquery/sql/virtual_table_tests.cpp
osquery/tables/networking/linux/process_open_sockets.cpp
osquery/tables/specs/linux/process_memory_map.table
osquery/tables/specs/x/crontab.table
osquery/tables/specs/x/etc_services.table
osquery/tables/specs/x/file.table
osquery/tables/specs/x/hash.table
osquery/tables/specs/x/last.table
osquery/tables/specs/x/logged_in_users.table
osquery/tables/specs/x/osquery_extensions.table [new file with mode: 0644]
osquery/tables/specs/x/osquery_info.table
osquery/tables/specs/x/passwd_changes.table
osquery/tables/specs/x/process_envs.table
osquery/tables/specs/x/process_open_sockets.table
osquery/tables/specs/x/processes.table
osquery/tables/system/linux/acpi_tables.cpp
osquery/tables/system/linux/processes.cpp
osquery/tables/system/linux/shared_memory.cpp
osquery/tables/templates/default.cpp.in
osquery/tables/utility/file.cpp
osquery/tables/utility/hash.cpp
osquery/tables/utility/osquery.cpp
packaging/osquery.spec

index 07c3eff..edad9d9 100644 (file)
@@ -51,8 +51,6 @@ class InternalRunnable : public apache::thrift::concurrency::Runnable {
   /// Check if the thread's entrypoint (run) executed, meaning thread context
   /// was allocated.
   bool hasRun() { return run_; }
-  /// Sleep in a boost::thread interruptable state.
-  void interruptableSleep(size_t milli);
 
  protected:
   /// Require the runnable thread define an entrypoint.
@@ -115,6 +113,7 @@ class Dispatcher {
    */
   Status add(std::shared_ptr<InternalRunnable> task);
 
+  /// See `add`, but services are not limited to a thread poll size.
   Status addService(std::shared_ptr<InternalRunnable> service);
 
   /**
@@ -146,8 +145,10 @@ class Dispatcher {
    */
   void join();
 
+  /// See `join`, but applied to osquery services.
   void joinServices();
 
+  /// Destroy and stop all osquery service threads and service objects.
   void removeServices();
 
   /**
@@ -247,7 +248,12 @@ class Dispatcher {
    * @see getThreadManager
    */
   InternalThreadManagerRef thread_manager_;
+  /// The set of shared osquery service threads.
   std::vector<std::shared_ptr<boost::thread> > service_threads_;
+  /// THe set of shared osquery services.
   std::vector<std::shared_ptr<InternalRunnable> > services_;
 };
+
+/// Sleep in a boost::thread interruptable state.
+void interruptableSleep(size_t milli);
 }
index e500905..de943a9 100644 (file)
@@ -21,6 +21,7 @@
 #include <boost/thread/mutex.hpp>
 
 #include <osquery/database.h>
+#include <osquery/dispatcher.h>
 #include <osquery/registry.h>
 #include <osquery/status.h>
 #include <osquery/tables.h>
index 37d9a27..6fa4209 100644 (file)
@@ -26,8 +26,6 @@ namespace osquery {
 
 DECLARE_string(extensions_socket);
 
-namespace extensions {
-
 /**
  * @brief Helper struct for managing extenion metadata.
  *
@@ -37,8 +35,30 @@ struct ExtensionInfo {
   std::string name;
   std::string version;
   std::string sdk_version;
+
+  ExtensionInfo& operator=(const extensions::InternalExtensionInfo& iei) {
+    name = iei.name;
+    version = iei.version;
+    sdk_version = iei.sdk_version;
+    return *this;
+  }
+
+  ExtensionInfo() {}
+  ExtensionInfo(const std::string& name) : name(name) {
+    version = OSQUERY_VERSION;
+    sdk_version = OSQUERY_VERSION;
+  }
 };
 
+typedef std::map<RouteUUID, ExtensionInfo> ExtensionList;
+
+inline std::string getExtensionSocket(
+    RouteUUID uuid, const std::string& path = FLAGS_extensions_socket) {
+  return path + "." + std::to_string(uuid);
+}
+
+namespace extensions {
+
 /**
  * @brief The Thrift API server used by an osquery Extension process.
  *
@@ -85,7 +105,7 @@ class ExtensionManagerHandler : virtual public ExtensionManagerIf,
   ExtensionManagerHandler() {}
 
   /// Return a list of Route UUIDs and extension metadata.
-  void extensions(ExtensionList& _return) { _return = extensions_; }
+  void extensions(InternalExtensionList& _return) { _return = extensions_; }
 
   /**
    * @brief Request a Route UUID and advertise a set of Registry routes.
@@ -123,7 +143,7 @@ class ExtensionManagerHandler : virtual public ExtensionManagerIf,
   bool exists(const std::string& name);
 
   /// Maintain a map of extension UUID to metadata for tracking deregistrations.
-  ExtensionList extensions_;
+  InternalExtensionList extensions_;
 };
 }
 
@@ -161,7 +181,7 @@ class ExtensionRunner : public InternalRunnable {
  public:
   virtual ~ExtensionRunner();
   ExtensionRunner(const std::string& manager_path, RouteUUID uuid) {
-    path_ = manager_path + "." + std::to_string(uuid);
+    path_ = getExtensionSocket(uuid, manager_path);
     uuid_ = uuid;
   }
 
@@ -194,6 +214,16 @@ class ExtensionManagerRunner : public InternalRunnable {
   std::string path_;
 };
 
+/// Status get a list of active extenions.
+Status getExtensions(ExtensionList& extensions);
+
+/// Internal getExtensions using a UNIX domain socket path.
+Status getExtensions(const std::string& manager_path,
+                     ExtensionList& extensions);
+
+/// Ping an extension manager or extension.
+Status pingExtension(const std::string& path);
+
 /**
  * @brief Call a Plugin exposed by an Extension Registry route.
  *
index 2006744..5a3de6f 100644 (file)
@@ -155,6 +155,9 @@ Status isDirectory(const boost::filesystem::path& path);
  */
 std::vector<boost::filesystem::path> getHomeDirectories();
 
+/// Return bit-mask-style permissions.
+std::string lsperms(int mode);
+
 #ifdef __APPLE__
 /**
  * @brief Parse a property list on disk into a property tree.
index 75f4b28..0e29c3b 100644 (file)
@@ -111,7 +111,7 @@ class Plugin {
   /// The plugin may perform some tear down, release, not required.
   virtual void tearDown() {}
   /// The plugin may publish route info (other than registry type and name).
-  virtual RouteInfo routeInfo() {
+  virtual RouteInfo routeInfo() const {
     RouteInfo info;
     return info;
   }
@@ -150,6 +150,7 @@ class RegistryHelperCore {
 
  public:
   RegistryHelperCore(bool auto_setup = true) : auto_setup_(auto_setup) {}
+  virtual ~RegistryHelperCore() {}
 
   /**
    * @brief Remove a registry item by its identifier.
@@ -255,13 +256,11 @@ class RegistryHelper : public RegistryHelperCore {
       return Status(1, "Duplicate registry item exists: " + item_name);
     }
 
-    // Run the item's constructor, the setUp call will happen later.
-    auto item = (RegistryType*)new Item();
+    // Cast the specific registry-type derived item as the API type of the
+    // registry used when created using the registry factory.
+    std::shared_ptr<RegistryType> item((RegistryType*)new Item());
     item->setName(item_name);
-    // Cast the specific registry-type derived item as the API typ the registry
-    // used when it was created using the registry factory.
-    std::shared_ptr<RegistryType> shared_item(item);
-    items_[item_name] = shared_item;
+    items_[item_name] = item;
     return Status(0, "OK");
   }
 
@@ -274,11 +273,11 @@ class RegistryHelper : public RegistryHelperCore {
    * @param item_name An identifier for this registry plugin.
    * @return A std::shared_ptr of type RegistryType.
    */
-  RegistryTypeRef get(const std::string& item_name) {
+  RegistryTypeRef get(const std::string& item_name) const {
     return std::dynamic_pointer_cast<RegistryType>(items_.at(item_name));
   }
 
-  const std::map<std::string, RegistryTypeRef> all() {
+  const std::map<std::string, RegistryTypeRef> all() const {
     std::map<std::string, RegistryTypeRef> ditems;
     for (const auto& item : items_) {
       ditems[item.first] = std::dynamic_pointer_cast<RegistryType>(item.second);
@@ -326,10 +325,9 @@ class RegistryFactory : private boost::noncopyable {
       return 0;
     }
 
-    auto registry = (PluginRegistryHelper*)new RegistryHelper<Type>(auto_setup);
+    PluginRegistryHelperRef registry((PluginRegistryHelper*)new RegistryHelper<Type>(auto_setup));
     registry->setName(registry_name);
-    PluginRegistryHelperRef shared_registry(registry);
-    instance().registries_[registry_name] = shared_registry;
+    instance().registries_[registry_name] = registry;
     return 0;
   }
 
index 692937d..b83803c 100644 (file)
@@ -3,7 +3,7 @@
  *  All rights reserved.
  *
  *  This source code is licensed under the BSD-style license found in the
- *  LICENSE file in the root directory of this source tree. An additional grant 
+ *  LICENSE file in the root directory of this source tree. An additional grant
  *  of patent rights can be found in the PATENTS file in the same directory.
  *
  */
@@ -50,7 +50,7 @@ class SQL {
    *
    * @return A QueryData object of the query results
    */
-  QueryData rows();
+  const QueryData& rows();
 
   /**
    * @brief Accessor to switch off of when checking the success of a query
@@ -60,6 +60,13 @@ class SQL {
   bool ok();
 
   /**
+   * @brief Get the status returned by the query
+   *
+   * @return The query status
+   */
+  Status getStatus();
+
+  /**
    * @brief Accessor for the message string indicating the status of the query
    *
    * @return The message string indicating the status of the query
@@ -67,6 +74,15 @@ class SQL {
   std::string getMessageString();
 
   /**
+   * @brief Add host info columns onto existing QueryData
+   *
+   * Use this to add columns providing host info to the query results.
+   * Distributed queries use this to add host information before returning
+   * results to the aggregator.
+   */
+  void annotateHostInfo();
+
+  /**
    * @brief Accessor for the list of queryable tables
    *
    * @return A vector of table names
@@ -96,7 +112,7 @@ class SQL {
                                  tables::ConstraintOperator op,
                                  const std::string& expr);
 
- private:
+ protected:
   /**
    * @brief Private default constructor
    *
@@ -104,7 +120,9 @@ class SQL {
    */
   SQL(){};
 
- private:
+  // The key used to store hostname for annotateHostInfo
+  static const std::string kHostColumnName;
+
   /// the internal member which holds the results of the query
   QueryData results_;
 
@@ -152,4 +170,18 @@ Status query(const std::string& query, QueryData& results);
  * @return status indicating success or failure of the operation
  */
 Status getQueryColumns(const std::string& q, tables::TableColumns& columns);
+
+/*
+ * @brief A mocked subclass of SQL useful for testing
+ */
+class MockSQL : public SQL {
+ public:
+  explicit MockSQL() : MockSQL({}) {}
+  explicit MockSQL(const QueryData& results) : MockSQL(results, Status()) {}
+  explicit MockSQL(const QueryData& results, const Status& status) {
+    results_ = results;
+    status_ = status;
+  }
+};
+
 }
index 4b96c44..7115f79 100644 (file)
@@ -124,7 +124,7 @@ struct ConstraintList {
    * @param expr a SQL type expression of the column literal type to check.
    * @return If the expression matched all constraints.
    */
-  bool matches(const std::string& expr);
+  bool matches(const std::string& expr) const;
 
   /**
    * @brief Check if an expression matches the query constraints.
@@ -137,7 +137,7 @@ struct ConstraintList {
    * @return If the expression matched all constraints.
    */
   template <typename T>
-  bool matches(const T& expr) {
+  bool matches(const T& expr) const {
     return matches(TEXT(expr));
   }
 
@@ -150,7 +150,7 @@ struct ConstraintList {
    *
    * @return true if any constraint exists.
    */
-  bool exists() { return (constraints_.size() > 0); }
+  bool exists() const { return (constraints_.size() > 0); }
 
   /**
    * @brief Check if a constrait exist AND matches the type expression.
@@ -161,7 +161,7 @@ struct ConstraintList {
    * @return true if any constraint exists AND matches the type expression.
    */
   template <typename T>
-  bool existsAndMatches(const T& expr) {
+  bool existsAndMatches(const T& expr) const {
     return (exists() && matches(expr));
   }
 
@@ -176,7 +176,7 @@ struct ConstraintList {
    * @return true if constraint is missing or matches the type expression.
    */
   template <typename T>
-  bool notExistsOrMatches(const T& expr) {
+  bool notExistsOrMatches(const T& expr) const {
     return (!exists() || matches(expr));
   }
 
@@ -184,7 +184,7 @@ struct ConstraintList {
    * @brief Helper templated function for ConstraintList::matches.
    */
   template <typename T>
-  bool literal_matches(const T& base_expr);
+  bool literal_matches(const T& base_expr) const;
 
   /**
    * @brief Get all expressions for a given ConstraintOperator.
@@ -195,10 +195,10 @@ struct ConstraintList {
    * @param op the ConstraintOperator.
    * @return A list of TEXT%-represented types matching the operator.
    */
-  std::set<std::string> getAll(ConstraintOperator op);
+  std::set<std::string> getAll(ConstraintOperator op) const;
 
   template<typename T>
-  std::set<T> getAll(ConstraintOperator op) {
+  std::set<T> getAll(ConstraintOperator op) const {
     std::set<T> literal_matches;
     auto matches = getAll(op);
     for (const auto& match : matches) {
@@ -273,9 +273,9 @@ typedef struct Constraint Constraint;
 class TablePlugin : public Plugin {
  protected:
   /// Helper method to generate the virtual table CREATE statement.
-  virtual std::string statement();
-  virtual std::string columnDefinition();
-  virtual TableColumns columns() {
+  virtual std::string statement() const;
+  virtual std::string columnDefinition() const;
+  virtual TableColumns columns() const {
     TableColumns columns;
     return columns;
   }
index 3e3fa31..717b2ba 100644 (file)
@@ -14,7 +14,7 @@ typedef i64 ExtensionRouteUUID
 typedef map<string, string> ExtensionRoute
 typedef map<string, ExtensionRoute> ExtensionRouteTable
 typedef map<string, ExtensionRouteTable> ExtensionRegistry
-typedef map<ExtensionRouteUUID, InternalExtensionInfo> ExtensionList
+typedef map<ExtensionRouteUUID, InternalExtensionInfo> InternalExtensionList
 
 enum ExtensionCode {
   EXT_SUCCESS = 0,
@@ -49,7 +49,7 @@ service Extension {
 }
 
 service ExtensionManager extends Extension {
-  ExtensionList extensions(),
+  InternalExtensionList extensions(),
   ExtensionStatus registerExtension(
     1:InternalExtensionInfo info,
     2:ExtensionRegistry registry),
index 4ad8a2d..517816b 100644 (file)
@@ -87,6 +87,7 @@ ENDMACRO(TARGET_OSQUERY_LINK_WHOLE)
 ADD_SUBDIRECTORY(core)
 ADD_SUBDIRECTORY(config)
 ADD_SUBDIRECTORY(dispatcher)
+ADD_SUBDIRECTORY(distributed)
 ADD_SUBDIRECTORY(devtools)
 ADD_SUBDIRECTORY(database)
 ADD_SUBDIRECTORY(events)
index 336b7a6..0617915 100644 (file)
@@ -106,13 +106,14 @@ Status checkStalePid(const std::string& content) {
   if (status != ESRCH) {
     // The pid is running, check if it is an osqueryd process by name.
     std::stringstream query_text;
-    query_text << "SELECT name FROM processes WHERE pid = " << pid << ";";
+    query_text << "SELECT name FROM processes WHERE pid = " << pid
+               << " AND name = 'osqueryd';";
     auto q = SQL(query_text.str());
     if (!q.ok()) {
       return Status(1, "Error querying processes: " + q.getMessageString());
     }
 
-    if (q.rows().size() >= 1 && q.rows().front()["name"] == "osqueryd") {
+    if (q.rows().size() > 0) {
       // If the process really is osqueryd, return an "error" status.
       if (FLAGS_force) {
         // The caller may choose to abort the existing daemon with --force.
index 910418b..20f6d1d 100644 (file)
@@ -17,7 +17,7 @@ namespace osquery {
 namespace tables {
 
 
-bool ConstraintList::matches(const std::string& expr) {
+bool ConstraintList::matches(const std::string& expr) const {
   // Support each SQL affinity type casting.
   if (affinity == "TEXT") {
     return literal_matches<TEXT_LITERAL>(expr);
@@ -37,7 +37,7 @@ bool ConstraintList::matches(const std::string& expr) {
 }
 
 template <typename T>
-bool ConstraintList::literal_matches(const T& base_expr) {
+bool ConstraintList::literal_matches(const T& base_expr) const {
   bool aggregate = true;
   for (size_t i = 0; i < constraints_.size(); ++i) {
     T constraint_expr = AS_LITERAL(T, constraints_[i].expr);
@@ -63,7 +63,7 @@ bool ConstraintList::literal_matches(const T& base_expr) {
   return true;
 }
 
-std::set<std::string> ConstraintList::getAll(ConstraintOperator op) {
+std::set<std::string> ConstraintList::getAll(ConstraintOperator op) const {
   std::set<std::string> set;
   for (size_t i = 0; i < constraints_.size(); ++i) {
     if (constraints_[i].op == op) {
@@ -122,9 +122,7 @@ void TablePlugin::setRequestFromContext(const QueryContext& context,
 
 void TablePlugin::setResponseFromQueryData(const QueryData& data,
                                            PluginResponse& response) {
-  for (const auto& row : data) {
-    response.push_back(row);
-  }
+  response = std::move(data);
 }
 
 void TablePlugin::setContextFromRequest(const PluginRequest& request,
@@ -170,7 +168,7 @@ Status TablePlugin::call(const PluginRequest& request,
   } else if (request.at("action") == "columns") {
     // "columns" returns a PluginRequest filled with column information
     // such as name and type.
-    auto column_list = columns();
+    const auto& column_list = columns();
     for (const auto& column : column_list) {
       response.push_back({{"name", column.first}, {"type", column.second}});
     }
@@ -183,20 +181,19 @@ Status TablePlugin::call(const PluginRequest& request,
   return Status(0, "OK");
 }
 
-std::string TablePlugin::columnDefinition() {
+std::string TablePlugin::columnDefinition() const {
   const auto& column_list = columns();
   std::string statement = "(";
   for (size_t i = 0; i < column_list.size(); ++i) {
-    statement += column_list[i].first + " " + column_list.at(i).second;
+    statement += column_list.at(i).first + " " + column_list.at(i).second;
     if (i < column_list.size() - 1) {
       statement += ", ";
     }
   }
-  statement += ")";
-  return statement;
+  return statement += ")";
 }
 
-std::string TablePlugin::statement() {
+std::string TablePlugin::statement() const {
   return "CREATE TABLE " + name_ + columnDefinition();
 }
 
index 20d450d..323c8ae 100644 (file)
@@ -26,7 +26,7 @@ DEFINE_osquery_flag(int32,
                     4,
                     "Number of work dispatch threads");
 
-void InternalRunnable::interruptableSleep(size_t milli) {
+void interruptableSleep(size_t milli) {
   boost::this_thread::sleep(boost::posix_time::milliseconds(milli));
 }
 
diff --git a/osquery/distributed/CMakeLists.txt b/osquery/distributed/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1d49aa1
--- /dev/null
@@ -0,0 +1,3 @@
+ADD_OSQUERY_LIBRARY(TRUE osquery_distributed distributed.cpp)
+
+ADD_OSQUERY_TEST(TRUE distributed_tests distributed_tests.cpp)
diff --git a/osquery/distributed/distributed.cpp b/osquery/distributed/distributed.cpp
new file mode 100644 (file)
index 0000000..bd7a5ca
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (c) 2014, Facebook, Inc.
+ *  All rights reserved.
+ *
+ *  This source code is licensed under the BSD-style license found in the
+ *  LICENSE file in the root directory of this source tree. An additional grant
+ *  of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+#include <sstream>
+
+#include <boost/property_tree/json_parser.hpp>
+
+#include <osquery/core.h>
+#include <osquery/database.h>
+#include <osquery/logger.h>
+
+#include "osquery/distributed/distributed.h"
+
+namespace pt = boost::property_tree;
+
+namespace osquery {
+
+DEFINE_osquery_flag(int32,
+                    distributed_get_queries_retries,
+                    3,
+                    "Times to retry retrieving distributed queries");
+
+DEFINE_osquery_flag(int32,
+                    distributed_write_results_retries,
+                    3,
+                    "Times to retry writing distributed query results");
+
+Status MockDistributedProvider::getQueriesJSON(std::string& query_json) {
+  query_json = queriesJSON_;
+  return Status();
+}
+
+Status MockDistributedProvider::writeResultsJSON(const std::string& results) {
+    resultsJSON_ = results;
+    return Status();
+}
+
+Status DistributedQueryHandler::parseQueriesJSON(
+    const std::string& query_json,
+    std::vector<DistributedQueryRequest>& requests) {
+  // Parse the JSON into a ptree
+  pt::ptree tree;
+  try {
+    std::istringstream query_stream(query_json);
+    pt::read_json(query_stream, tree);
+  }
+  catch (const std::exception& e) {
+    return Status(1, std::string("Error loading query JSON: ") + e.what());
+  }
+
+  // Parse the ptree into DistributedQueryRequests
+  std::vector<DistributedQueryRequest> results;
+  for (const auto& node : tree) {
+    const auto& request_tree = node.second;
+    DistributedQueryRequest request;
+    try {
+      request.query = request_tree.get_child("query").get_value<std::string>();
+      request.id = request_tree.get_child("id").get_value<std::string>();
+    } catch (const std::exception& e) {
+      return Status(1, std::string("Error parsing queries: ") + e.what());
+    }
+    results.push_back(request);
+  }
+
+  requests = std::move(results);
+
+  return Status();
+}
+
+SQL DistributedQueryHandler::handleQuery(const std::string& query_string) {
+  SQL query = SQL(query_string);
+  query.annotateHostInfo();
+  return query;
+}
+
+Status DistributedQueryHandler::serializeResults(
+    const std::vector<std::pair<DistributedQueryRequest, SQL> >& results,
+    pt::ptree& tree) {
+  try {
+    pt::ptree& res_tree = tree.put_child("results", pt::ptree());
+    for (const auto& result : results) {
+      DistributedQueryRequest request = result.first;
+      SQL sql = result.second;
+      pt::ptree& child = res_tree.put_child(request.id, pt::ptree());
+      child.put("status", sql.getStatus().getCode());
+      pt::ptree& rows_child = child.put_child("rows", pt::ptree());
+      Status s = serializeQueryData(sql.rows(), rows_child);
+      if (!s.ok()) {
+        return s;
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    return Status(1, std::string("Error serializing results: ") + e.what());
+  }
+  return Status();
+}
+
+Status DistributedQueryHandler::doQueries() {
+  // Get and parse the queries
+  Status status;
+  std::string query_json;
+  int retries = 0;
+  do {
+    status = provider_->getQueriesJSON(query_json);
+    ++retries;
+  } while (!status.ok() && retries <= FLAGS_distributed_get_queries_retries);
+  if (!status.ok()) {
+    return status;
+  }
+
+  std::vector<DistributedQueryRequest> requests;
+  status = parseQueriesJSON(query_json, requests);
+  if (!status.ok()) {
+    return status;
+  }
+
+  // Run the queries
+  std::vector<std::pair<DistributedQueryRequest, SQL> > query_results;
+  std::set<std::string> successful_query_ids;
+  for (const auto& request : requests) {
+    if (executedRequestIds_.find(request.id) != executedRequestIds_.end()) {
+      // We've already successfully returned results for this request, don't
+      // process it again.
+      continue;
+    }
+    SQL query_result = handleQuery(request.query);
+    if (query_result.ok()) {
+      successful_query_ids.insert(request.id);
+    }
+    query_results.push_back({request, query_result});
+  }
+
+  // Serialize the results
+  pt::ptree serialized_results;
+  serializeResults(query_results, serialized_results);
+  std::string json;
+  try {
+    std::ostringstream ss;
+    pt::write_json(ss, serialized_results, false);
+    json = ss.str();
+  }
+  catch (const std::exception& e) {
+    return Status(1, e.what());
+  }
+
+  // Write the results
+  retries = 0;
+  do {
+    status = provider_->writeResultsJSON(json);
+    ++retries;
+  } while (!status.ok() && retries <= FLAGS_distributed_write_results_retries);
+  if (!status.ok()) {
+    return status;
+  }
+
+  // Only note that the queries were successfully completed if we were actually
+  // able to write the results.
+  executedRequestIds_.insert(successful_query_ids.begin(),
+                             successful_query_ids.end());
+
+  return status;
+}
+}
diff --git a/osquery/distributed/distributed.h b/osquery/distributed/distributed.h
new file mode 100644 (file)
index 0000000..38b6857
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ *  Copyright (c) 2014, Facebook, Inc.
+ *  All rights reserved.
+ *
+ *  This source code is licensed under the BSD-style license found in the
+ *  LICENSE file in the root directory of this source tree. An additional grant
+ *  of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+#pragma once
+
+#include <set>
+#include <vector>
+
+#include <boost/property_tree/ptree.hpp>
+
+#include <osquery/database/results.h>
+#include <osquery/sql.h>
+
+namespace osquery {
+
+/**
+ * @brief This is an interface for distributed query "providers"
+ *
+ * Providers implement the communication between the distributed query master
+ * and the individual host. A provider may utilize any communications strategy
+ * that supports reading and writing JSON (i.e. HTTPS requests, reading from a
+ * file, querying a message queue, etc.)
+ */
+class IDistributedProvider {
+public:
+  virtual ~IDistributedProvider() {}
+
+  /*
+   * @brief Get the JSON string containing the queries to be executed
+   *
+   * @param query_json A string to fill with the retrieved JSON
+   *
+   * @return osquery::Status indicating success or failure of the operation
+   */
+  virtual Status getQueriesJSON(std::string& query_json) = 0;
+
+  /*
+   * @brief Write the results JSON back to the master
+   *
+   * @param results A string containing the results JSON
+   *
+   * @return osquery::Status indicating success or failure of the operation
+   */
+  virtual Status writeResultsJSON(const std::string& results) = 0;
+};
+
+/**
+ * @brief A mocked implementation of IDistributedProvider
+ *
+ * This implementation is useful for writing unit tests of the
+ * DistributedQueryHandler functionality.
+ */
+class MockDistributedProvider : public IDistributedProvider {
+public:
+ // These methods just read/write the corresponding public members
+  Status getQueriesJSON(std::string& query_json) override;
+  Status writeResultsJSON(const std::string& results) override;
+
+  std::string queriesJSON_;
+  std::string resultsJSON_;
+};
+
+/**
+ * @brief Small struct containing the query and ID information for a
+ * distributed query
+ */
+struct DistributedQueryRequest {
+public:
+ explicit DistributedQueryRequest() {}
+ explicit DistributedQueryRequest(const std::string& q, const std::string& i)
+     : query(q), id(i) {}
+  std::string query;
+  std::string id;
+};
+
+/**
+ * @brief The main handler class for distributed queries
+ *
+ * This class is responsible for implementing the core functionality of
+ * distributed queries. It manages state, uses the provider to read/write from
+ * the master, and executes queries.
+ */
+class DistributedQueryHandler {
+public:
+ /**
+  * @brief Construct a new handler with the given provider
+  *
+  * @param provider The provider used retrieving queries and writing results
+  */
+ explicit DistributedQueryHandler(
+     std::unique_ptr<IDistributedProvider> provider)
+     : provider_(std::move(provider)) {}
+
+ /**
+  * @brief Retrieve queries, run them, and write results
+  *
+  * This is the core method of DistributedQueryHandler, tying together all the
+  * other components to read the requests from the provider, execute the
+  * queries, and write the results back to the provider.
+  *
+  * @return osquery::Status indicating success or failure of the operation
+  */
+ Status doQueries();
+
+ /**
+  * @brief Run and annotate an individual query
+  *
+  * @param query_string A string containing the query to be executed
+  *
+  * @return A SQL object containing the (annotated) query results
+  */
+ static SQL handleQuery(const std::string& query_string);
+
+ /**
+  * @brief Serialize the results of all requests into a ptree
+  *
+  * @param results The vector of requests and results
+  * @param tree The tree to serialize results into
+  *
+  * @return osquery::Status indicating success or failure of the operation
+  */
+ static Status serializeResults(
+     const std::vector<std::pair<DistributedQueryRequest, SQL> >& results,
+     boost::property_tree::ptree& tree);
+
+ /**
+  * @brief Parse the query JSON into the individual query objects
+  *
+  * @param query_json The JSON string containing the queries
+  * @param requests A vector to fill with the query objects
+  *
+  * @return osquery::Status indicating success or failure of the parsing
+  */
+  static Status parseQueriesJSON(const std::string& query_json,
+                                 std::vector<DistributedQueryRequest>& requests);
+
+private:
+  // The provider used to read and write queries and results
+  std::unique_ptr<IDistributedProvider> provider_;
+
+  // Used to store already executed queries to avoid duplication. (Some master
+  // configurations may asynchronously process the results of requests, so a
+  // request might be seen by the host after it has already been executed.)
+  std::set<std::string> executedRequestIds_;
+};
+
+} // namespace osquery
diff --git a/osquery/distributed/distributed_tests.cpp b/osquery/distributed/distributed_tests.cpp
new file mode 100644 (file)
index 0000000..6d5a8d4
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ *  Copyright (c) 2014, Facebook, Inc.
+ *  All rights reserved.
+ *
+ *  This source code is licensed under the BSD-style license found in the
+ *  LICENSE file in the root directory of this source tree. An additional grant
+ *  of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+#include <iostream>
+
+#include <boost/property_tree/json_parser.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <gtest/gtest.h>
+
+#include <osquery/core.h>
+#include <osquery/sql.h>
+
+#include "osquery/distributed/distributed.h"
+
+namespace pt = boost::property_tree;
+
+namespace osquery {
+
+class DistributedTests : public testing::Test {};
+
+TEST_F(DistributedTests, test_test_distributed_provider) {
+  MockDistributedProvider p;
+  std::string query_string = "['foo']";
+  std::string result_string = "['bar']";
+
+  p.queriesJSON_ = query_string;
+  std::string query_json;
+  Status s = p.getQueriesJSON(query_json);
+  ASSERT_EQ(Status(), s);
+  EXPECT_EQ(query_string, query_json);
+
+  s = p.writeResultsJSON(result_string);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ(result_string, p.resultsJSON_);
+}
+
+TEST_F(DistributedTests, test_parse_query_json) {
+  std::string request_json = R"([{"query": "foo", "id": "bar"}])";
+  std::vector<DistributedQueryRequest> requests;
+  Status s = DistributedQueryHandler::parseQueriesJSON(request_json, requests);
+  ASSERT_EQ(Status(), s);
+  EXPECT_EQ(1, requests.size());
+  EXPECT_EQ("foo", requests[0].query);
+  EXPECT_EQ("bar", requests[0].id);
+
+  std::string bad_json = R"([{"query": "foo", "id": "bar"}, {"query": "b"}])";
+  requests.clear();
+  s = DistributedQueryHandler::parseQueriesJSON(bad_json, requests);
+  ASSERT_FALSE(s.ok());
+  EXPECT_EQ(0, requests.size());
+}
+
+TEST_F(DistributedTests, test_handle_query) {
+  SQL query = DistributedQueryHandler::handleQuery("SELECT hour from time");
+  ASSERT_TRUE(query.ok());
+  QueryData rows = query.rows();
+  ASSERT_EQ(1, rows.size());
+  EXPECT_EQ(rows[0]["_source_host"], getHostname());
+
+  query = DistributedQueryHandler::handleQuery("bad query");
+  ASSERT_FALSE(query.ok());
+  rows = query.rows();
+  ASSERT_EQ(0, rows.size());
+}
+
+TEST_F(DistributedTests, test_serialize_results_empty) {
+  DistributedQueryRequest r0("foo", "foo_id");
+  MockSQL q0 = MockSQL();
+  pt::ptree tree;
+
+  DistributedQueryHandler::serializeResults({{r0, q0}}, tree);
+
+  EXPECT_EQ(0, tree.get<int>("results.foo_id.status"));
+  EXPECT_TRUE(tree.get_child("results.foo_id.rows").empty());
+}
+
+TEST_F(DistributedTests, test_serialize_results_basic) {
+  DistributedQueryRequest r0("foo", "foo_id");
+  QueryData rows0 = {{{"foo0", "foo0_val"}, {"bar0", "bar0_val"}},
+                     {{"foo1", "foo1_val"}, {"bar1", "bar1_val"}}, };
+  MockSQL q0 = MockSQL(rows0);
+  pt::ptree tree;
+
+  DistributedQueryHandler::serializeResults({{r0, q0}}, tree);
+
+  EXPECT_EQ(0, tree.get<int>("results.foo_id.status"));
+
+  const pt::ptree& tree_rows = tree.get_child("results.foo_id.rows");
+  EXPECT_EQ(2, tree_rows.size());
+
+  auto row = tree_rows.begin();
+  EXPECT_EQ("foo0_val", row->second.get<std::string>("foo0"));
+  EXPECT_EQ("bar0_val", row->second.get<std::string>("bar0"));
+  ++row;
+  EXPECT_EQ("foo1_val", row->second.get<std::string>("foo1"));
+  EXPECT_EQ("bar1_val", row->second.get<std::string>("bar1"));
+}
+
+TEST_F(DistributedTests, test_serialize_results_multiple) {
+  DistributedQueryRequest r0("foo", "foo_id");
+  QueryData rows0 = {{{"foo0", "foo0_val"}, {"bar0", "bar0_val"}},
+                     {{"foo1", "foo1_val"}, {"bar1", "bar1_val"}}, };
+  MockSQL q0 = MockSQL(rows0);
+
+  DistributedQueryRequest r1("bar", "bar_id");
+  MockSQL q1 = MockSQL({}, Status(1, "Fail"));
+
+  pt::ptree tree;
+
+  DistributedQueryHandler::serializeResults({{r0, q0}, {r1, q1}}, tree);
+
+  EXPECT_EQ(0, tree.get<int>("results.foo_id.status"));
+  const pt::ptree& tree_rows = tree.get_child("results.foo_id.rows");
+  EXPECT_EQ(2, tree_rows.size());
+  auto row = tree_rows.begin();
+  EXPECT_EQ("foo0_val", row->second.get<std::string>("foo0"));
+  EXPECT_EQ("bar0_val", row->second.get<std::string>("bar0"));
+  ++row;
+  EXPECT_EQ("foo1_val", row->second.get<std::string>("foo1"));
+  EXPECT_EQ("bar1_val", row->second.get<std::string>("bar1"));
+
+  EXPECT_EQ(1, tree.get<int>("results.bar_id.status"));
+  const pt::ptree& fail_rows = tree.get_child("results.bar_id.rows");
+  EXPECT_EQ(0, fail_rows.size());
+}
+
+TEST_F(DistributedTests, test_do_queries) {
+  auto provider_raw = new MockDistributedProvider();
+  provider_raw->queriesJSON_ =
+    R"([
+      {"query": "SELECT hour FROM time", "id": "hour"},
+      {"query": "bad", "id": "bad"},
+      {"query": "SELECT minutes FROM time", "id": "minutes"}
+    ])";
+  std::unique_ptr<MockDistributedProvider>
+    provider(provider_raw);
+  DistributedQueryHandler handler(std::move(provider));
+
+  Status s = handler.doQueries();
+  ASSERT_EQ(Status(), s);
+
+  pt::ptree tree;
+  std::istringstream json_stream(provider_raw->resultsJSON_);
+  ASSERT_NO_THROW(pt::read_json(json_stream, tree));
+
+  {
+    EXPECT_EQ(0, tree.get<int>("results.hour.status"));
+    const pt::ptree& tree_rows = tree.get_child("results.hour.rows");
+    EXPECT_EQ(1, tree_rows.size());
+    auto row = tree_rows.begin();
+    EXPECT_GE(row->second.get<int>("hour"), 0);
+    EXPECT_LE(row->second.get<int>("hour"), 24);
+    EXPECT_EQ(getHostname(), row->second.get<std::string>("_source_host"));
+  }
+
+  {
+    // this query should have failed
+    EXPECT_EQ(1, tree.get<int>("results.bad.status"));
+    const pt::ptree& tree_rows = tree.get_child("results.bad.rows");
+    EXPECT_EQ(0, tree_rows.size());
+  }
+
+  {
+    EXPECT_EQ(0, tree.get<int>("results.minutes.status"));
+    const pt::ptree& tree_rows = tree.get_child("results.minutes.rows");
+    EXPECT_EQ(1, tree_rows.size());
+    auto row = tree_rows.begin();
+    EXPECT_GE(row->second.get<int>("minutes"), 0);
+    EXPECT_LE(row->second.get<int>("minutes"), 60);
+    EXPECT_EQ(getHostname(), row->second.get<std::string>("_source_host"));
+  }
+}
+
+TEST_F(DistributedTests, test_duplicate_request) {
+  auto provider_raw = new MockDistributedProvider();
+  provider_raw->queriesJSON_ =
+    R"([
+      {"query": "SELECT hour FROM time", "id": "hour"}
+    ])";
+  std::unique_ptr<MockDistributedProvider>
+    provider(provider_raw);
+  DistributedQueryHandler handler(std::move(provider));
+
+  Status s = handler.doQueries();
+  ASSERT_EQ(Status(), s);
+
+  pt::ptree tree;
+  std::istringstream json_stream(provider_raw->resultsJSON_);
+  ASSERT_NO_THROW(pt::read_json(json_stream, tree));
+
+  EXPECT_EQ(0, tree.get<int>("results.hour.status"));
+  const pt::ptree& tree_rows = tree.get_child("results.hour.rows");
+  EXPECT_EQ(1, tree_rows.size());
+  auto row = tree_rows.begin();
+  EXPECT_GE(row->second.get<int>("hour"), 0);
+  EXPECT_LE(row->second.get<int>("hour"), 24);
+  EXPECT_EQ(getHostname(), row->second.get<std::string>("_source_host"));
+
+  // The second time, 'hour' should not be executed again
+  s = handler.doQueries();
+  ASSERT_EQ(Status(), s);
+  json_stream.str(provider_raw->resultsJSON_);
+  ASSERT_NO_THROW(pt::read_json(json_stream, tree));
+  EXPECT_EQ(0, tree.get_child("results").size());
+}
+}
+
+int main(int argc, char* argv[]) {
+  testing::InitGoogleTest(&argc, argv);
+  osquery::initOsquery(argc, argv);
+  return RUN_ALL_TESTS();
+}
index e26e84a..7c38d36 100644 (file)
@@ -15,7 +15,6 @@
 #include <boost/lexical_cast.hpp>
 
 #include <osquery/core.h>
-#include <osquery/dispatcher.h>
 #include <osquery/events.h>
 #include <osquery/flags.h>
 #include <osquery/logger.h>
@@ -24,6 +23,9 @@
 
 namespace osquery {
 
+/// Helper cooloff (ms) macro to prevent thread failure thrashing.
+#define EVENTS_COOLOFF 20
+
 DEFINE_osquery_flag(bool,
                     disable_events,
                     false,
@@ -417,7 +419,7 @@ Status EventFactory::run(EventPublisherID& type_id) {
   while (!publisher->isEnding() && status.ok()) {
     // Can optionally implement a global cooloff latency here.
     status = publisher->run();
-    ::usleep(20);
+    osquery::interruptableSleep(EVENTS_COOLOFF);
   }
 
   // The runloop status is not reflective of the event type's.
index f91a673..12ff672 100644 (file)
@@ -20,7 +20,8 @@
 
 namespace osquery {
 
-int kINotifyULatency = 200;
+int kINotifyMLatency = 200;
+
 static const uint32_t BUFFER_SIZE =
     (10 * ((sizeof(struct inotify_event)) + NAME_MAX + 1));
 
@@ -70,7 +71,7 @@ Status INotifyEventPublisher::run() {
   FD_ZERO(&set);
   FD_SET(getHandle(), &set);
 
-  struct timeval timeout = {0, kINotifyULatency};
+  struct timeval timeout = {0, kINotifyMLatency};
   int selector = ::select(getHandle() + 1, &set, nullptr, nullptr, &timeout);
   if (selector == -1) {
     LOG(ERROR) << "Could not read inotify handle";
@@ -111,7 +112,7 @@ Status INotifyEventPublisher::run() {
     p += (sizeof(struct inotify_event)) + event->len;
   }
 
-  ::usleep(kINotifyULatency);
+  osquery::interruptableSleep(kINotifyMLatency);
   return Status(0, "Continue");
 }
 
@@ -140,7 +141,7 @@ INotifyEventContextRef INotifyEventPublisher::createEventContextFrom(
 }
 
 bool INotifyEventPublisher::shouldFire(const INotifySubscriptionContextRef& sc,
-                                       const INotifyEventContextRef& ec) {
+                                       const INotifyEventContextRef& ec) const {
   if (!sc->recursive && sc->path != ec->path) {
     // Monitored path is not recursive and path is not an exact match.
     return false;
index a0bbd32..7325687 100644 (file)
@@ -122,7 +122,7 @@ class INotifyEventPublisher
   bool removeMonitor(int watch, bool force = false);
   /// Given a SubscriptionContext and INotifyEventContext match path and action.
   bool shouldFire(const INotifySubscriptionContextRef& mc,
-                  const INotifyEventContextRef& ec);
+                  const INotifyEventContextRef& ec) const;
   /// Get the INotify file descriptor.
   int getHandle() { return inotify_handle_; }
   /// Get the number of actual INotify active descriptors.
index 74d6e50..4c3e899 100644 (file)
@@ -16,7 +16,7 @@
 
 namespace osquery {
 
-int kUdevULatency = 200;
+int kUdevMLatency = 200;
 
 REGISTER(UdevEventPublisher, "event_publisher", "udev");
 
@@ -76,7 +76,7 @@ Status UdevEventPublisher::run() {
 
   udev_device_unref(device);
 
-  ::usleep(kUdevULatency);
+  osquery::interruptableSleep(kUdevMLatency);
   return Status(0, "Continue");
 }
 
@@ -138,7 +138,7 @@ UdevEventContextRef UdevEventPublisher::createEventContextFrom(
 }
 
 bool UdevEventPublisher::shouldFire(const UdevSubscriptionContextRef& sc,
-                                    const UdevEventContextRef& ec) {
+                                    const UdevEventContextRef& ec) const {
   if (sc->action != UDEV_EVENT_ACTION_ALL) {
     if (sc->action != ec->action) {
       return false;
index b289396..7ce8deb 100644 (file)
@@ -113,7 +113,7 @@ class UdevEventPublisher
  private:
   /// Check subscription details.
   bool shouldFire(const UdevSubscriptionContextRef& mc,
-                  const UdevEventContextRef& ec);
+                  const UdevEventContextRef& ec) const;
   /// Helper function to create an EventContext using a udev_device pointer.
   UdevEventContextRef createEventContextFrom(struct udev_device* device);
 };
index afdaa9f..18421e9 100644 (file)
@@ -8,6 +8,7 @@
  *
  */
 
+#include <csignal>
 #include <thrift/protocol/TBinaryProtocol.h>
 #include <thrift/server/TSimpleServer.h>
 #include <thrift/transport/TServerSocket.h>
@@ -271,13 +272,84 @@ Status startExtension(const std::string& manager_path,
   return Status(0, std::to_string(status.uuid));
 }
 
+Status pingExtension(const std::string& path) {
+  if (FLAGS_disable_extensions) {
+    return Status(1, "Extensions disabled");
+  }
+
+  // Make sure the extension path exists, and is writable.
+  if (!pathExists(path) || !isWritable(path)) {
+    return Status(1, "Extension socket not availabe: " + path);
+  }
+
+  // Open a socket to the extension.
+  boost::shared_ptr<TSocket> socket(new TSocket(path));
+  boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
+  boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
+
+  ExtensionClient client(protocol);
+  ExtensionStatus ext_status;
+  try {
+    transport->open();
+    client.ping(ext_status);
+    transport->close();
+  } catch (const std::exception& e) {
+    return Status(1, "Extension call failed: " + std::string(e.what()));
+  }
+
+  return Status(ext_status.code, ext_status.message);
+}
+
+Status getExtensions(ExtensionList& extensions) {
+  if (FLAGS_disable_extensions) {
+    return Status(1, "Extensions disabled");
+  }
+  return getExtensions(FLAGS_extensions_socket, extensions);
+}
+
+Status getExtensions(const std::string& manager_path,
+                     ExtensionList& extensions) {
+  // Make sure the extension path exists, and is writable.
+  if (!pathExists(manager_path) || !isWritable(manager_path)) {
+    return Status(1, "Extension manager socket not availabe: " + manager_path);
+  }
+
+  // Open a socket to the extension.
+  boost::shared_ptr<TSocket> socket(new TSocket(manager_path));
+  boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
+  boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
+
+  ExtensionManagerClient client(protocol);
+  InternalExtensionList ext_list;
+  try {
+    transport->open();
+    client.extensions(ext_list);
+    transport->close();
+  } catch (const std::exception& e) {
+    return Status(1, "Extension call failed: " + std::string(e.what()));
+  }
+
+  // Add the extension manager to the list called (core).
+  extensions.insert(std::make_pair(0, ExtensionInfo("core")));
+
+  // Convert from Thrift-internal list type to RouteUUID/ExtenionInfo type.
+  for (const auto& extension : ext_list) {
+    extensions[extension.first] = extension.second;
+  }
+
+  return Status(0, "OK");
+}
+
 Status callExtension(const RouteUUID uuid,
                      const std::string& registry,
                      const std::string& item,
                      const PluginRequest& request,
                      PluginResponse& response) {
-  // Not yet implemented.
-  return Status(0, "OK");
+  if (FLAGS_disable_extensions) {
+    return Status(1, "Extensions disabled");
+  }
+  return callExtension(
+      getExtensionSocket(uuid), registry, item, request, response);
 }
 
 Status callExtension(const std::string& extension_path,
@@ -306,6 +378,7 @@ Status callExtension(const std::string& extension_path,
     return Status(1, "Extension call failed: " + std::string(e.what()));
   }
 
+  // Convert from Thrift-internal list type to PluginResponse type.
   if (ext_response.status.code == ExtensionCode::EXT_SUCCESS) {
     for (const auto& item : ext_response.response) {
       response.push_back(item);
@@ -329,6 +402,9 @@ Status startExtensionWatcher(const std::string& manager_path,
 }
 
 Status startExtensionManager() {
+  if (FLAGS_disable_extensions) {
+    return Status(1, "Extensions disabled");
+  }
   return startExtensionManager(FLAGS_extensions_socket);
 }
 
index d724b75..30626da 100644 (file)
@@ -73,23 +73,10 @@ class ExtensionsTest : public testing::Test {
   }
 
   ExtensionList registeredExtensions(int attempts = 3) {
-    // Open a socket to the test extension manager.
-    boost::shared_ptr<TSocket> socket(new TSocket(kTestManagerSocket));
-    boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
-    boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
-
-    ExtensionManagerClient client(protocol);
-
-    // Calling open will except if the socket does not exist.
     ExtensionList extensions;
     for (int i = 0; i < attempts; ++i) {
-      try {
-        transport->open();
-        client.extensions(extensions);
-        transport->close();
-      }
-      catch (const std::exception& e) {
-        ::usleep(kDelayUS);
+      if (getExtensions(kTestManagerSocket, extensions).ok()) {
+        break;
       }
     }
 
@@ -222,7 +209,8 @@ TEST_F(ExtensionsTest, test_extension_broadcast) {
 
   // Make sure the EM registered the extension (called in start extension).
   auto extensions = registeredExtensions();
-  EXPECT_EQ(extensions.size(), 1);
+  // Expect two, since `getExtensions` includes the core.
+  EXPECT_EQ(extensions.size(), 2);
   EXPECT_EQ(extensions.count(uuid), 1);
   EXPECT_EQ(extensions.at(uuid).name, "test");
   EXPECT_EQ(extensions.at(uuid).version, "0.1");
index 585e341..0719738 100644 (file)
@@ -485,4 +485,15 @@ std::vector<fs::path> getHomeDirectories() {
   }
   return results;
 }
+
+std::string lsperms(int mode) {
+  static const char rwx[] = {'0', '1', '2', '3', '4', '5', '6', '7'};
+  std::string bits;
+
+  bits += rwx[(mode >> 9) & 7];
+  bits += rwx[(mode >> 6) & 7];
+  bits += rwx[(mode >> 3) & 7];
+  bits += rwx[(mode >> 0) & 7];
+  return bits;
+}
 }
index 3c28579..bfbd601 100644 (file)
 
 #include <exception>
 #include <map>
+#include <regex>
 #include <vector>
 
+#include <linux/limits.h>
 #include <unistd.h>
 
-#include <boost/regex.hpp>
 #include <boost/filesystem.hpp>
 
 #include <osquery/filesystem.h>
@@ -25,21 +26,19 @@ namespace osquery {
 const std::string kLinuxProcPath = "/proc";
 
 Status procProcesses(std::vector<std::string>& processes) {
-  boost::regex process_filter("\\d+");
 
   // Iterate over each process-like directory in proc.
   boost::filesystem::directory_iterator it(kLinuxProcPath), end;
+  std::regex process_filter("[0-9]+", std::regex_constants::extended);
   try {
     for (; it != end; ++it) {
       if (boost::filesystem::is_directory(it->status())) {
-        boost::smatch what;
-        if (boost::regex_match(
-                it->path().leaf().string(), what, process_filter)) {
+        if (std::regex_match(it->path().leaf().string(), process_filter)) {
           processes.push_back(it->path().leaf().string());
         }
       }
     }
-  } catch (boost::filesystem::filesystem_error& e) {
+  } catch (const boost::filesystem::filesystem_error& e) {
     VLOG(1) << "Exception iterating Linux processes " << e.what();
     return Status(1, e.what());
   }
@@ -71,16 +70,13 @@ Status procReadDescriptor(const std::string& process,
                           const std::string& descriptor,
                           std::string& result) {
   auto link = kLinuxProcPath + "/" + process + "/fd/" + descriptor;
-  auto path_max = pathconf(link.c_str(), _PC_PATH_MAX);
-  auto result_path = (char*)malloc(path_max);
 
-  memset(result_path, 0, path_max);
-  auto size = readlink(link.c_str(), result_path, path_max);
+  char result_path[PATH_MAX] = {0};
+  auto size = readlink(link.c_str(), result_path, sizeof(result_path) - 1);
   if (size >= 0) {
     result = std::string(result_path);
   }
 
-  free(result_path);
   if (size >= 0) {
     return Status(0, "OK");
   } else {
index 545d911..a5a7944 100644 (file)
@@ -37,28 +37,28 @@ int main(int argc, char* argv[]) {
   osquery::Registry::setUp();
   osquery::attachEvents();
 
-  int result = 0;
   if (FLAGS_delay != 0) {
     ::sleep(FLAGS_delay);
   }
 
   osquery::QueryData results;
+  osquery::Status status;
   for (int i = 0; i < FLAGS_iterations; ++i) {
-    printf("Executing: %s\n", FLAGS_query.c_str());
-    auto status = osquery::query(FLAGS_query, results);
+    status = osquery::query(FLAGS_query, results);
     if (!status.ok()) {
       fprintf(stderr, "Query failed: %d\n", status.getCode());
       break;
-    } else {
-      if (FLAGS_delay != 0) {
-        ::sleep(FLAGS_delay);
-      }
     }
   }
 
+  if (FLAGS_delay != 0) {
+    ::sleep(FLAGS_delay);
+  }
+
+
   // Instead of calling "shutdownOsquery" force the EF to join its threads.
   osquery::EventFactory::end(true);
   __GFLAGS_NAMESPACE::ShutDownCommandLineFlags();
 
-  return result;
+  return status.getCode();
 }
index 704ec4d..882f9e1 100644 (file)
@@ -60,7 +60,7 @@ Status RegistryHelperCore::call(const std::string& item_name,
                                 const PluginRequest& request,
                                 PluginResponse& response) {
   if (items_.count(item_name) > 0) {
-    return items_[item_name]->call(request, response);
+    return items_.at(item_name)->call(request, response);
   }
   return Status(1, "Cannot call registry item: " + item_name);
 }
@@ -206,7 +206,7 @@ Status RegistryFactory::call(const std::string& registry_name,
   if (instance().registries_.count(registry_name) == 0) {
     return Status(1, "Unknown registry: " + registry_name);
   }
-  return instance().registries_[registry_name]->call(
+  return instance().registries_.at(registry_name)->call(
       item_name, request, response);
 }
 
index a6ee7b1..1118b0c 100644 (file)
@@ -154,7 +154,7 @@ class WidgetPlugin : public Plugin {
   /// to parse and format. BUT a plugin/registry item can also fill this
   /// information in if the plugin type/registry type exposes routeInfo as
   /// a virtual method.
-  RouteInfo routeInfo() {
+  RouteInfo routeInfo() const {
     RouteInfo info;
     info["name"] = name_;
     return info;
@@ -162,7 +162,7 @@ class WidgetPlugin : public Plugin {
 
   /// Plugin types should contain generic request/response formatters and
   /// decorators.
-  std::string secretPower(const PluginRequest& request) {
+  std::string secretPower(const PluginRequest& request) const {
     if (request.count("secret_power") > 0) {
       return request.at("secret_power");
     }
@@ -183,8 +183,12 @@ Status SpecialWidget::call(const PluginRequest& request,
   return Status(0, "OK");
 }
 
+#define UNUSED(x) (void)(x)
+
 TEST_F(RegistryTests, test_registry_api) {
   auto AutoWidgetRegistry = TestCoreRegistry::create<WidgetPlugin>("widgets");
+  UNUSED(AutoWidgetRegistry);
+
   TestCoreRegistry::add<SpecialWidget>("widgets", "special");
 
   // Test route info propogation, from item to registry, to broadcast.
index 557915b..d828036 100644 (file)
@@ -30,12 +30,22 @@ const std::map<tables::ConstraintOperator, std::string> kSQLOperatorRepr = {
 
 SQL::SQL(const std::string& q) { status_ = query(q, results_); }
 
-QueryData SQL::rows() { return results_; }
+const QueryData& SQL::rows() { return results_; }
 
 bool SQL::ok() { return status_.ok(); }
 
+Status SQL::getStatus() { return status_; }
+
 std::string SQL::getMessageString() { return status_.toString(); }
 
+const std::string SQL::kHostColumnName = "_source_host";
+void SQL::annotateHostInfo() {
+  std::string hostname = getHostname();
+  for (Row& row : results_) {
+    row[kHostColumnName] = hostname;
+  }
+}
+
 std::vector<std::string> SQL::getTableNames() {
   std::vector<std::string> results;
   for (const auto& name : Registry::names("table")) {
index d681146..b2c79a5 100644 (file)
@@ -37,7 +37,7 @@ TEST_F(SQLTests, test_raw_access) {
 
 class TestTable : public tables::TablePlugin {
  private:
-  tables::TableColumns columns() {
+  tables::TableColumns columns() const {
     return {{"test_int", "INTEGER"}, {"test_text", "TEXT"}};
   }
 
index 2fa271e..f5bb3b1 100644 (file)
@@ -52,6 +52,51 @@ const std::map<int, std::string> kSQLiteReturnCodes = {
     {101, "SQLITE_DONE: sqlite3_step() has finished executing"},
 };
 
+SQLiteDBInstance::SQLiteDBInstance() {
+  primary_ = false;
+  sqlite3_open(":memory:", &db_);
+  tables::attachVirtualTables(db_);
+}
+
+SQLiteDBInstance::SQLiteDBInstance(sqlite3*& db) {
+  primary_ = true;
+  db_ = db;
+}
+
+SQLiteDBInstance::~SQLiteDBInstance() {
+  if (!primary_) {
+    sqlite3_close(db_);
+  } else {
+    SQLiteDBManager::unlock();
+  }
+}
+
+void SQLiteDBManager::unlock() { instance().lock_.unlock(); }
+
+SQLiteDBInstance SQLiteDBManager::getUnique() { return SQLiteDBInstance(); }
+
+SQLiteDBInstance SQLiteDBManager::get() {
+  auto& self = instance();
+
+  if (!self.lock_.owns_lock() && self.lock_.try_lock()) {
+    if (self.db_ == nullptr) {
+      // Create primary sqlite DB instance.
+      sqlite3_open(":memory:", &self.db_);
+      tables::attachVirtualTables(self.db_);
+    }
+    return SQLiteDBInstance(self.db_);
+  } else {
+    // If this thread or another has the lock, return a transient db.
+    return SQLiteDBInstance();
+  }
+}
+
+SQLiteDBManager::~SQLiteDBManager() {
+  if (db_ != nullptr) {
+    sqlite3_close(db_);
+  }
+}
+
 std::string getStringForSQLiteReturnCode(int code) {
   if (kSQLiteReturnCodes.find(code) != kSQLiteReturnCodes.end()) {
     return kSQLiteReturnCodes.at(code);
@@ -62,13 +107,6 @@ std::string getStringForSQLiteReturnCode(int code) {
   }
 }
 
-sqlite3* createDB() {
-  sqlite3* db = nullptr;
-  sqlite3_open(":memory:", &db);
-  tables::attachVirtualTables(db);
-  return db;
-}
-
 int queryDataCallback(void* argument, int argc, char* argv[], char* column[]) {
   if (argument == nullptr) {
     LOG(ERROR) << "queryDataCallback received nullptr as data argument";
@@ -85,9 +123,8 @@ int queryDataCallback(void* argument, int argc, char* argv[], char* column[]) {
 }
 
 Status queryInternal(const std::string& q, QueryData& results) {
-  sqlite3* db = createDB();
-  auto status = queryInternal(q, results, db);
-  sqlite3_close(db);
+  auto dbc = SQLiteDBManager::get();
+  auto status = queryInternal(q, results, dbc.db());
   return status;
 }
 
@@ -104,9 +141,8 @@ Status queryInternal(const std::string& q, QueryData& results, sqlite3* db) {
 
 Status getQueryColumnsInternal(const std::string& q,
     tables::TableColumns& columns) {
-  sqlite3* db = createDB();
-  Status status = getQueryColumnsInternal(q, columns, db);
-  sqlite3_close(db);
+  auto dbc = SQLiteDBManager::get();
+  Status status = getQueryColumnsInternal(q, columns, dbc.db());
   return status;
 }
 
index 95775eb..051e0d0 100644 (file)
 
 #pragma once
 
+#include <map>
+#include <mutex>
+
 #include <sqlite3.h>
 
+#include <boost/thread/mutex.hpp>
+#include <boost/noncopyable.hpp>
+
 namespace osquery {
+
+/**
+ * @brief An RAII wrapper around an `sqlite3` object.
+ *
+ * The SQLiteDBInstance is also "smart" in that it may unlock access to a
+ * managed `sqlite3` resource. If there's no contention then only a single
+ * database is needed during the life of an osquery tool.
+ *
+ * If there is resource contention (multiple threads want access to the SQLite
+ * abstraction layer), then the SQLiteDBManager will provide a transient
+ * SQLiteDBInstance.
+ */
+class SQLiteDBInstance {
+ public:
+  SQLiteDBInstance();
+  SQLiteDBInstance(sqlite3*& db);
+  ~SQLiteDBInstance();
+
+  /**
+   * @brief Accessor to the internal `sqlite3` object, do not store references
+   * to the object within osquery code.
+   */
+  sqlite3* db() { return db_; }
+
+ private:
+  bool primary_;
+  sqlite3* db_;
+};
+
+/**
+ * @brief osquery internal SQLite DB abstraction resource management.
+ *
+ * The SQLiteDBManager should be the ONLY method for accessing SQLite resources.
+ * The manager provides an abstraction to manage internal SQLite memory and
+ * resources as well as provide optimization around resource access.
+ */
+class SQLiteDBManager : private boost::noncopyable {
+ public:
+  static SQLiteDBManager& instance() {
+    static SQLiteDBManager instance;
+    return instance;
+  }
+
+  /**
+   * @brief Return a fully configured `sqlite3` database object wrapper.
+   *
+   * An osquery database is basically just a SQLite3 database with several
+   * virtual tables attached. This method is the main abstraction for accessing
+   * SQLite3 databases within osquery.
+   *
+   * A RAII wrapper around the `sqlite3` database will manage attaching tables
+   * and freeing resources when the instance (connection per-say) goes out of
+   * scope. Using the SQLiteDBManager will also try to optimize the number of
+   * `sqlite3` databases in use by managing a single global instance and
+   * returning resource-safe transient databases if there's access contention.
+   *
+   * Note: osquery::initOsquery must be called before calling `get` in order
+   * for virtual tables to be registered.
+   *
+   * @return a SQLiteDBInstance with all virtual tables attached.
+   */
+  static SQLiteDBInstance get();
+
+  /// See `get` but always return a transient DB connection (for testing).
+  static SQLiteDBInstance getUnique();
+
+  /// When the primary SQLiteDBInstance is destructed it will unlock.
+  static void unlock();
+
+ protected:
+  SQLiteDBManager() : db_(nullptr), lock_(mutex_, boost::defer_lock) {}
+  SQLiteDBManager(SQLiteDBManager const&);
+  void operator=(SQLiteDBManager const&);
+  virtual ~SQLiteDBManager();
+
+ private:
+  /// Primary (managed) sqlite3 database.
+  sqlite3* db_;
+  /// Mutex and lock around sqlite3 access.
+  boost::mutex mutex_;
+  /// Mutex and lock around sqlite3 access.
+  boost::unique_lock<boost::mutex> lock_;
+};
+
 /**
  * @brief A map of SQLite status codes to their corresponding message string
  *
@@ -60,20 +150,6 @@ Status getQueryColumnsInternal(const std::string& q,
                                sqlite3* db);
 
 /**
- * @brief Return a fully configured sqlite3 database object
- *
- * An osquery database is basically just a SQLite3 database with several
- * virtual tables attached. This method is the main abstraction for creating
- * SQLite3 databases within osquery.
- *
- * Note: osquery::initOsquery must be called before calling createDB in order
- * for virtual tables to be registered.
- *
- * @return a SQLite3 database with all virtual tables attached
- */
-sqlite3* createDB();
-
-/**
  * @brief Get a string representation of a SQLite return code
  */
 std::string getStringForSQLiteReturnCode(int code);
index cc3331b..780a9ff 100644 (file)
@@ -22,8 +22,8 @@ namespace osquery {
 
 class SQLiteUtilTests : public testing::Test {};
 
-sqlite3* createTestDB() {
-  sqlite3* db = createDB();
+SQLiteDBInstance getTestDBC() {
+  SQLiteDBInstance dbc = SQLiteDBManager::getUnique();
   char* err = nullptr;
   std::vector<std::string> queries = {
       "CREATE TABLE test_table ("
@@ -33,29 +33,43 @@ sqlite3* createTestDB() {
       "INSERT INTO test_table VALUES (\"mike\", 23)",
       "INSERT INTO test_table VALUES (\"matt\", 24)"};
   for (auto q : queries) {
-    sqlite3_exec(db, q.c_str(), nullptr, nullptr, &err);
+    sqlite3_exec(dbc.db(), q.c_str(), nullptr, nullptr, &err);
     if (err != nullptr) {
-      return nullptr;
+      throw std::domain_error("Cannot create testing DBC's db.");
     }
   }
 
-  return db;
+  return dbc;
+}
+
+TEST_F(SQLiteUtilTests, test_sqlite_instance_manager) {
+  auto dbc1 = SQLiteDBManager::get();
+  auto dbc2 = SQLiteDBManager::get();
+  EXPECT_NE(dbc1.db(), dbc2.db());
+  EXPECT_EQ(dbc1.db(), dbc1.db());
+}
+
+TEST_F(SQLiteUtilTests, test_sqlite_instance) {
+  // Don't do this at home kids.
+  // Keep a copy of the internal DB and let the SQLiteDBInstance go oos.
+  auto internal_db = SQLiteDBManager::get().db();
+  // Compare the internal DB to another request with no SQLiteDBInstances
+  // in scope, meaning the primary will be returned.
+  EXPECT_EQ(internal_db, SQLiteDBManager::get().db());
 }
 
 TEST_F(SQLiteUtilTests, test_simple_query_execution) {
-  auto db = createTestDB();
+  auto dbc = getTestDBC();
   QueryData results;
-  auto status = queryInternal(kTestQuery, results, db);
-  sqlite3_close(db);
+  auto status = queryInternal(kTestQuery, results, dbc.db());
   EXPECT_TRUE(status.ok());
   EXPECT_EQ(results, getTestDBExpectedResults());
 }
 
 TEST_F(SQLiteUtilTests, test_passing_callback_no_data_param) {
   char* err = nullptr;
-  auto db = createTestDB();
-  sqlite3_exec(db, kTestQuery.c_str(), queryDataCallback, nullptr, &err);
-  sqlite3_close(db);
+  auto dbc = getTestDBC();
+  sqlite3_exec(dbc.db(), kTestQuery.c_str(), queryDataCallback, nullptr, &err);
   EXPECT_TRUE(err != nullptr);
   if (err != nullptr) {
     sqlite3_free(err);
@@ -63,20 +77,19 @@ TEST_F(SQLiteUtilTests, test_passing_callback_no_data_param) {
 }
 
 TEST_F(SQLiteUtilTests, test_aggregate_query) {
-  auto db = createTestDB();
+  auto dbc = getTestDBC();
   QueryData results;
-  auto status = queryInternal(kTestQuery, results, db);
-  sqlite3_close(db);
+  auto status = queryInternal(kTestQuery, results, dbc.db());
   EXPECT_TRUE(status.ok());
   EXPECT_EQ(results, getTestDBExpectedResults());
 }
 
 TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) {
-  auto db = createTestDB();
+  auto dbc = getTestDBC();
   auto results = getTestDBResultStream();
   for (auto r : results) {
     char* err_char = nullptr;
-    sqlite3_exec(db, (r.first).c_str(), nullptr, nullptr, &err_char);
+    sqlite3_exec(dbc.db(), (r.first).c_str(), nullptr, nullptr, &err_char);
     EXPECT_TRUE(err_char == nullptr);
     if (err_char != nullptr) {
       sqlite3_free(err_char);
@@ -84,16 +97,13 @@ TEST_F(SQLiteUtilTests, test_get_test_db_result_stream) {
     }
 
     QueryData expected;
-    auto status = queryInternal(kTestQuery, expected, db);
+    auto status = queryInternal(kTestQuery, expected, dbc.db());
     EXPECT_EQ(expected, r.second);
   }
-  sqlite3_close(db);
 }
 
 TEST_F(SQLiteUtilTests, test_get_query_columns) {
-  std::unique_ptr<sqlite3, decltype(sqlite3_close)*> db_managed(createDB(),
-                                                                sqlite3_close);
-  sqlite3* db = db_managed.get();
+  auto dbc = getTestDBC();
 
   std::string query;
   Status status;
@@ -102,7 +112,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) {
   query =
       "SELECT hour, minutes, seconds, version, config_md5, config_path, \
            pid FROM time JOIN osquery_info";
-  status = getQueryColumnsInternal(query, results, db);
+  status = getQueryColumnsInternal(query, results, dbc.db());
   ASSERT_TRUE(status.ok());
   ASSERT_EQ(7, results.size());
   EXPECT_EQ(std::make_pair(std::string("hour"), std::string("INTEGER")),
@@ -121,7 +131,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) {
             results[6]);
 
   query = "SELECT hour + 1 AS hour1, minutes + 1 FROM time";
-  status = getQueryColumnsInternal(query, results, db);
+  status = getQueryColumnsInternal(query, results, dbc.db());
   ASSERT_TRUE(status.ok());
   ASSERT_EQ(2, results.size());
   EXPECT_EQ(std::make_pair(std::string("hour1"), std::string("UNKNOWN")),
@@ -130,7 +140,7 @@ TEST_F(SQLiteUtilTests, test_get_query_columns) {
             results[1]);
 
   query = "SELECT * FROM foo";
-  status = getQueryColumnsInternal(query, results, db);
+  status = getQueryColumnsInternal(query, results, dbc.db());
   ASSERT_FALSE(status.ok());
 }
 }
index afe9706..43b77fa 100644 (file)
@@ -129,8 +129,8 @@ int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) {
       afinite = boost::lexical_cast<int>(value);
     } catch (const boost::bad_lexical_cast &e) {
       afinite = -1;
-      LOG(WARNING) << "Error casting " << column_name << " (" << value
-                   << ") to INTEGER";
+      VLOG(1) << "Error casting " << column_name << " (" << value
+              << ") to INTEGER";
     }
     sqlite3_result_int(ctx, afinite);
   } else if (type == "BIGINT") {
@@ -139,8 +139,8 @@ int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) {
       afinite = boost::lexical_cast<long long int>(value);
     } catch (const boost::bad_lexical_cast &e) {
       afinite = -1;
-      LOG(WARNING) << "Error casting " << column_name << " (" << value
-                   << ") to BIGINT";
+      VLOG(1) << "Error casting " << column_name << " (" << value
+              << ") to BIGINT";
     }
     sqlite3_result_int64(ctx, afinite);
   }
index 7ae8e0b..de97471 100644 (file)
@@ -24,7 +24,7 @@ class VirtualTableTests : public testing::Test {};
 // sample plugin used on tests
 class sampleTablePlugin : public TablePlugin {
  private:
-  TableColumns columns() {
+  TableColumns columns() const {
     return {
         {"foo", "INTEGER"}, {"bar", "TEXT"},
     };
@@ -45,24 +45,24 @@ TEST_F(VirtualTableTests, test_tableplugin_statement) {
 TEST_F(VirtualTableTests, test_sqlite3_attach_vtable) {
   auto table = std::make_shared<sampleTablePlugin>();
   table->setName("sample");
-  sqlite3* db = nullptr;
-  sqlite3_open(":memory:", &db);
+  //sqlite3* db = nullptr;
+  //sqlite3_open(":memory:", &db);
+  auto dbc = SQLiteDBManager::get();
 
   // Virtual tables require the registry/plugin API to query tables.
-  int rc = osquery::tables::attachTable(db, "failed_sample");
+  int rc = osquery::tables::attachTable(dbc.db(), "failed_sample");
   EXPECT_EQ(rc, SQLITE_ERROR);
 
   // The table attach will complete only when the table name is registered.
   Registry::add<sampleTablePlugin>("table", "sample");
-  rc = osquery::tables::attachTable(db, "sample");
+  rc = osquery::tables::attachTable(dbc.db(), "sample");
   EXPECT_EQ(rc, SQLITE_OK);
 
   std::string q = "SELECT sql FROM sqlite_temp_master WHERE tbl_name='sample';";
   QueryData results;
-  auto status = queryInternal(q, results, db);
+  auto status = queryInternal(q, results, dbc.db());
   EXPECT_EQ("CREATE VIRTUAL TABLE sample USING sample(foo INTEGER, bar TEXT)",
             results[0]["sql"]);
-  sqlite3_close(db);
 }
 }
 }
index 575e6d7..6384cd9 100644 (file)
@@ -8,11 +8,12 @@
  *
  */
 
+#include <regex>
+
 #include <arpa/inet.h>
 #include <linux/netlink.h>
 
 #include <boost/algorithm/string/split.hpp>
-#include <boost/regex.hpp>
 
 #include <osquery/core.h>
 #include <osquery/filesystem.h>
@@ -285,15 +286,15 @@ QueryData genOpenSockets(QueryContext &context) {
   }
 
   // Generate a map of socket inode to process tid.
-  boost::regex inode_regex("[0-9]+");
+  std::regex inode_regex("[0-9]+", std::regex_constants::extended);
   std::map<std::string, std::string> socket_inodes;
   for (const auto& process : processes) {
     std::map<std::string, std::string> descriptors;
     if (osquery::procDescriptors(process, descriptors).ok()) {
       for (const auto& fd : descriptors) {
         if (fd.second.find("socket:") != std::string::npos) {
-          boost::smatch inode;
-          boost::regex_search(fd.second, inode, inode_regex);
+          std::smatch inode;
+          std::regex_search(fd.second, inode, inode_regex);
           if (inode[0].str().length() > 0) {
             socket_inodes[inode[0].str()] = process;
           }
index ee9718a..ad08a23 100644 (file)
@@ -1,7 +1,7 @@
 table_name("process_memory_map")
 description("Process memory mapped files and pseudo device/regions.")
 schema([
-    Column("pid", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
     Column("start", TEXT, "Virtual start address (hex)"),
     Column("end", TEXT, "Virtual end address (hex)"),
     Column("permissions", TEXT, "r=read, w=write, x=execute, p=private (cow)"),
index e12278a..b7c9cfa 100644 (file)
@@ -2,11 +2,11 @@ table_name("crontab")
 description("Line parsed values from system and user cron/tab.")
 schema([
     Column("event", TEXT, "The job @event name (rare)"),
-    Column("minute", TEXT),
-    Column("hour", TEXT),
-    Column("day_of_month", TEXT),
-    Column("month", TEXT),
-    Column("day_of_week", TEXT),
+    Column("minute", TEXT, "The exact minute for the job"),
+    Column("hour", TEXT, "The hour of the day for the job"),
+    Column("day_of_month", TEXT, "The day of the month for the job"),
+    Column("month", TEXT, "The month of the year for the job"),
+    Column("day_of_week", TEXT, "The day of the week for the job"),
     Column("command", TEXT, "Raw command string"),
     Column("path", TEXT, "File parsed"),
 ])
index 64599d4..6f2e814 100644 (file)
@@ -1,9 +1,9 @@
 table_name("etc_services")
 description("Line-parsed /etc/services.")
 schema([
-    Column("name", TEXT),
-    Column("port", INTEGER),
-    Column("protocol", TEXT),
+    Column("name", TEXT, "Service name"),
+    Column("port", INTEGER, "Service port number"),
+    Column("protocol", TEXT, "Transport protocol (TCP/UDP)"),
     Column("aliases", TEXT, "Optional space separated list of other names for a service"),
     Column("comment", TEXT, "Optional comment for a service."),
 ])
index 59b5059..ee61bac 100644 (file)
@@ -2,8 +2,9 @@ table_name("file")
 description("Interactive filesystem attributes and metadata.")
 schema([
     Column("path", TEXT, "Absolute file path", required=True),
+    Column("directory", TEXT, "Directory of file(s)", required=True),
     Column("filename", TEXT, "Name portion of file path"),
-    Column("inode", BIGINT),
+    Column("inode", BIGINT, "Filesystem inode number"),
     Column("uid", BIGINT, "Owning user ID"),
     Column("gid", BIGINT, "Owning group ID"),
     Column("mode", TEXT, "Permission bits"),
index 03e7096..3aa0670 100644 (file)
@@ -3,8 +3,8 @@ description("Filesystem hash data.")
 schema([
     Column("path", TEXT, "Must provide a path or directory", required=True),
     Column("directory", TEXT, "Must provide a path or directory", required=True),
-    Column("md5", TEXT),
-    Column("sha1", TEXT),
-    Column("sha256", TEXT),
+    Column("md5", TEXT, "MD5 hash of provided filesystem data"),
+    Column("sha1", TEXT, "SHA1 hash of provided filesystem data"),
+    Column("sha256", TEXT, "SHA256 hash of provided filesystem data"),
 ])
 implementation("utility/hash@genHash")
index 0bf072f..c1252d0 100644 (file)
@@ -3,7 +3,7 @@ description("System logins and logouts.")
 schema([
     Column("username", TEXT),
     Column("tty", TEXT),
-    Column("pid", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
     Column("type", INTEGER),
     Column("time", INTEGER),
     Column("host", TEXT),
index 135a643..096b9a9 100644 (file)
@@ -1,10 +1,10 @@
 table_name("logged_in_users")
 description("Users with an active shell on the system.")
 schema([
-    Column("user", TEXT),
-    Column("tty", TEXT),
-    Column("host", TEXT),
-    Column("time", INTEGER),
-    Column("pid", INTEGER),
+    Column("user", TEXT, "User login name"),
+    Column("tty", TEXT, "Device name"),
+    Column("host", TEXT, "Remote hostname"),
+    Column("time", INTEGER, "Time entry was made"),
+    Column("pid", INTEGER, "Process (or thread) ID"),
 ])
 implementation("logged_in_users@genLoggedInUsers")
diff --git a/osquery/tables/specs/x/osquery_extensions.table b/osquery/tables/specs/x/osquery_extensions.table
new file mode 100644 (file)
index 0000000..984933a
--- /dev/null
@@ -0,0 +1,10 @@
+table_name("osquery_extensions")
+description("List of active osquery extensions.")
+schema([
+    Column("uuid", BIGINT),
+    Column("name", TEXT),
+    Column("version", TEXT),
+    Column("sdk_version", TEXT),
+    Column("socket", TEXT),
+])
+implementation("osquery@genOsqueryExtensions")
index 3f1682a..cd88bff 100644 (file)
@@ -4,6 +4,7 @@ schema([
     Column("version", TEXT),
     Column("config_md5", TEXT),
     Column("config_path", TEXT),
-    Column("pid", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
+    Column("extensions", TEXT),
 ])
 implementation("osquery@genOsqueryInfo")
index e854c67..56e1632 100644 (file)
@@ -2,7 +2,7 @@ table_name("passwd_changes")
 description("Track time, action changes to /etc/passwd.")
 schema([
     Column("target_path", TEXT, "The path changed"),
-    Column("time", TEXT),
+    Column("time", TEXT, "Time of the change"),
     Column("action", TEXT, "Change action (UPDATE, REMOVE, etc)"),
     Column("transaction_id", BIGINT, "ID used during bulk update"),
 ])
index 76e860c..0c9226f 100644 (file)
@@ -1,7 +1,7 @@
 table_name("process_envs")
 description("A key/value table of environment variables for each process.")
 schema([
-    Column("pid", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
     Column("key", TEXT, "Environment variable name"),
     Column("value", TEXT, "Environment variable value"),
     ForeignKey(column="pid", table="processes"),
index f3e9f97..2e87c44 100644 (file)
@@ -1,14 +1,14 @@
 table_name("process_open_sockets")
 description("Processes which have open network sockets on the system.")
 schema([
-    Column("pid", INTEGER),
-    Column("socket", INTEGER),
-    Column("family", INTEGER),
-    Column("protocol", INTEGER),
-    Column("local_address", TEXT),
-    Column("remote_address", TEXT),
-    Column("local_port", INTEGER),
-    Column("remote_port", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
+    Column("socket", INTEGER, "Socket descriptor number"),
+    Column("family", INTEGER, "Network protocol (IPv4, IPv6)"),
+    Column("protocol", INTEGER, "Transport protocol (TCP/UDP)"),
+    Column("local_address", TEXT, "Socket local address"),
+    Column("remote_address", TEXT, "Socket remote address"),
+    Column("local_port", INTEGER, "Socket local port"),
+    Column("remote_port", INTEGER, "Socket remote port"),
 ])
 implementation("system/process_open_sockets@genOpenSockets")
 
index ba18966..1c67b6d 100644 (file)
@@ -1,21 +1,23 @@
 table_name("processes")
 description("All running processes on the host system.")
 schema([
-    Column("pid", INTEGER),
+    Column("pid", INTEGER, "Process (or thread) ID"),
     Column("name", TEXT, "The process path or shorthand argv[0]"),
-    Column("path", TEXT),
+    Column("path", TEXT, "Path to executed binary"),
     Column("cmdline", TEXT, "Complete argv"),
-    Column("uid", BIGINT),
-    Column("gid", BIGINT),
-    Column("euid", BIGINT),
-    Column("egid", BIGINT),
+    Column("cwd", TEXT, "Process current working directory"),
+    Column("root", TEXT, "Process virtual root directory"),
+    Column("uid", BIGINT, "Unsigned user ID"),
+    Column("gid", BIGINT, "Unsgiend groud ID"),
+    Column("euid", BIGINT, "Unsigned effective user ID"),
+    Column("egid", BIGINT, "Unsigned effective group ID"),
     Column("on_disk", TEXT, "The process path exist yes=1, no=-1"),
-    Column("wired_size", TEXT),
-    Column("resident_size", TEXT),
-    Column("phys_footprint", TEXT),
-    Column("user_time", TEXT),
-    Column("system_time", TEXT),
-    Column("start_time", TEXT),
-    Column("parent", INTEGER),
+    Column("wired_size", TEXT, "Bytes of unpagable memory used by process"),
+    Column("resident_size", TEXT, "Bytes of private memory used by process"),
+    Column("phys_footprint", TEXT, "Bytes of total physical memory used"),
+    Column("user_time", TEXT, "CPU time spent in user space"),
+    Column("system_time", TEXT, "CPU time spent in kernel space"),
+    Column("start_time", TEXT, "Unix timestamp of process start"),
+    Column("parent", INTEGER, "Process parent's PID"),
 ])
 implementation("system/processes@genProcesses")
index c7ce6b3..662926d 100644 (file)
@@ -32,7 +32,7 @@ void genACPITable(const std::string& table, QueryData& results) {
     status = osquery::listFilesInDirectory(table_path, child_tables);
     if (status.ok()) {
       for (const auto& child_table : child_tables) {
-        genACPITable(table, results);
+        genACPITable(child_table, results);
       }
     }
 
index 8c97a05..3ca594b 100644 (file)
@@ -35,15 +35,15 @@ namespace tables {
   PROC_FILLCOM | PROC_FILLMEM | PROC_FILLSTATUS | PROC_FILLSTAT
 #endif
 
-std::string getProcName(const proc_t* proc_info) {
+inline std::string getProcName(const proc_t* proc_info) {
   return std::string(proc_info->cmd);
 }
 
-std::string getProcAttr(const std::string& attr, const proc_t* proc_info) {
+inline std::string getProcAttr(const std::string& attr, const proc_t* proc_info) {
   return "/proc/" + std::to_string(proc_info->tid) + "/" + attr;
 }
 
-std::string readProcCMDLine(const proc_t* proc_info) {
+inline std::string readProcCMDLine(const proc_t* proc_info) {
   auto attr = getProcAttr("cmdline", proc_info);
 
   std::string result;
@@ -61,20 +61,18 @@ std::string readProcCMDLine(const proc_t* proc_info) {
   return result;
 }
 
-std::string readProcLink(const proc_t* proc_info) {
+inline std::string readProcLink(const proc_t* proc_info,
+    const std::string& attr) {
   // The exe is a symlink to the binary on-disk.
-  auto attr = getProcAttr("exe", proc_info);
-  long path_max = pathconf(attr.c_str(), _PC_PATH_MAX);
-  auto link_path = (char*)malloc(path_max);
-  memset(link_path, 0, path_max);
+  auto attr_path = getProcAttr("exe", proc_info);
 
   std::string result;
-  int bytes = readlink(attr.c_str(), link_path, path_max);
+  char link_path[PATH_MAX] = {0};
+  auto bytes = readlink(attr_path.c_str(), link_path, sizeof(link_path) - 1);
   if (bytes >= 0) {
     result = std::string(link_path);
   }
 
-  free(link_path);
   return result;
 }
 
@@ -177,23 +175,38 @@ QueryData genProcesses(QueryContext& context) {
 
     Row r;
     r["pid"] = INTEGER(proc_info->tid);
-    r["uid"] = BIGINT((unsigned int)proc_info->ruid);
-    r["gid"] = BIGINT((unsigned int)proc_info->rgid);
-    r["euid"] = BIGINT((unsigned int)proc_info->euid);
-    r["egid"] = BIGINT((unsigned int)proc_info->egid);
+    r["parent"] = INTEGER(proc_info->ppid);
+    r["path"] = readProcLink(proc_info, "exe");
     r["name"] = getProcName(proc_info);
+
+    // Read/parse cmdline arguments.
     std::string cmdline = readProcCMDLine(proc_info);
     boost::algorithm::trim(cmdline);
     r["cmdline"] = cmdline;
-    r["path"] = readProcLink(proc_info);
+    r["cwd"] = readProcLink(proc_info, "cwd");
+    r["root"] = readProcLink(proc_info, "root");
+
+    r["uid"] = BIGINT((unsigned int)proc_info->ruid);
+    r["gid"] = BIGINT((unsigned int)proc_info->rgid);
+    r["euid"] = BIGINT((unsigned int)proc_info->euid);
+    r["egid"] = BIGINT((unsigned int)proc_info->egid);
+
+    // If the path of the executable that started the process is available and
+    // the path exists on disk, set on_disk to 1. If the path is not
+    // available, set on_disk to -1. If, and only if, the path of the
+    // executable is available and the file does NOT exist on disk, set on_disk
+    // to 0.
     r["on_disk"] = osquery::pathExists(r["path"]).toString();
 
+    // size/memory information
+    r["wired_size"] = "0"; // No support for unpagable counters in linux.
     r["resident_size"] = INTEGER(proc_info->vm_rss);
     r["phys_footprint"] = INTEGER(proc_info->vm_size);
+
+    // time information
     r["user_time"] = INTEGER(proc_info->utime);
     r["system_time"] = INTEGER(proc_info->stime);
     r["start_time"] = INTEGER(proc_info->start_time);
-    r["parent"] = INTEGER(proc_info->ppid);
 
     results.push_back(r);
     standardFreeproc(proc_info);
index 1c06125..5ae5b4d 100644 (file)
@@ -12,6 +12,7 @@
 #include <pwd.h>
 
 #include <osquery/core.h>
+#include <osquery/filesystem.h>
 #include <osquery/logger.h>
 #include <osquery/tables.h>
 
@@ -71,7 +72,7 @@ QueryData genSharedMemory(QueryContext &context) {
     r["dtime"] = BIGINT(shmseg.shm_dtime);
     r["ctime"] = BIGINT(shmseg.shm_ctime);
 
-    r["permissions"] = INTEGER(ipcp->mode);
+    r["permissions"] = lsperms(ipcp->mode);
     r["size"] = BIGINT(shmseg.shm_segsz);
     r["attached"] = INTEGER(shmseg.shm_nattch);
     r["status"] = (ipcp->mode & SHM_DEST) ? "dest" : "";
index 5255202..f7bd8b2 100644 (file)
@@ -29,7 +29,7 @@ class {{class_name}} {
 
 class {{table_name_cc}}TablePlugin : public TablePlugin {
  private:
-  TableColumns columns() {
+  TableColumns columns() const {
     return {
 {% for column in schema %}\
       {"{{column.name}}", "{{column.type.affinity}}"}\
index 65a1292..ea7abbf 100644 (file)
 namespace osquery {
 namespace tables {
 
-inline std::string lsperms(int mode) {
-  static const char rwx[] = {'0', '1', '2', '3', '4', '5', '6', '7'};
-  std::string bits;
+void genFileInfo(const std::string& path,
+                 const std::string& filename,
+                 const std::string& dir,
+                 QueryData& results) {
+  // Must provide the path, filename, directory separate from boost path->string
+  // helpers to match any explicit (query-parsed) predicate constraints.
+  struct stat file_stat, link_stat;
+  if (lstat(path.c_str(), &link_stat) < 0 || stat(path.c_str(), &file_stat)) {
+    // Path was not real, had too may links, or could not be accessed.
+    return;
+  }
+
+  Row r;
+  r["path"] = path;
+  r["filename"] = filename;
+  r["directory"] = dir;
+
+  r["inode"] = BIGINT(file_stat.st_ino);
+  r["uid"] = BIGINT(file_stat.st_uid);
+  r["gid"] = BIGINT(file_stat.st_gid);
+  r["mode"] = lsperms(file_stat.st_mode);
+  r["device"] = BIGINT(file_stat.st_rdev);
+  r["size"] = BIGINT(file_stat.st_size);
+  r["block_size"] = INTEGER(file_stat.st_blksize);
+  r["hard_links"] = INTEGER(file_stat.st_nlink);
 
-  bits += rwx[(mode >> 9) & 7];
-  bits += rwx[(mode >> 6) & 7];
-  bits += rwx[(mode >> 3) & 7];
-  bits += rwx[(mode >> 0) & 7];
-  return bits;
+  // Times
+  r["atime"] = BIGINT(file_stat.st_atime);
+  r["mtime"] = BIGINT(file_stat.st_mtime);
+  r["ctime"] = BIGINT(file_stat.st_ctime);
+
+  // Type booleans
+  r["is_file"] = (!S_ISDIR(file_stat.st_mode)) ? "1" : "0";
+  r["is_dir"] = (S_ISDIR(file_stat.st_mode)) ? "1" : "0";
+  r["is_link"] = (S_ISLNK(link_stat.st_mode)) ? "1" : "0";
+  r["is_char"] = (S_ISCHR(file_stat.st_mode)) ? "1" : "0";
+  r["is_block"] = (S_ISBLK(file_stat.st_mode)) ? "1" : "0";
+
+  results.push_back(r);
 }
 
 QueryData genFile(QueryContext& context) {
@@ -35,40 +65,30 @@ QueryData genFile(QueryContext& context) {
   auto paths = context.constraints["path"].getAll(EQUALS);
   for (const auto& path_string : paths) {
     boost::filesystem::path path = path_string;
+    genFileInfo(path_string,
+                path.filename().string(),
+                path.parent_path().string(),
+                results);
+  }
 
-    Row r;
-    r["path"] = path.string();
-    r["filename"] = path.filename().string();
-
-    struct stat file_stat, link_stat;
-    if (lstat(path.string().c_str(), &link_stat) < 0 ||
-        stat(path.string().c_str(), &file_stat)) {
-      // Path was not real, had too may links, or could not be accessed.
+  // Now loop through constraints using the directory column constraint.
+  auto directories = context.constraints["directory"].getAll(EQUALS);
+  for (const auto& directory_string : directories) {
+    boost::filesystem::path directory = directory_string;
+    if (!boost::filesystem::is_directory(directory)) {
       continue;
     }
 
-    r["inode"] = BIGINT(file_stat.st_ino);
-    r["uid"] = BIGINT(file_stat.st_uid);
-    r["gid"] = BIGINT(file_stat.st_gid);
-    r["mode"] = std::string(lsperms(file_stat.st_mode));
-    r["device"] = BIGINT(file_stat.st_rdev);
-    r["size"] = BIGINT(file_stat.st_size);
-    r["block_size"] = INTEGER(file_stat.st_blksize);
-    r["hard_links"] = INTEGER(file_stat.st_nlink);
-
-    // Times
-    r["atime"] = BIGINT(file_stat.st_atime);
-    r["mtime"] = BIGINT(file_stat.st_mtime);
-    r["ctime"] = BIGINT(file_stat.st_ctime);
-
-    // Type booleans
-    r["is_file"] = (!S_ISDIR(file_stat.st_mode)) ? "1" : "0";
-    r["is_dir"] = (S_ISDIR(file_stat.st_mode)) ? "1" : "0";
-    r["is_link"] = (S_ISLNK(link_stat.st_mode)) ? "1" : "0";
-    r["is_char"] = (S_ISCHR(file_stat.st_mode)) ? "1" : "0";
-    r["is_block"] = (S_ISBLK(file_stat.st_mode)) ? "1" : "0";
-
-    results.push_back(r);
+    // Iterate over the directory and generate a hash for each regular file.
+    boost::filesystem::directory_iterator begin(directory), end;
+    for (; begin != end; ++begin) {
+      if (boost::filesystem::is_regular_file(begin->status())) {
+        genFileInfo(begin->path().string(),
+                    begin->path().filename().string(),
+                    directory_string,
+                    results);
+      }
+    }
   }
 
   return results;
index ad5bec7..6b152d7 100644 (file)
 #include <osquery/hash.h>
 #include <osquery/tables.h>
 
+namespace fs = boost::filesystem;
+
 namespace osquery {
 namespace tables {
 
+void genHashForFile(const std::string& path,
+                    const std::string& dir,
+                    QueryData& results) {
+  // Must provide the path, filename, directory separate from boost path->string
+  // helpers to match any explicit (query-parsed) predicate constraints.
+  Row r;
+  r["path"] = path;
+  r["directory"] = dir;
+  r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, path);
+  r["sha1"] = osquery::hashFromFile(HASH_TYPE_SHA1, path);
+  r["sha256"] = osquery::hashFromFile(HASH_TYPE_SHA256, path);
+  results.push_back(r);
+}
+
 QueryData genHash(QueryContext& context) {
   QueryData results;
 
+  // The query must provide a predicate with constratins including path or
+  // directory. We search for the parsed predicate constraints with the equals
+  // operator.
   auto paths = context.constraints["path"].getAll(EQUALS);
   for (const auto& path_string : paths) {
     boost::filesystem::path path = path_string;
@@ -27,15 +46,10 @@ QueryData genHash(QueryContext& context) {
       continue;
     }
 
-    Row r;
-    r["path"] = path.string();
-    r["directory"] = path.parent_path().string();
-    r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, path.string());
-    r["sha1"] = osquery::hashFromFile(HASH_TYPE_SHA1, path.string());
-    r["sha256"] = osquery::hashFromFile(HASH_TYPE_SHA256, path.string());
-    results.push_back(r);
+    genHashForFile(path_string, path.parent_path().string(), results);
   }
 
+  // Now loop through constraints using the directory column constraint.
   auto directories = context.constraints["directory"].getAll(EQUALS);
   for (const auto& directory_string : directories) {
     boost::filesystem::path directory = directory_string;
@@ -46,17 +60,9 @@ QueryData genHash(QueryContext& context) {
     // Iterate over the directory and generate a hash for each regular file.
     boost::filesystem::directory_iterator begin(directory), end;
     for (; begin != end; ++begin) {
-      Row r;
-      r["path"] = begin->path().string();
-      r["directory"] = directory_string;
       if (boost::filesystem::is_regular_file(begin->status())) {
-        r["md5"] = osquery::hashFromFile(HASH_TYPE_MD5, begin->path().string());
-        r["sha1"] =
-            osquery::hashFromFile(HASH_TYPE_SHA1, begin->path().string());
-        r["sha256"] =
-            osquery::hashFromFile(HASH_TYPE_SHA256, begin->path().string());
+        genHashForFile(begin->path().string(), directory_string, results);
       }
-      results.push_back(r);
     }
   }
 
index c15e7ed..a1e6a52 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <osquery/config.h>
 #include <osquery/core.h>
+#include <osquery/extensions.h>
 #include <osquery/flags.h>
 #include <osquery/logger.h>
 #include <osquery/sql.h>
@@ -48,6 +49,27 @@ QueryData genOsqueryFlags(QueryContext& context) {
   return results;
 }
 
+QueryData genOsqueryExtensions(QueryContext& context) {
+  QueryData results;
+
+  ExtensionList extensions;
+  if (!getExtensions(extensions).ok()) {
+    return {};
+  }
+
+  for (const auto& extenion : extensions) {
+    Row r;
+    r["uuid"] = TEXT(extenion.first);
+    r["name"] = extenion.second.name;
+    r["version"] = extenion.second.version;
+    r["sdk_version"] = extenion.second.sdk_version;
+    r["socket"] = getExtensionSocket(extenion.first);
+    results.push_back(r);
+  }
+
+  return results;
+}
+
 QueryData genOsqueryInfo(QueryContext& context) {
   QueryData results;
 
@@ -65,6 +87,8 @@ QueryData genOsqueryInfo(QueryContext& context) {
   }
 
   r["config_path"] = Flag::get().getValue("config_path");
+  r["extensions"] =
+      (pingExtension(FLAGS_extensions_socket).ok()) ? "active" : "inactive";
   results.push_back(r);
 
   return results;
index 8a4901b..e7fbc5d 100644 (file)
@@ -1,5 +1,5 @@
 Name: osquery
-Version: 1.4.0
+Version: 1.4.1
 Release: 0
 License: Apache-2.0 and GPLv2
 Summary: A SQL powered operating system instrumentation, monitoring framework.