diff options
Diffstat (limited to 'extcap/falcodump.cpp')
-rw-r--r-- | extcap/falcodump.cpp | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/extcap/falcodump.cpp b/extcap/falcodump.cpp new file mode 100644 index 00000000..b8710644 --- /dev/null +++ b/extcap/falcodump.cpp @@ -0,0 +1,1016 @@ +/* falcodump.cpp + * Falcodump is an extcap tool which dumps logs using Falco source plugins. + * https://falco.org/docs/plugins/ + * + * Adapted from sdjournal. + * Copyright 2022, Gerald Combs and Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * To do: + * - Pull plugin source description from list_open_params? + * - Add filtering. + * - Add an option to dump plugin fields. + * - Add options for credentials. + * - Let the user create preconfigured interfaces. + * - Exit more cleanly (see MRs 2063 and 7673). + * - Better config schema default value parsing? Would likely require a schema change. + * - Make sure all types are handled in parse_schema_properties. + * - Handle "required" config schema annotation (Okta). + */ + +#include "config.h" + +#include <sinsp.h> +#include <plugin_manager.h> + +#define WS_LOG_DOMAIN "falcodump" + +#include <extcap/extcap-base.h> + +#include <wsutil/file_util.h> +#include <wsutil/filesystem.h> +#include <wsutil/json_dumper.h> +#include <wsutil/privileges.h> +#include <wsutil/utf8_entities.h> +#include <wsutil/wsjson.h> +#include <wsutil/wslog.h> + +#define FALCODUMP_VERSION_MAJOR "1" +#define FALCODUMP_VERSION_MINOR "0" +#define FALCODUMP_VERSION_RELEASE "0" + +#define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>" + +// We load our plugins and fetch their configs before we set our log level. +// #define DEBUG_JSON_PARSING +// #define DEBUG_SINSP + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_PLUGIN_API_VERSION, + OPT_PLUGIN_SOURCE, + OPT_SCHEMA_PROPERTIES_START, +}; + +// "s3DownloadConcurrency": { +// "type": "integer", +// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)" +// }, +struct config_properties { + std::string name; + std::string display; // "title" property || name + std::string option; // Command line option including leading dashes (lowercase display name) + int option_index; // Starts from OPT_SCHEMA_PROPERTIES_START + std::string type; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES" + std::string description; + std::string default_value; + std::vector<std::string>enum_values; + std::string current_value; +}; + +struct plugin_configuration { + std::vector<struct config_properties> property_list; + + std::string json_config() { + json_dumper dumper = {}; + dumper.output_string = g_string_new(NULL); + + json_dumper_begin_object(&dumper); + + for (const auto &prop : property_list) { + if (prop.type == "BEGIN_CONFIG_PROPERTIES") { + json_dumper_set_member_name(&dumper, prop.name.c_str()); + json_dumper_begin_object(&dumper); + continue; + } else if (prop.type == "END_CONFIG_PROPERTIES") { + json_dumper_end_object(&dumper); + continue; + } + + if (prop.current_value == prop.default_value) { + continue; + } + + json_dumper_set_member_name(&dumper, prop.name.c_str()); + if (prop.type == "string" || prop.type == "selector") { + json_dumper_value_string(&dumper, prop.current_value.c_str()); + } else { + json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str()); + } + } + + json_dumper_end_object(&dumper); + json_dumper_finish(&dumper); + std::string config_blob = dumper.output_string->str; + ws_debug("configuration: %s", dumper.output_string->str); + g_string_free(dumper.output_string, TRUE); + return config_blob; + } +}; + +// Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c. +// XXX Use g_file_get_contents or GMappedFile instead? +static int +fgetline(char *buf, int size, FILE *fp) +{ + if (fgets(buf, size, fp)) { + int len = (int)strcspn(buf, "\r\n"); + buf[len] = '\0'; + return len; + } + return -1; +} + +static const size_t MAX_AWS_LINELEN = 2048; +void print_cloudtrail_aws_profile_config(int arg_num, const char *display, const char *description) { + char buf[MAX_AWS_LINELEN]; + char profile[MAX_AWS_LINELEN]; + FILE *aws_fp; + std::set<std::string>profiles; + + // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + char *cred_path = g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE")); + if (cred_path == NULL) { + cred_path = g_build_filename(g_get_home_dir(), ".aws", "credentials", (gchar *)NULL); + } + + aws_fp = ws_fopen(cred_path, "r"); + g_free(cred_path); + + if (aws_fp != NULL) { + while (fgetline(buf, sizeof(buf), aws_fp) >= 0) { + if (sscanf(buf, "[%2047[^]]s]", profile) == 1) { + if (strcmp(profile, "default") == 0) { + continue; + } + profiles.insert(profile); + } + } + fclose(aws_fp); + } + + char *conf_path = g_strdup(g_getenv("AWS_CONFIG_FILE")); + if (conf_path == NULL) { + conf_path = g_build_filename(g_get_home_dir(), ".aws", "config", (gchar *)NULL); + } + + aws_fp = ws_fopen(conf_path, "r"); + g_free(conf_path); + + if (aws_fp != NULL) { + while (fgetline(buf, sizeof(buf), aws_fp) >= 0) { + if (sscanf(buf, "[profile %2047[^]]s]", profile) == 1) { + if (strcmp(profile, "default") == 0) { + continue; + } + profiles.insert(profile); + } + } + fclose(aws_fp); + } + + const char *aws_profile_env = g_getenv("AWS_PROFILE"); + for (auto &profile : profiles) { + if (aws_profile_env && profile == aws_profile_env) { + aws_profile_env = nullptr; + } + } + if (aws_profile_env) { + profiles.insert(aws_profile_env); + } + + printf( + "arg {number=%d}" + "{call=--cloudtrail-aws-profile}" + "{display=%s}" + "{type=editselector}" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, display, description); + printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num); + for (auto &profile : profiles) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "\n", + arg_num, profile.c_str(), profile.c_str()); + } +} + +void print_cloudtrail_aws_region_config(int arg_num, const char *display, const char *description) { + // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort + std::set<std::string> regions = { + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-south-2", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ap-southeast-4", + "ca-central-1", + "eu-central-1", + "eu-central-2", + "eu-north-1", + "eu-south-1", + "eu-south-2", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + }; + + const char *aws_region_env = g_getenv("AWS_REGION"); + for (auto ®ion : regions) { + if (aws_region_env && region == aws_region_env) { + aws_region_env = nullptr; + } + } + if (aws_region_env) { + regions.insert(aws_region_env); + } + + printf( + "arg {number=%d}" + "{call=--cloudtrail-aws-region}" + "{display=%s}" + "{type=editselector}" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, display, description); + printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num); + + for (auto ®ion : regions) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "\n", + arg_num, region.c_str(), region.c_str()); + } +} + + +// Load our plugins. This should match the behavior of the Falco Bridge dissector. +static void load_plugins(sinsp &inspector) { + WS_DIR *dir; + WS_DIRENT *file; + char *plugin_paths[] = { + g_build_filename(get_plugins_dir_with_version(), "falco", NULL), + g_build_filename(get_plugins_pers_dir_with_version(), "falco", NULL) + }; + + for (size_t idx = 0; idx < 2; idx++) { + char *plugin_path = plugin_paths[idx]; + if ((dir = ws_dir_open(plugin_path, 0, NULL)) != NULL) { + while ((file = ws_dir_read_name(dir)) != NULL) { + char *libname = g_build_filename(plugin_path, ws_dir_get_name(file), NULL); + try { + auto plugin = inspector.register_plugin(libname); + ws_debug("Registered plugin %s via %s", plugin->name().c_str(), libname); + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + } + g_free(libname); + } + ws_dir_close(dir); + } + g_free(plugin_path); + } +} + +// Given a key, try to find its value in a JSON object. +// Returns (value, true) on success, or (err_str, false) on failure. +const std::pair<const std::string,bool> find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(object_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete", false); + default: + break; + } + + tokens.resize(num_tokens); + json_parse(object_blob.c_str(), tokens.data(), num_tokens); + for (int idx = 0; idx < num_tokens - 1; idx++) { + jsmntok_t &k_tok = tokens[idx]; + jsmntok_t &v_tok = tokens[idx+1]; + std::string cur_key = object_blob.substr(k_tok.start, k_tok.end - k_tok.start); + if (cur_key == key && k_tok.type == JSMN_STRING && v_tok.type == value_type) { + std::string value = object_blob.substr(v_tok.start, v_tok.end - v_tok.start); + return std::pair<std::string,bool>(value, true); + } +#ifdef DEBUG_JSON_PARSING + else if (cur_key == key) ws_warning("|%s(%d): %s(%d)|\n", cur_key.c_str(), k_tok.type, object_blob.substr(v_tok.start, v_tok.end - v_tok.start).c_str(), v_tok.type); +#endif + } + return std::pair<const std::string,bool>("", false); +} + +// Given an RFC 6901-style JSON pointer, try to find its value in a JSON object. +// Returns (value, true) on success, or (err_str, false) on failure. +const std::pair<const std::string,bool> find_json_pointer_value(const std::string &object_blob, const std::string &pointer, int value_type) { + std::string blob = object_blob; + std::istringstream ob_stream(pointer); + std::string token; + while (std::getline(ob_stream, token, '/')) { + if (token == "#" || token.empty()) { + continue; + } + std::pair<std::string,bool> jv = find_json_object_value(blob, token, value_type); + if (!jv.second) { +#ifdef DEBUG_JSON_PARSING + ws_warning("JSON pointer %s not found at %s", blob.c_str(), token.c_str()); +#endif + return std::pair<std::string,bool>("", false); + } + blob = jv.first; + } +#ifdef DEBUG_JSON_PARSING + ws_warning("JSON pointer %s = %s ... %s", pointer.c_str(), blob.substr(0, 10).c_str(), blob.substr(blob.size() - 10, 10).c_str()); +#endif + return std::pair<const std::string,bool>(blob, true); +} + +// Convert a JSON array to a string vector. +// Returns (vector, true) on success, or (err_str, false) on failure. +const std::pair<std::vector<std::string>,bool> get_json_array(const std::string &array_blob) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(array_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false); + case JSMN_ERROR_PART: + { + return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false); + } + default: + break; + } + + tokens.resize(num_tokens); + json_parse(array_blob.c_str(), tokens.data(), num_tokens); + std::vector<std::string> elements; + // First token is the full array. + for (int idx = 1; idx < num_tokens; idx++) { + jsmntok_t &el_tok = tokens[idx]; + elements.push_back(array_blob.substr(el_tok.start, el_tok.end - el_tok.start)); + } +#ifdef DEBUG_JSON_PARSING + ws_warning("%s: %d", array_blob.c_str(), (int)elements.size()); +#endif + return std::pair<std::vector<std::string>,bool>(elements, true); +} + +// Given a JSON blob containing a schema properties object, add each property to the +// given plugin config. +const std::pair<const std::string,bool> get_schema_properties(const std::string props_blob, int &opt_idx, const std::string option_prefix, const std::string plugin_name, std::vector<struct config_properties> &property_list) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(props_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete", false); + default: + break; + } + + tokens.resize(num_tokens); + json_parse(props_blob.c_str(), tokens.data(), num_tokens); + + if (tokens[0].type != JSMN_OBJECT) { + return std::pair<std::string,bool>("malformed", false); + } + + char *plugin_name_lower = g_ascii_strdown(plugin_name.c_str(), -1); + + // We have an object blob which contains a list of properties and property blobs, e.g. + // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } } + // Skip over the outer { ... } and process the contents as pairs. + for (int idx = 1; idx < num_tokens - 2; idx++) { + jsmntok_t &n_tok = tokens[idx]; + jsmntok_t &p_tok = tokens[idx+1]; + + std::string name = props_blob.substr(n_tok.start, n_tok.end - n_tok.start); + std::string display = name; + std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start); + std::vector<std::string> enum_values; + + // XXX Check for errors? + int prop_tokens = json_parse(property_blob.c_str(), NULL, 0); + switch (prop_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid property", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete property", false); + default: + break; + } +#ifdef DEBUG_JSON_PARSING + ws_warning("property %s [%d]\n", name.c_str(), prop_tokens); +#endif + + std::pair<std::string,bool> jv = find_json_object_value(property_blob, "properties", JSMN_OBJECT); + if (jv.second) { + config_properties properties = { + name, + display, + "", + -1, + "BEGIN_CONFIG_PROPERTIES", + "", + "", + enum_values, + "", + }; + property_list.push_back(properties); + get_schema_properties(jv.first, opt_idx, option_prefix + "-" + name, plugin_name, property_list); + properties = { + name, + display, + "", + -1, + "END_CONFIG_PROPERTIES", + "", + "", + enum_values, + "", + }; + property_list.push_back(properties); + idx += prop_tokens; + continue; + } + + jv = find_json_object_value(property_blob, "title", JSMN_STRING); + if (jv.second) { + display = jv.first; + } + // else split+capitalize "name"? + + jv = find_json_object_value(property_blob, "type", JSMN_STRING); + if (!jv.second) { + return std::pair<std::string,bool>("missing type", false); + } + std::string type = jv.first; + jv = find_json_object_value(property_blob, "description", JSMN_STRING); + if (!jv.second) { + return std::pair<std::string,bool>("missing description", false); + } + std::string description = jv.first; + std::string default_value; + jv = find_json_object_value(property_blob, "default", JSMN_STRING); + if (jv.second) { + default_value = jv.first; + } else { + std::string default_pfx = "(Default: "; + size_t pfx_pos = description.rfind(default_pfx); + if (pfx_pos != std::string::npos) { + default_value = description.substr(pfx_pos + default_pfx.size()); + pfx_pos = default_value.rfind(")"); + default_value = default_value.erase(pfx_pos); + } + } + jv = find_json_object_value(property_blob, "enum", JSMN_ARRAY); + if (jv.second) { + const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first); + if (ja.second) { + enum_values = ja.first; + type = "selector"; + } + } +#ifdef DEBUG_JSON_PARSING + ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size()); +#endif + const char *call = g_ascii_strdown(name.c_str(), -1); + config_properties properties = { + name, + display, + std::string() + plugin_name_lower + option_prefix + "-" + call, // Command line option (lowercase plugin + display name) + opt_idx, + type, + description, + default_value, + enum_values, + default_value, + }; + property_list.push_back(properties); + g_free((gpointer)call); + idx += prop_tokens; + opt_idx++; + } + g_free(plugin_name_lower); + return std::pair<std::string,bool>("",true); +} + +// Wherein we try to implement a sufficiently complete JSON Schema parser. +// Given a plugin config schema like the following: +//{ +// "$schema": "http://json-schema.org/draft-04/schema#", +// "$ref": "#/definitions/PluginConfig", +// "definitions": { +// "PluginConfig": { +// "properties": { +// "s3DownloadConcurrency": { +// "type": "integer", +// "title": "S3 download concurrency", +// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)", +// "default": 1 +// }, +// [ ... ] +// "aws": { +// "$schema": "http://json-schema.org/draft-04/schema#", +// "$ref": "#/definitions/PluginConfigAWS" +// } +// }, +// "additionalProperties": true, +// "type": "object" +// }, +// "PluginConfigAWS": { +// "properties": { +// "profile": { +// "type": "string", +// "title": "Shared AWS Config Profile", +// "description": "If non-empty", +// "default": "''" +// }, +// [ ... ] +// }, +// "additionalProperties": true, +// "type": "object" +// } +// } +//} +// find the plugin properties and parse them using parse_schema_properties. +// https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta + +static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin, plugin_configuration &plugin_config) +{ + ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON; + std::string schema_blob = plugin->get_init_schema(schema_type); + std::string config_name; + std::pair<std::string,bool> jv; + +#ifdef DEBUG_JSON_PARSING + ws_warning("raw schema: %s\n", schema_blob.c_str()); +#endif + + int ref_cnt = 0; + std::string::size_type ref_pos = 0; + while ((ref_pos = schema_blob.find("\"$ref\"", ref_pos)) != std::string::npos) { + ref_cnt++; + ref_pos += 5; + } + + // Dereference all of our $ref pairs. + // This is kind of janky, but makes subsequent parsing more simple. + for (int ref_idx = 0; ref_idx < ref_cnt; ref_idx++) { + jv = find_json_object_value(schema_blob, "$ref", JSMN_STRING); + if (!jv.second) { + break; + } + const std::string ref_pointer = jv.first; + jv = find_json_pointer_value(schema_blob, ref_pointer, JSMN_OBJECT); + if (!jv.second) { + ws_warning("Unable to find $ref %s.", ref_pointer.c_str()); + return false; + } + const std::string ref_body = jv.first.substr(1, jv.first.size() - 2); + + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(schema_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + ws_warning("Invalid schema."); + return false; + case JSMN_ERROR_PART: + { + ws_warning("Incomplete schema."); + return false; + } + default: + break; + } + +#ifdef DEBUG_JSON_PARSING + ws_warning("searching for $ref: %s\n", ref_pointer.c_str()); +#endif + tokens.resize(num_tokens); + json_parse(schema_blob.c_str(), tokens.data(), num_tokens); + std::vector<std::string> elements; + // First token is the full array. + for (int idx = 0; idx < num_tokens - 1; idx++) { + if (tokens[idx].type != JSMN_STRING && tokens[idx+1].type != JSMN_STRING) { + continue; + } + auto key_tok = &tokens[idx]; + auto val_tok = &tokens[idx+1]; + std::string key = schema_blob.substr(key_tok->start, key_tok->end - key_tok->start); + std::string value = schema_blob.substr(val_tok->start, val_tok->end - val_tok->start); + if (key != "$ref" || value != ref_pointer) { + continue; + } + try { +#ifdef DEBUG_JSON_PARSING + ws_warning("replacing: %s\n", schema_blob.substr(key_tok->start - 1, val_tok->end - key_tok->start + 2).c_str()); +#endif + schema_blob.replace(key_tok->start - 1, val_tok->end - key_tok->start + 2, ref_body); + } catch (std::out_of_range const&) { + ws_warning("Unknown reference %s.", key.c_str()); + return false; + } + } + } + +#ifdef DEBUG_JSON_PARSING + ws_warning("cooked schema: %s\n", schema_blob.c_str()); +#endif + + // XXX Should each sub-schema ref be in its own category? + jv = find_json_object_value(schema_blob, "properties", JSMN_OBJECT); + if (!jv.second) { + ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), schema_blob.c_str()); + return false; + } + int opt_idx = OPT_SCHEMA_PROPERTIES_START; + jv = get_schema_properties(jv.first, opt_idx, "", plugin->name(), plugin_config.property_list); + if (!jv.second) { + ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin->name().c_str(), jv.first.c_str()); + return false; + } + + return true; +} + +// For each loaded plugin, get its name and properties. +static bool get_source_plugins(sinsp &inspector, std::map<std::string, struct plugin_configuration> &plugin_configs) { + const auto plugin_manager = inspector.get_plugin_manager(); + + // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail. + try { + for (auto &plugin : plugin_manager->plugins()) { + if (plugin->caps() & CAP_SOURCING) { + plugin_configuration plugin_config = {}; + if (!get_plugin_config_schema(plugin, plugin_config)) { + return false; + } + plugin_configs[plugin->name()] = plugin_config; + } + } + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + return false; + } + return true; +} + +// Build our command line options based on our source plugins. +static const std::vector<ws_option> get_longopts(const std::map<std::string, struct plugin_configuration> &plugin_configs) { + std::vector<ws_option> longopts; + struct ws_option base_longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "plugin-api-version", ws_no_argument, NULL, OPT_PLUGIN_API_VERSION}, + { "plugin-source", ws_required_argument, NULL, OPT_PLUGIN_SOURCE }, + { 0, 0, 0, 0} + }; + int idx; + for (idx = 0; base_longopts[idx].name; idx++) { + longopts.push_back(base_longopts[idx]); + } + for (const auto &it : plugin_configs) { + const struct plugin_configuration plugin_configs = it.second; + for (const auto &prop : plugin_configs.property_list) { + ws_option option = { g_strdup(prop.option.c_str()), ws_required_argument, NULL, prop.option_index }; + longopts.push_back(option); + } + } + longopts.push_back(base_longopts[idx]); + return longopts; +} + +// Show the configuration for a given plugin/interface. +static int show_config(const std::string &interface, const struct plugin_configuration &plugin_config) +{ + unsigned arg_num = 0; +// char* plugin_filter; + + if (interface.empty()) { + ws_warning("ERROR: No interface specified."); + return EXIT_FAILURE; + } + + printf( + "arg {number=%u}" + "{call=--plugin-source}" + "{display=Log data URL}" + "{type=string}" + "{tooltip=The plugin data source. This is usually a URL.}" + "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS "}" + "{required=true}" + "{group=Capture}\n", + arg_num++); +// if (plugin_filter) +// printf("{default=%s}", plugin_filter); + for (const auto &properties : plugin_config.property_list) { + if (properties.option_index < OPT_SCHEMA_PROPERTIES_START) { + continue; + } + std::string default_value; + if (!properties.default_value.empty()) { + default_value = "{default=" + properties.default_value + "}"; + } + if (properties.option == "cloudtrail-aws-profile") { + print_cloudtrail_aws_profile_config(arg_num, properties.display.c_str(), properties.description.c_str()); + } else if (properties.option == "cloudtrail-aws-region") { + print_cloudtrail_aws_region_config(arg_num, properties.display.c_str(), properties.description.c_str()); + } else { + printf( + "arg {number=%d}" + "{call=--%s}" + "{display=%s}" + "{type=%s}" + "%s" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str()); + if (properties.enum_values.size() > 0) { + for (const auto &enum_val : properties.enum_values) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "%s" + "\n", + arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : ""); + } + } + } + arg_num++; + } + extcap_config_debug(&arg_num); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + char* configuration_init_error; + int result; + int option_idx = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + std::map<std::string, struct plugin_configuration> plugin_configs; + char* help_url; + char* help_header = NULL; + sinsp inspector; + std::string plugin_source; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("falcodump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + configuration_init_error = configuration_init(argv[0], "Logray"); + if (configuration_init_error != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + configuration_init_error); + g_free(configuration_init_error); + } + + load_plugins(inspector); + + if (!get_source_plugins(inspector, plugin_configs)) { + goto end; + } + + for (auto iter = plugin_configs.begin(); iter != plugin_configs.end(); ++iter) { + // We don't have a Falco source plugins DLT, so use USER0 (147). + // Additional info available via plugin->description() and plugin->event_source(). + extcap_base_register_interface(extcap_conf, iter->first.c_str(), "Falco plugin", 147, "USER0"); + } + + help_url = data_file_url("falcodump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], FALCODUMP_VERSION_MAJOR, FALCODUMP_VERSION_MINOR, + FALCODUMP_VERSION_RELEASE, help_url); + g_free(help_url); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url>\n", + argv[0], + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER, + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER, + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--plugin-api-version", "print the Falco plugin API version"); + extcap_help_add_option(extcap_conf, "--plugin-source", "plugin source URL"); + + for (const auto &it : plugin_configs) { + const struct plugin_configuration plugin_configs = it.second; + for (const auto &prop : plugin_configs.property_list) { + if (prop.option_index < OPT_SCHEMA_PROPERTIES_START) { + continue; + } + extcap_help_add_option(extcap_conf, g_strdup_printf("--%s", prop.option.c_str()), g_strdup(prop.description.c_str())); + } + } + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + static const std::vector<ws_option> longopts = get_longopts(plugin_configs); + while ((result = ws_getopt_long(argc, argv, ":", longopts.data(), &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_PLUGIN_API_VERSION: + fprintf(stdout, "Falco plugin API version %s\n", inspector.get_plugin_api_version()); + ret = EXIT_SUCCESS; + goto end; + + case OPT_PLUGIN_SOURCE: + plugin_source = ws_optarg; + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (result >= OPT_SCHEMA_PROPERTIES_START) { + bool found = false; + for (auto &it : plugin_configs) { + struct plugin_configuration *plugin_config = &it.second; + for (auto &prop : plugin_config->property_list) { + if (prop.option_index == result) { + prop.current_value = ws_optarg; + found = true; + break; + } + } + if (found) { + break; + } + } + if (!found) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } else if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (plugin_configs.size() < 1) { + ws_warning("No source plugins found."); + goto end; + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = show_config(extcap_conf->interface, plugin_configs.at(extcap_conf->interface)); + goto end; + } + + if (extcap_conf->capture) { + if (plugin_source.empty()) { + ws_warning("Missing or invalid parameter: --plugin-source"); + goto end; + } + + std::shared_ptr<sinsp_plugin> plugin_interface; + const auto plugin_manager = inspector.get_plugin_manager(); + for (auto &plugin : plugin_manager->plugins()) { + if (plugin->name() == extcap_conf->interface) { + plugin_interface = plugin; + } + } + + if (plugin_interface == nullptr) { + ws_warning("Unable to find interface %s", extcap_conf->interface); + goto end; + } + + sinsp_dumper dumper; +#ifdef DEBUG_SINSP + inspector.set_debug_mode(true); + inspector.set_log_stderr(); +#endif + try { + std::string init_err; + plugin_interface->init(plugin_configs[extcap_conf->interface].json_config().c_str(), init_err); + if (!init_err.empty()) { + ws_warning("%s", init_err.c_str()); + goto end; + } + inspector.open_plugin(extcap_conf->interface, plugin_source); + // scap_dump_open handles "-" + dumper.open(&inspector, extcap_conf->fifo, false); + } catch (sinsp_exception &e) { + dumper.close(); + ws_warning("%s", e.what()); + goto end; + } + sinsp_evt *evt; + ws_noisy("Starting capture loop."); + while (!extcap_end_application) { + try { + int32_t res = inspector.next(&evt); + switch (res) { + case SCAP_TIMEOUT: + case SCAP_FILTERED_EVENT: + break; + case SCAP_SUCCESS: + dumper.dump(evt); + dumper.flush(); + break; + default: + ws_noisy("Inspector exited with %d", res); + extcap_end_application = true; + break; + } + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + goto end; + } + } + ws_noisy("Closing dumper and inspector."); + dumper.close(); + inspector.close(); + ret = EXIT_SUCCESS; + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + } + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + return ret; +} |