2015-02-03 05:21:36 +00:00
|
|
|
/*
|
2016-02-11 19:48:58 +00:00
|
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
2015-02-03 05:21:36 +00:00
|
|
|
* 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 <osquery/core.h>
|
|
|
|
#include <osquery/logger.h>
|
2016-08-31 22:32:20 +00:00
|
|
|
#include <osquery/registry.h>
|
2015-02-03 05:21:36 +00:00
|
|
|
#include <osquery/sql.h>
|
|
|
|
#include <osquery/tables.h>
|
|
|
|
|
2017-03-21 05:03:09 +00:00
|
|
|
#include "osquery/core/conversions.h"
|
|
|
|
|
2015-02-03 05:21:36 +00:00
|
|
|
namespace osquery {
|
|
|
|
|
2015-03-18 19:01:58 +00:00
|
|
|
FLAG(int32, value_max, 512, "Maximum returned row value size");
|
|
|
|
|
2017-01-07 20:21:35 +00:00
|
|
|
CREATE_LAZY_REGISTRY(SQLPlugin, "sql");
|
|
|
|
|
2016-08-15 08:33:17 +00:00
|
|
|
SQL::SQL(const std::string& q) {
|
2017-01-21 06:52:47 +00:00
|
|
|
TableColumns table_columns;
|
|
|
|
q_ = q;
|
|
|
|
status_ = getQueryColumns(q_, table_columns);
|
|
|
|
if (status_.ok()) {
|
|
|
|
for (auto c : table_columns) {
|
|
|
|
columns_.push_back(std::get<0>(c));
|
|
|
|
}
|
|
|
|
status_ = query(q_, results_);
|
|
|
|
}
|
2016-08-15 08:33:17 +00:00
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
|
2016-08-15 08:33:17 +00:00
|
|
|
const QueryData& SQL::rows() const {
|
|
|
|
return results_;
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
|
2017-01-21 06:52:47 +00:00
|
|
|
const ColumnNames& SQL::columns() {
|
|
|
|
return columns_;
|
|
|
|
}
|
|
|
|
|
2016-08-15 08:33:17 +00:00
|
|
|
bool SQL::ok() {
|
|
|
|
return status_.ok();
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
|
2016-08-15 08:33:17 +00:00
|
|
|
const Status& SQL::getStatus() const {
|
|
|
|
return status_;
|
|
|
|
}
|
2015-01-23 22:52:07 +00:00
|
|
|
|
2016-08-15 08:33:17 +00:00
|
|
|
std::string SQL::getMessageString() {
|
|
|
|
return status_.toString();
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
|
2016-02-25 01:58:58 +00:00
|
|
|
static inline void escapeNonPrintableBytes(std::string& data) {
|
2015-11-02 08:46:04 +00:00
|
|
|
std::string escaped;
|
2016-08-15 08:33:17 +00:00
|
|
|
// clang-format off
|
2015-11-02 08:46:04 +00:00
|
|
|
char const hex_chars[16] = {
|
2016-08-15 08:33:17 +00:00
|
|
|
'0', '1', '2', '3', '4', '5', '6', '7',
|
|
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
2015-11-02 08:46:04 +00:00
|
|
|
};
|
2016-08-15 08:33:17 +00:00
|
|
|
// clang-format on
|
2015-11-02 08:46:04 +00:00
|
|
|
|
|
|
|
bool needs_replacement = false;
|
|
|
|
for (size_t i = 0; i < data.length(); i++) {
|
2016-02-25 01:58:58 +00:00
|
|
|
if (((unsigned char)data[i]) < 0x20 || ((unsigned char)data[i]) >= 0x80) {
|
2015-11-02 08:46:04 +00:00
|
|
|
needs_replacement = true;
|
|
|
|
escaped += "\\x";
|
2016-02-25 01:58:58 +00:00
|
|
|
escaped += hex_chars[(((unsigned char)data[i])) >> 4];
|
|
|
|
escaped += hex_chars[((unsigned char)data[i] & 0x0F) >> 0];
|
2015-11-02 08:46:04 +00:00
|
|
|
} else {
|
|
|
|
escaped += data[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only replace if any escapes were made.
|
|
|
|
if (needs_replacement) {
|
2016-02-25 01:58:58 +00:00
|
|
|
data = std::move(escaped);
|
2015-11-02 08:46:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-25 01:58:58 +00:00
|
|
|
void escapeNonPrintableBytesEx(std::string& data) {
|
|
|
|
return escapeNonPrintableBytes(data);
|
|
|
|
}
|
|
|
|
|
2015-11-02 08:46:04 +00:00
|
|
|
void SQL::escapeResults() {
|
|
|
|
for (auto& row : results_) {
|
|
|
|
for (auto& column : row) {
|
|
|
|
escapeNonPrintableBytes(column.second);
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QueryData SQL::selectAllFrom(const std::string& table) {
|
2015-02-06 17:42:03 +00:00
|
|
|
PluginResponse response;
|
2016-02-25 01:58:58 +00:00
|
|
|
Registry::call("table", table, {{"action", "generate"}}, response);
|
2015-02-06 17:42:03 +00:00
|
|
|
return response;
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QueryData SQL::selectAllFrom(const std::string& table,
|
|
|
|
const std::string& column,
|
2015-05-20 20:27:53 +00:00
|
|
|
ConstraintOperator op,
|
2015-02-03 05:21:36 +00:00
|
|
|
const std::string& expr) {
|
2015-02-06 17:42:03 +00:00
|
|
|
PluginRequest request = {{"action", "generate"}};
|
2016-02-25 01:58:58 +00:00
|
|
|
{
|
2016-05-09 17:32:33 +00:00
|
|
|
// Create a fake content, there will be no caching.
|
2016-02-25 01:58:58 +00:00
|
|
|
QueryContext ctx;
|
|
|
|
ctx.constraints[column].add(Constraint(op, expr));
|
|
|
|
TablePlugin::setRequestFromContext(ctx, request);
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
|
2016-02-25 01:58:58 +00:00
|
|
|
PluginResponse response;
|
2015-02-06 17:42:03 +00:00
|
|
|
Registry::call("table", table, request, response);
|
|
|
|
return response;
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
|
2015-02-19 23:19:00 +00:00
|
|
|
Status SQLPlugin::call(const PluginRequest& request, PluginResponse& response) {
|
|
|
|
response.clear();
|
|
|
|
if (request.count("action") == 0) {
|
|
|
|
return Status(1, "SQL plugin must include a request action");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (request.at("action") == "query") {
|
|
|
|
return this->query(request.at("query"), response);
|
|
|
|
} else if (request.at("action") == "columns") {
|
2015-05-20 20:27:53 +00:00
|
|
|
TableColumns columns;
|
2015-02-19 23:19:00 +00:00
|
|
|
auto status = this->getQueryColumns(request.at("query"), columns);
|
|
|
|
// Convert columns to response
|
|
|
|
for (const auto& column : columns) {
|
2016-08-31 22:32:20 +00:00
|
|
|
response.push_back(
|
|
|
|
{{"n", std::get<0>(column)},
|
|
|
|
{"t", columnTypeName(std::get<1>(column))},
|
|
|
|
{"o", INTEGER(static_cast<size_t>(std::get<2>(column)))}});
|
2015-02-19 23:19:00 +00:00
|
|
|
}
|
|
|
|
return status;
|
2015-02-23 05:56:52 +00:00
|
|
|
} else if (request.at("action") == "attach") {
|
|
|
|
// Attach a virtual table name using an optional included definition.
|
|
|
|
return this->attach(request.at("table"));
|
|
|
|
} else if (request.at("action") == "detach") {
|
|
|
|
this->detach(request.at("table"));
|
|
|
|
return Status(0, "OK");
|
2017-03-16 01:48:01 +00:00
|
|
|
} else if (request.at("action") == "tables") {
|
|
|
|
std::vector<std::string> tables;
|
|
|
|
auto status = this->getQueryTables(request.at("query"), tables);
|
|
|
|
if (status.ok()) {
|
|
|
|
for (const auto& table : tables) {
|
|
|
|
response.push_back({{"t", table}});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return status;
|
2015-02-19 23:19:00 +00:00
|
|
|
}
|
|
|
|
return Status(1, "Unknown action");
|
|
|
|
}
|
|
|
|
|
2015-02-03 05:21:36 +00:00
|
|
|
Status query(const std::string& q, QueryData& results) {
|
2015-02-19 23:19:00 +00:00
|
|
|
return Registry::call(
|
|
|
|
"sql", "sql", {{"action", "query"}, {"query", q}}, results);
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|
|
|
|
|
2015-05-20 20:27:53 +00:00
|
|
|
Status getQueryColumns(const std::string& q, TableColumns& columns) {
|
2015-02-19 23:19:00 +00:00
|
|
|
PluginResponse response;
|
|
|
|
auto status = Registry::call(
|
|
|
|
"sql", "sql", {{"action", "columns"}, {"query", q}}, response);
|
|
|
|
|
|
|
|
// Convert response to columns
|
|
|
|
for (const auto& item : response) {
|
2016-08-31 22:32:20 +00:00
|
|
|
columns.push_back(make_tuple(
|
|
|
|
item.at("n"), columnTypeName(item.at("t")), ColumnOptions::DEFAULT));
|
2015-02-19 23:19:00 +00:00
|
|
|
}
|
|
|
|
return status;
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|
2017-03-16 01:48:01 +00:00
|
|
|
|
2017-03-21 05:03:09 +00:00
|
|
|
Status mockGetQueryTables(std::string copy_q,
|
|
|
|
std::vector<std::string>& tables) {
|
|
|
|
std::transform(copy_q.begin(), copy_q.end(), copy_q.begin(), ::tolower);
|
|
|
|
auto offset_from = copy_q.find("from ");
|
|
|
|
if (offset_from == std::string::npos) {
|
|
|
|
return Status(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto simple_tables = osquery::split(copy_q.substr(offset_from + 5), ",");
|
|
|
|
for (const auto& table : simple_tables) {
|
|
|
|
tables.push_back(table);
|
|
|
|
}
|
|
|
|
return Status(0);
|
|
|
|
}
|
|
|
|
|
2017-03-16 01:48:01 +00:00
|
|
|
Status getQueryTables(const std::string& q, std::vector<std::string>& tables) {
|
2017-03-21 05:03:09 +00:00
|
|
|
if (!Registry::get().exists("sql", "sql") && kToolType == ToolType::TEST) {
|
|
|
|
// We 'mock' this functionality for internal tests.
|
|
|
|
return mockGetQueryTables(q, tables);
|
|
|
|
}
|
|
|
|
|
2017-03-16 01:48:01 +00:00
|
|
|
PluginResponse response;
|
|
|
|
auto status = Registry::call(
|
|
|
|
"sql", "sql", {{"action", "tables"}, {"query", q}}, response);
|
2017-03-21 05:03:09 +00:00
|
|
|
|
2017-03-16 01:48:01 +00:00
|
|
|
for (const auto& table : response) {
|
|
|
|
tables.push_back(table.at("t"));
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
2015-02-03 05:21:36 +00:00
|
|
|
}
|