/* 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 * 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 #include #define WS_LOG_DOMAIN "falcodump" #include #include #include #include #include #include #include #include #define FALCODUMP_VERSION_MAJOR "1" #define FALCODUMP_VERSION_MINOR "0" #define FALCODUMP_VERSION_RELEASE "0" #define FALCODUMP_PLUGIN_PLACEHOLDER "" // 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::vectorenum_values; std::string current_value; }; struct plugin_configuration { std::vector 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::setprofiles; // 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) { // aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text std::set 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", "ca-central-1", "eu-central-1", "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_path = g_build_filename(get_plugins_dir_with_version(), "falco", NULL); 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); inspector.register_plugin(libname); g_free(libname); } ws_dir_close(dir); } g_free(plugin_path); plugin_path = g_build_filename(get_plugins_pers_dir_with_version(), "falco", NULL); 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); inspector.register_plugin(libname); 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 find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) { std::vector tokens; int num_tokens = json_parse(object_blob.c_str(), NULL, 0); switch (num_tokens) { case JSMN_ERROR_INVAL: return std::pair("invalid", false); case JSMN_ERROR_PART: return std::pair("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(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("", 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 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 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("", 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(blob, true); } // Convert a JSON array to a string vector. // Returns (vector, true) on success, or (err_str, false) on failure. const std::pair,bool> get_json_array(const std::string &array_blob) { std::vector tokens; int num_tokens = json_parse(array_blob.c_str(), NULL, 0); switch (num_tokens) { case JSMN_ERROR_INVAL: return std::pair,bool>(std::vector{"invalid"}, false); case JSMN_ERROR_PART: { return std::pair,bool>(std::vector{"incomplete"}, false); } default: break; } tokens.resize(num_tokens); json_parse(array_blob.c_str(), tokens.data(), num_tokens); std::vector 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,bool>(elements, true); } // Given a JSON blob containing a schema properties object, add each property to the // given plugin config. const std::pair get_schema_properties(const std::string props_blob, int &opt_idx, const std::string option_prefix, const std::string plugin_name, std::vector &property_list) { std::vector tokens; int num_tokens = json_parse(props_blob.c_str(), NULL, 0); switch (num_tokens) { case JSMN_ERROR_INVAL: return std::pair("invalid", false); case JSMN_ERROR_PART: return std::pair("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("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 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("invalid property", false); case JSMN_ERROR_PART: return std::pair("incomplete property", false); default: break; } #ifdef DEBUG_JSON_PARSING ws_warning("property %s [%d]\n", name.c_str(), prop_tokens); #endif std::pair 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("missing type", false); } std::string type = jv.first; jv = find_json_object_value(property_blob, "description", JSMN_STRING); if (!jv.second) { return std::pair("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,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("",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 &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 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 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 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 &plugin_configs) { const sinsp_plugin_manager *plugin_manager = inspector.get_plugin_manager(); // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail. for (const 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; } } return true; } // Build our command line options based on our source plugins. static const std::vector get_longopts(const std::map &plugin_configs) { std::vector 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=Plugin source}" "{type=string}" "{tooltip=The plugin data source. This us 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 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= --capture --plugin-source=\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 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 plugin_interface; const sinsp_plugin_manager *pm = inspector.get_plugin_manager(); for (auto &plugin : pm->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 = (&inspector); #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(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; }