dcerpc-netlogon: all netr_LogonControl[2[Ex]] calls have the same reply values
[wireshark-sm.git] / extcap / falcodump.cpp
blob934e6ea27e2c4a257128de7b989ee1aa7ddd015e
1 /* falcodump.cpp
2 * Falcodump is an extcap tool which dumps logs using Falco source plugins.
3 * https://falco.org/docs/plugins/
5 * Adapted from sdjournal.
6 * Copyright 2022, Gerald Combs and Dario Lombardo
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * SPDX-License-Identifier: GPL-2.0-or-later
16 * To do:
17 * - Pull plugin source description from list_open_params?
18 * - Add filtering.
19 * - Add an option to dump plugin fields.
20 * - Add options for credentials.
21 * - Let the user create preconfigured interfaces.
22 * - Exit more cleanly (see MRs 2063 and 7673).
23 * - Better config schema default value parsing? Would likely require a schema change.
24 * - Make sure all types are handled in parse_schema_properties.
25 * - Handle "required" config schema annotation (Okta).
28 #include "config.h"
30 #include <libsinsp/sinsp.h>
31 #include <plugin_manager.h>
33 #include <scap_engines.h>
35 #define WS_LOG_DOMAIN "falcodump"
37 #include <extcap/extcap-base.h>
39 #include <wsutil/file_util.h>
40 #include <wsutil/filesystem.h>
41 #include <wsutil/json_dumper.h>
42 #include <wsutil/privileges.h>
43 #include <wsutil/utf8_entities.h>
44 #include <wsutil/wsjson.h>
45 #include <wsutil/wslog.h>
47 #define FALCODUMP_VERSION_MAJOR "1"
48 #define FALCODUMP_VERSION_MINOR "0"
49 #define FALCODUMP_VERSION_RELEASE "0"
51 #define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
53 #define SINSP_CHECK_VERSION(major, minor, micro) \
54 (((SINSP_VERSION_MAJOR << 16) + (SINSP_VERSION_MINOR << 8) + SINSP_VERSION_MICRO) >= ((major << 16) + (minor << 8) + micro))
56 // We load our plugins and fetch their configs before we set our log level.
57 // #define DEBUG_JSON_PARSING
58 // #define DEBUG_SINSP
60 enum {
61 EXTCAP_BASE_OPTIONS_ENUM,
62 OPT_HELP,
63 OPT_VERSION,
64 OPT_PLUGIN_API_VERSION,
65 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
66 OPT_INCLUDE_CAPTURE_PROCESSES,
67 OPT_INCLUDE_SWITCH_CALLS,
68 #endif
69 OPT_PLUGIN_SOURCE,
70 OPT_SCHEMA_PROPERTIES_START,
73 // "s3DownloadConcurrency": {
74 // "type": "integer",
75 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)"
76 // },
77 struct config_properties {
78 std::string name;
79 std::string display; // "title" property || name
80 std::string option; // Command line option including leading dashes (lowercase display name)
81 int option_index; // Starts from OPT_SCHEMA_PROPERTIES_START
82 std::string type; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES"
83 std::string description;
84 std::string default_value;
85 std::vector<std::string>enum_values;
86 std::string current_value;
89 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
90 struct syscall_configuration {
91 bool include_capture_processes;
92 bool include_switch_calls;
94 #endif
96 struct plugin_configuration {
97 std::vector<struct config_properties> property_list;
99 std::string json_config() {
100 json_dumper dumper = {};
101 dumper.output_string = g_string_new(NULL);
103 json_dumper_begin_object(&dumper);
105 for (const auto &prop : property_list) {
106 if (prop.type == "BEGIN_CONFIG_PROPERTIES") {
107 json_dumper_set_member_name(&dumper, prop.name.c_str());
108 json_dumper_begin_object(&dumper);
109 continue;
110 } else if (prop.type == "END_CONFIG_PROPERTIES") {
111 json_dumper_end_object(&dumper);
112 continue;
115 if (prop.current_value == prop.default_value) {
116 continue;
119 json_dumper_set_member_name(&dumper, prop.name.c_str());
120 if (prop.type == "string" || prop.type == "selector") {
121 json_dumper_value_string(&dumper, prop.current_value.c_str());
122 } else {
123 json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str());
127 json_dumper_end_object(&dumper);
128 json_dumper_finish(&dumper);
129 std::string config_blob = dumper.output_string->str;
130 ws_debug("configuration: %s", dumper.output_string->str);
131 g_string_free(dumper.output_string, TRUE);
132 return config_blob;
136 // Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c.
137 // XXX Use g_file_get_contents or GMappedFile instead?
138 static int
139 fgetline(char *buf, int size, FILE *fp)
141 if (fgets(buf, size, fp)) {
142 int len = (int)strcspn(buf, "\r\n");
143 buf[len] = '\0';
144 return len;
146 return -1;
149 static const size_t MAX_AWS_LINELEN = 2048;
150 void print_cloudtrail_aws_profile_config(int arg_num, const char *display, const char *description) {
151 char buf[MAX_AWS_LINELEN];
152 char profile_name[MAX_AWS_LINELEN];
153 FILE *aws_fp;
154 std::set<std::string>profiles;
156 // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
157 char *cred_path = g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE"));
158 if (cred_path == NULL) {
159 cred_path = g_build_filename(g_get_home_dir(), ".aws", "credentials", (char *)NULL);
162 aws_fp = ws_fopen(cred_path, "r");
163 g_free(cred_path);
165 if (aws_fp != NULL) {
166 while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
167 if (sscanf(buf, "[%2047[^]]s]", profile_name) == 1) {
168 if (strcmp(profile_name, "default") == 0) {
169 continue;
171 profiles.insert(profile_name);
174 fclose(aws_fp);
177 char *conf_path = g_strdup(g_getenv("AWS_CONFIG_FILE"));
178 if (conf_path == NULL) {
179 conf_path = g_build_filename(g_get_home_dir(), ".aws", "config", (char *)NULL);
182 aws_fp = ws_fopen(conf_path, "r");
183 g_free(conf_path);
185 if (aws_fp != NULL) {
186 while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
187 if (sscanf(buf, "[profile %2047[^]]s]", profile_name) == 1) {
188 if (strcmp(profile_name, "default") == 0) {
189 continue;
191 profiles.insert(profile_name);
194 fclose(aws_fp);
197 const char *aws_profile_env = g_getenv("AWS_PROFILE");
198 for (auto &profile : profiles) {
199 if (aws_profile_env && profile == aws_profile_env) {
200 aws_profile_env = nullptr;
203 if (aws_profile_env) {
204 profiles.insert(aws_profile_env);
207 printf(
208 "arg {number=%d}"
209 "{call=--cloudtrail-aws-profile}"
210 "{display=%s}"
211 "{type=editselector}"
212 "{tooltip=%s}"
213 "{group=Capture}"
214 "\n",
215 arg_num, display, description);
216 printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num);
217 for (auto &profile : profiles) {
218 printf(
219 "value {arg=%d}"
220 "{value=%s}"
221 "{display=%s}"
222 "\n",
223 arg_num, profile.c_str(), profile.c_str());
227 void print_cloudtrail_aws_region_config(int arg_num, const char *display, const char *description) {
228 // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort
229 std::set<std::string> regions = {
230 "af-south-1",
231 "ap-east-1",
232 "ap-northeast-1",
233 "ap-northeast-2",
234 "ap-northeast-3",
235 "ap-south-1",
236 "ap-south-2",
237 "ap-southeast-1",
238 "ap-southeast-2",
239 "ap-southeast-3",
240 "ap-southeast-4",
241 "ca-central-1",
242 "ca-west-1",
243 "eu-central-1",
244 "eu-central-2",
245 "eu-north-1",
246 "eu-south-1",
247 "eu-south-2",
248 "eu-west-1",
249 "eu-west-2",
250 "eu-west-3",
251 "il-central-1",
252 "me-central-1",
253 "me-south-1",
254 "sa-east-1",
255 "us-east-1",
256 "us-east-2",
257 "us-west-1",
258 "us-west-2",
261 const char *aws_region_env = g_getenv("AWS_REGION");
262 for (auto &region : regions) {
263 if (aws_region_env && region == aws_region_env) {
264 aws_region_env = nullptr;
267 if (aws_region_env) {
268 regions.insert(aws_region_env);
271 printf(
272 "arg {number=%d}"
273 "{call=--cloudtrail-aws-region}"
274 "{display=%s}"
275 "{type=editselector}"
276 "{tooltip=%s}"
277 "{group=Capture}"
278 "\n",
279 arg_num, display, description);
280 printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num);
282 for (auto &region : regions) {
283 printf(
284 "value {arg=%d}"
285 "{value=%s}"
286 "{display=%s}"
287 "\n",
288 arg_num, region.c_str(), region.c_str());
293 // Load our plugins. This should match the behavior of the Falco Bridge dissector.
294 static void load_plugins(sinsp &inspector) {
295 WS_DIR *dir;
296 WS_DIRENT *file;
297 char *plugin_paths[] = {
298 // XXX Falco plugins should probably be installed in a path that reflects
299 // the Falco version or its plugin API version.
300 g_build_filename(get_plugins_dir(), "falco", NULL),
301 g_build_filename(get_plugins_pers_dir(), "falco", NULL)
304 for (size_t idx = 0; idx < 2; idx++) {
305 char *plugin_path = plugin_paths[idx];
306 if ((dir = ws_dir_open(plugin_path, 0, NULL)) != NULL) {
307 while ((file = ws_dir_read_name(dir)) != NULL) {
308 char *libname = g_build_filename(plugin_path, ws_dir_get_name(file), NULL);
309 try {
310 auto plugin = inspector.register_plugin(libname);
311 ws_debug("Registered plugin %s via %s", plugin->name().c_str(), libname);
312 } catch (sinsp_exception &e) {
313 ws_warning("%s", e.what());
315 g_free(libname);
317 ws_dir_close(dir);
319 g_free(plugin_path);
323 // Given a key, try to find its value in a JSON object.
324 // Returns (value, true) on success, or (err_str, false) on failure.
325 const std::pair<const std::string,bool> find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) {
326 std::vector<jsmntok_t> tokens;
327 int num_tokens = json_parse(object_blob.c_str(), NULL, 0);
329 switch (num_tokens) {
330 case JSMN_ERROR_INVAL:
331 return std::pair<std::string,bool>("invalid", false);
332 case JSMN_ERROR_PART:
333 return std::pair<std::string,bool>("incomplete", false);
334 default:
335 break;
338 tokens.resize(num_tokens);
339 json_parse(object_blob.c_str(), tokens.data(), num_tokens);
340 for (int idx = 0; idx < num_tokens - 1; idx++) {
341 jsmntok_t &k_tok = tokens[idx];
342 jsmntok_t &v_tok = tokens[idx+1];
343 std::string cur_key = object_blob.substr(k_tok.start, k_tok.end - k_tok.start);
344 if (cur_key == key && k_tok.type == JSMN_STRING && v_tok.type == value_type) {
345 std::string value = object_blob.substr(v_tok.start, v_tok.end - v_tok.start);
346 return std::pair<std::string,bool>(value, true);
348 #ifdef DEBUG_JSON_PARSING
349 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);
350 #endif
352 return std::pair<const std::string,bool>("", false);
355 // Given an RFC 6901-style JSON pointer, try to find its value in a JSON object.
356 // Returns (value, true) on success, or (err_str, false) on failure.
357 const std::pair<const std::string,bool> find_json_pointer_value(const std::string &object_blob, const std::string &pointer, int value_type) {
358 std::string blob = object_blob;
359 std::istringstream ob_stream(pointer);
360 std::string token;
361 while (std::getline(ob_stream, token, '/')) {
362 if (token == "#" || token.empty()) {
363 continue;
365 std::pair<std::string,bool> jv = find_json_object_value(blob, token, value_type);
366 if (!jv.second) {
367 #ifdef DEBUG_JSON_PARSING
368 ws_warning("JSON pointer %s not found at %s", blob.c_str(), token.c_str());
369 #endif
370 return std::pair<std::string,bool>("", false);
372 blob = jv.first;
374 #ifdef DEBUG_JSON_PARSING
375 ws_warning("JSON pointer %s = %s ... %s", pointer.c_str(), blob.substr(0, 10).c_str(), blob.substr(blob.size() - 10, 10).c_str());
376 #endif
377 return std::pair<const std::string,bool>(blob, true);
380 // Convert a JSON array to a string vector.
381 // Returns (vector, true) on success, or (err_str, false) on failure.
382 const std::pair<std::vector<std::string>,bool> get_json_array(const std::string &array_blob) {
383 std::vector<jsmntok_t> tokens;
384 int num_tokens = json_parse(array_blob.c_str(), NULL, 0);
386 switch (num_tokens) {
387 case JSMN_ERROR_INVAL:
388 return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false);
389 case JSMN_ERROR_PART:
391 return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false);
393 default:
394 break;
397 tokens.resize(num_tokens);
398 json_parse(array_blob.c_str(), tokens.data(), num_tokens);
399 std::vector<std::string> elements;
400 // First token is the full array.
401 for (int idx = 1; idx < num_tokens; idx++) {
402 jsmntok_t &el_tok = tokens[idx];
403 elements.push_back(array_blob.substr(el_tok.start, el_tok.end - el_tok.start));
405 #ifdef DEBUG_JSON_PARSING
406 ws_warning("%s: %d", array_blob.c_str(), (int)elements.size());
407 #endif
408 return std::pair<std::vector<std::string>,bool>(elements, true);
411 // Given a JSON blob containing a schema properties object, add each property to the
412 // given plugin config.
413 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) {
414 std::vector<jsmntok_t> tokens;
415 int num_tokens = json_parse(props_blob.c_str(), NULL, 0);
417 switch (num_tokens) {
418 case JSMN_ERROR_INVAL:
419 return std::pair<std::string,bool>("invalid", false);
420 case JSMN_ERROR_PART:
421 return std::pair<std::string,bool>("incomplete", false);
422 default:
423 break;
426 tokens.resize(num_tokens);
427 json_parse(props_blob.c_str(), tokens.data(), num_tokens);
429 if (tokens[0].type != JSMN_OBJECT) {
430 return std::pair<std::string,bool>("malformed", false);
433 char *plugin_name_lower = g_ascii_strdown(plugin_name.c_str(), -1);
435 // We have an object blob which contains a list of properties and property blobs, e.g.
436 // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } }
437 // Skip over the outer { ... } and process the contents as pairs.
438 for (int idx = 1; idx < num_tokens - 2; idx++) {
439 jsmntok_t &n_tok = tokens[idx];
440 jsmntok_t &p_tok = tokens[idx+1];
442 std::string name = props_blob.substr(n_tok.start, n_tok.end - n_tok.start);
443 std::string display = name;
444 std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start);
445 std::vector<std::string> enum_values;
447 // XXX Check for errors?
448 int prop_tokens = json_parse(property_blob.c_str(), NULL, 0);
449 switch (prop_tokens) {
450 case JSMN_ERROR_INVAL:
451 return std::pair<std::string,bool>("invalid property", false);
452 case JSMN_ERROR_PART:
453 return std::pair<std::string,bool>("incomplete property", false);
454 default:
455 break;
457 #ifdef DEBUG_JSON_PARSING
458 ws_warning("property %s [%d]\n", name.c_str(), prop_tokens);
459 #endif
461 std::pair<std::string,bool> jv = find_json_object_value(property_blob, "properties", JSMN_OBJECT);
462 if (jv.second) {
463 config_properties properties = {
464 name,
465 display,
468 "BEGIN_CONFIG_PROPERTIES",
471 enum_values,
474 property_list.push_back(properties);
475 get_schema_properties(jv.first, opt_idx, option_prefix + "-" + name, plugin_name, property_list);
476 properties = {
477 name,
478 display,
481 "END_CONFIG_PROPERTIES",
484 enum_values,
487 property_list.push_back(properties);
488 idx += prop_tokens;
489 continue;
492 jv = find_json_object_value(property_blob, "title", JSMN_STRING);
493 if (jv.second) {
494 display = jv.first;
496 // else split+capitalize "name"?
498 jv = find_json_object_value(property_blob, "type", JSMN_STRING);
499 if (!jv.second) {
500 return std::pair<std::string,bool>("missing type", false);
502 std::string type = jv.first;
503 jv = find_json_object_value(property_blob, "description", JSMN_STRING);
504 if (!jv.second) {
505 return std::pair<std::string,bool>("missing description", false);
507 std::string description = jv.first;
508 std::string default_value;
509 jv = find_json_object_value(property_blob, "default", JSMN_STRING);
510 if (jv.second) {
511 default_value = jv.first;
512 } else {
513 std::string default_pfx = "(Default: ";
514 size_t pfx_pos = description.rfind(default_pfx);
515 if (pfx_pos != std::string::npos) {
516 default_value = description.substr(pfx_pos + default_pfx.size());
517 pfx_pos = default_value.rfind(")");
518 default_value = default_value.erase(pfx_pos);
521 jv = find_json_object_value(property_blob, "enum", JSMN_ARRAY);
522 if (jv.second) {
523 const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first);
524 if (ja.second) {
525 enum_values = ja.first;
526 type = "selector";
529 #ifdef DEBUG_JSON_PARSING
530 ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size());
531 #endif
532 const char *call = g_ascii_strdown(name.c_str(), -1);
533 config_properties properties = {
534 name,
535 display,
536 std::string() + plugin_name_lower + option_prefix + "-" + call, // Command line option (lowercase plugin + display name)
537 opt_idx,
538 type,
539 description,
540 default_value,
541 enum_values,
542 default_value,
544 property_list.push_back(properties);
545 g_free((void *)call);
546 idx += prop_tokens;
547 opt_idx++;
549 g_free(plugin_name_lower);
550 return std::pair<std::string,bool>("",true);
553 // Wherein we try to implement a sufficiently complete JSON Schema parser.
554 // Given a plugin config schema like the following:
556 // "$schema": "http://json-schema.org/draft-04/schema#",
557 // "$ref": "#/definitions/PluginConfig",
558 // "definitions": {
559 // "PluginConfig": {
560 // "properties": {
561 // "s3DownloadConcurrency": {
562 // "type": "integer",
563 // "title": "S3 download concurrency",
564 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)",
565 // "default": 1
566 // },
567 // [ ... ]
568 // "aws": {
569 // "$schema": "http://json-schema.org/draft-04/schema#",
570 // "$ref": "#/definitions/PluginConfigAWS"
571 // }
572 // },
573 // "additionalProperties": true,
574 // "type": "object"
575 // },
576 // "PluginConfigAWS": {
577 // "properties": {
578 // "profile": {
579 // "type": "string",
580 // "title": "Shared AWS Config Profile",
581 // "description": "If non-empty",
582 // "default": "''"
583 // },
584 // [ ... ]
585 // },
586 // "additionalProperties": true,
587 // "type": "object"
588 // }
589 // }
591 // find the plugin properties and parse them using parse_schema_properties.
592 // https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
594 static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin, plugin_configuration &plugin_config)
596 ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON;
597 std::string schema_blob = plugin->get_init_schema(schema_type);
598 std::string config_name;
599 std::pair<std::string,bool> jv;
601 #ifdef DEBUG_JSON_PARSING
602 ws_warning("raw schema: %s\n", schema_blob.c_str());
603 #endif
605 int ref_cnt = 0;
606 std::string::size_type ref_pos = 0;
607 while ((ref_pos = schema_blob.find("\"$ref\"", ref_pos)) != std::string::npos) {
608 ref_cnt++;
609 ref_pos += 5;
612 // Dereference all of our $ref pairs.
613 // This is kind of janky, but makes subsequent parsing more simple.
614 for (int ref_idx = 0; ref_idx < ref_cnt; ref_idx++) {
615 jv = find_json_object_value(schema_blob, "$ref", JSMN_STRING);
616 if (!jv.second) {
617 break;
619 const std::string ref_pointer = jv.first;
620 jv = find_json_pointer_value(schema_blob, ref_pointer, JSMN_OBJECT);
621 if (!jv.second) {
622 ws_warning("Unable to find $ref %s.", ref_pointer.c_str());
623 return false;
625 const std::string ref_body = jv.first.substr(1, jv.first.size() - 2);
627 std::vector<jsmntok_t> tokens;
628 int num_tokens = json_parse(schema_blob.c_str(), NULL, 0);
630 switch (num_tokens) {
631 case JSMN_ERROR_INVAL:
632 ws_warning("Invalid schema.");
633 return false;
634 case JSMN_ERROR_PART:
636 ws_warning("Incomplete schema.");
637 return false;
639 default:
640 break;
643 #ifdef DEBUG_JSON_PARSING
644 ws_warning("searching for $ref: %s\n", ref_pointer.c_str());
645 #endif
646 tokens.resize(num_tokens);
647 json_parse(schema_blob.c_str(), tokens.data(), num_tokens);
648 std::vector<std::string> elements;
649 // First token is the full array.
650 for (int idx = 0; idx < num_tokens - 1; idx++) {
651 if (tokens[idx].type != JSMN_STRING && tokens[idx+1].type != JSMN_STRING) {
652 continue;
654 auto key_tok = &tokens[idx];
655 auto val_tok = &tokens[idx+1];
656 std::string key = schema_blob.substr(key_tok->start, key_tok->end - key_tok->start);
657 std::string value = schema_blob.substr(val_tok->start, val_tok->end - val_tok->start);
658 if (key != "$ref" || value != ref_pointer) {
659 continue;
661 try {
662 #ifdef DEBUG_JSON_PARSING
663 ws_warning("replacing: %s\n", schema_blob.substr(key_tok->start - 1, val_tok->end - key_tok->start + 2).c_str());
664 #endif
665 schema_blob.replace(key_tok->start - 1, val_tok->end - key_tok->start + 2, ref_body);
666 } catch (std::out_of_range const&) {
667 ws_warning("Unknown reference %s.", key.c_str());
668 return false;
673 #ifdef DEBUG_JSON_PARSING
674 ws_warning("cooked schema: %s\n", schema_blob.c_str());
675 #endif
677 // XXX Should each sub-schema ref be in its own category?
678 jv = find_json_object_value(schema_blob, "properties", JSMN_OBJECT);
679 if (!jv.second) {
680 ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), schema_blob.c_str());
681 return false;
683 int opt_idx = OPT_SCHEMA_PROPERTIES_START;
684 jv = get_schema_properties(jv.first, opt_idx, "", plugin->name(), plugin_config.property_list);
685 if (!jv.second) {
686 ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin->name().c_str(), jv.first.c_str());
687 return false;
690 return true;
693 // For each loaded plugin, get its name and properties.
694 static bool get_source_plugins(sinsp &inspector, std::map<std::string, struct plugin_configuration> &plugin_configs) {
695 const auto plugin_manager = inspector.get_plugin_manager();
697 // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail.
698 try {
699 for (auto &plugin : plugin_manager->plugins()) {
700 if (plugin->caps() & CAP_SOURCING) {
701 plugin_configuration plugin_config = {};
702 if (!get_plugin_config_schema(plugin, plugin_config)) {
703 return false;
705 plugin_configs[plugin->name()] = plugin_config;
708 } catch (sinsp_exception &e) {
709 ws_warning("%s", e.what());
710 return false;
712 return true;
715 // Build our command line options based on our source plugins.
716 static const std::vector<ws_option> get_longopts(const std::map<std::string, struct plugin_configuration> &plugin_configs) {
717 std::vector<ws_option> longopts;
718 struct ws_option base_longopts[] = {
719 EXTCAP_BASE_OPTIONS,
720 { "help", ws_no_argument, NULL, OPT_HELP},
721 { "version", ws_no_argument, NULL, OPT_VERSION},
722 { "plugin-api-version", ws_no_argument, NULL, OPT_PLUGIN_API_VERSION},
723 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
724 { "include-capture-processes", ws_required_argument, NULL, OPT_INCLUDE_CAPTURE_PROCESSES },
725 { "include-switch-calls", ws_required_argument, NULL, OPT_INCLUDE_SWITCH_CALLS },
726 #endif
727 { "plugin-source", ws_required_argument, NULL, OPT_PLUGIN_SOURCE },
728 { 0, 0, 0, 0}
730 int idx;
731 for (idx = 0; base_longopts[idx].name; idx++) {
732 longopts.push_back(base_longopts[idx]);
734 for (const auto &it : plugin_configs) {
735 const struct plugin_configuration plugin_config = it.second;
736 for (const auto &prop : plugin_config.property_list) {
737 ws_option option = { g_strdup(prop.option.c_str()), ws_required_argument, NULL, prop.option_index };
738 longopts.push_back(option);
741 longopts.push_back(base_longopts[idx]);
742 return longopts;
745 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
746 static bool
747 get_bool_value(const char *bool_str)
749 if (!bool_str) {
750 return false;
752 switch (bool_str[0]) {
753 case 'f':
754 case 'F':
755 case '0':
756 return false;
757 default:
758 return true;
762 // Show the configuration for a given plugin/interface.
763 static int show_syscall_config(void)
765 printf(
766 "arg {number=0}"
767 "{call=--include-capture-processes}"
768 "{display=Include capture processes}"
769 "{type=boolean}"
770 "{tooltip=Include system calls made by any capture processes (falcodump, dumpcap, and Stratoshark)}"
771 "{required=false}"
772 "{group=Capture}\n"
774 "arg {number=1}"
775 "{call=--include-switch-calls}"
776 "{display=Include \"switch\" calls}"
777 "{type=boolean}"
778 "{tooltip=Include \"switch\" system calls}"
779 "{required=false}"
780 "{group=Capture}\n"
782 "value {arg=0}{value=1}\n"
785 return EXIT_SUCCESS;
788 #include <fstream>
789 #include <iostream>
790 static const std::string syscall_capture_filter(const struct syscall_configuration &syscall_config, const char *capture_filter)
792 if (syscall_config.include_capture_processes && syscall_config.include_switch_calls) {
793 if (capture_filter) {
794 return std::string(capture_filter);
795 } else {
796 return std::string();
800 std::string filter;
802 if (capture_filter) {
803 filter = "(" + std::string(capture_filter) + ") and (";
806 if (!syscall_config.include_capture_processes) {
807 // We want to exclude Stratoshark and any of its children, including
808 // this one (falcodump).
810 std::string pid, comm, _s, ppid;
812 // Exclude this process only at a minimum.
813 std::ifstream stat_stream("/proc/self/stat");
814 stat_stream >> pid >> comm >> _s >> ppid;
815 std::string process_filter = "proc.pid != " + pid;
816 if (comm != "(falcodump)") {
817 ws_warning("Our process is named %s, not falcodump", comm.c_str());
819 stat_stream.close();
821 // If our parent is Stratoshark, exclude it and its direct children.
822 std::ifstream pstat_stream("/proc/" + ppid + "/stat");
823 pstat_stream >> _s >> comm;
824 if (comm == "(stratoshark)") {
825 // XXX Use proc.apid instead?
826 process_filter = "proc.pid != " + ppid + " and proc.ppid != " + ppid;
828 pstat_stream.close();
830 filter += process_filter;
833 if (!syscall_config.include_switch_calls) {
834 if (!syscall_config.include_capture_processes) {
835 filter += " and ";
837 filter += "evt.type != switch";
840 if (capture_filter) {
841 filter += ")";
844 return filter;
846 #endif // HAS_ENGINE_KMOD || HAS_ENGINE_MODERN_BPF
848 // Show the configuration for a given plugin/interface.
849 static int show_plugin_config(const std::string &interface, const struct plugin_configuration &plugin_config)
851 unsigned arg_num = 0;
852 // char* plugin_filter;
854 if (interface.empty()) {
855 ws_warning("ERROR: No interface specified.");
856 return EXIT_FAILURE;
859 printf(
860 "arg {number=%u}"
861 "{call=--plugin-source}"
862 "{display=Log data URL}"
863 "{type=string}"
864 "{tooltip=The plugin data source. This is usually a URL.}"
865 "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS "}"
866 "{required=true}"
867 "{group=Capture}\n",
868 arg_num++);
869 // if (plugin_filter)
870 // printf("{default=%s}", plugin_filter);
871 for (const auto &properties : plugin_config.property_list) {
872 if (properties.option_index < OPT_SCHEMA_PROPERTIES_START) {
873 continue;
875 std::string default_value;
876 if (!properties.default_value.empty()) {
877 default_value = "{default=" + properties.default_value + "}";
879 if (properties.option == "cloudtrail-aws-profile") {
880 print_cloudtrail_aws_profile_config(arg_num, properties.display.c_str(), properties.description.c_str());
881 } else if (properties.option == "cloudtrail-aws-region") {
882 print_cloudtrail_aws_region_config(arg_num, properties.display.c_str(), properties.description.c_str());
883 } else {
884 printf(
885 "arg {number=%u}"
886 "{call=--%s}"
887 "{display=%s}"
888 "{type=%s}"
889 "%s"
890 "{tooltip=%s}"
891 "{group=Capture}"
892 "\n",
893 arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str());
894 if (properties.enum_values.size() > 0) {
895 for (const auto &enum_val : properties.enum_values) {
896 printf(
897 "value {arg=%u}"
898 "{value=%s}"
899 "{display=%s}"
900 "%s"
901 "\n",
902 arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : "");
906 arg_num++;
908 extcap_config_debug(&arg_num);
910 return EXIT_SUCCESS;
913 int main(int argc, char **argv)
915 char* configuration_init_error;
916 int result;
917 int option_idx = 0;
918 int ret = EXIT_FAILURE;
919 extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
920 std::map<std::string, struct plugin_configuration> plugin_configs;
921 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
922 struct syscall_configuration syscall_config = {};
923 #endif
924 char* help_url;
925 char* help_header = NULL;
926 sinsp inspector;
927 std::string plugin_source;
929 /* Initialize log handler early so we can have proper logging during startup. */
930 extcap_log_init("falcodump");
933 * Get credential information for later use.
935 init_process_policies();
938 * Attempt to get the pathname of the directory containing the
939 * executable file.
941 configuration_init_error = configuration_init(argv[0], "Stratoshark");
942 if (configuration_init_error != NULL) {
943 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
944 configuration_init_error);
945 g_free(configuration_init_error);
948 // Plain eBPF requires extra configuration, so probe for kmod and modern BPF support only for now.
949 #ifdef HAS_ENGINE_KMOD
950 try {
951 inspector.open_kmod();
952 extcap_base_register_interface(extcap_conf, KMOD_ENGINE, "System calls via kernel module", 147, "USER0");
953 } catch (sinsp_exception &e) {
954 ws_warning("Unable to open kmod: %s", e.what());
956 inspector.close();
957 #endif
958 #ifdef HAS_ENGINE_MODERN_BPF
959 try {
960 inspector.open_modern_bpf();
961 extcap_base_register_interface(extcap_conf, MODERN_BPF_ENGINE, "System calls via modern eBPF", 147, "USER0");
962 } catch (sinsp_exception &e) {
963 ws_warning("Unable to open kmod: %s", e.what());
965 inspector.close();
966 #endif
968 load_plugins(inspector);
970 if (get_source_plugins(inspector, plugin_configs)) {
971 for (auto iter = plugin_configs.begin(); iter != plugin_configs.end(); ++iter) {
972 // Where we're going we don't need DLTs, so use USER0 (147).
973 // Additional info available via plugin->description() and plugin->event_source().
974 extcap_base_register_interface(extcap_conf, iter->first.c_str(), "Falco plugin", 147, "USER0");
976 } else {
977 ws_warning("Unable to load plugins.");
980 if (g_list_length(extcap_conf->interfaces) < 1) {
981 ws_debug("No source plugins found.");
982 goto end;
985 help_url = data_file_url("falcodump.html");
986 extcap_base_set_util_info(extcap_conf, argv[0], FALCODUMP_VERSION_MAJOR, FALCODUMP_VERSION_MINOR,
987 FALCODUMP_VERSION_RELEASE, help_url);
988 g_free(help_url);
990 help_header = ws_strdup_printf(
991 " %s --extcap-interfaces\n"
992 " %s --extcap-interface=%s --extcap-capture-filter=<filter>\n"
993 " %s --extcap-interface=%s --extcap-dlts\n"
994 " %s --extcap-interface=%s --extcap-config\n"
995 " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url> [--extcap-capture-filter=<filter>]\n",
996 argv[0],
997 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
998 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
999 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
1000 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER);
1001 extcap_help_add_header(extcap_conf, help_header);
1002 g_free(help_header);
1003 extcap_help_add_option(extcap_conf, "--help", "print this help");
1004 extcap_help_add_option(extcap_conf, "--version", "print the version");
1005 extcap_help_add_option(extcap_conf, "--plugin-api-version", "print the Falco plugin API version");
1006 extcap_help_add_option(extcap_conf, "--plugin-source", "plugin source URL");
1007 extcap_help_add_option(extcap_conf, "--include-capture-processes", "Include capture processes");
1008 extcap_help_add_option(extcap_conf, "--include-switch-calls", "Include \"switch\" calls");
1010 for (const auto &it : plugin_configs) {
1011 const struct plugin_configuration plugin_config = it.second;
1012 for (const auto &prop : plugin_config.property_list) {
1013 if (prop.option_index < OPT_SCHEMA_PROPERTIES_START) {
1014 continue;
1016 extcap_help_add_option(extcap_conf, g_strdup_printf("--%s", prop.option.c_str()), g_strdup(prop.description.c_str()));
1020 ws_opterr = 0;
1021 ws_optind = 0;
1023 if (argc == 1) {
1024 extcap_help_print(extcap_conf);
1025 goto end;
1028 static const std::vector<ws_option> longopts = get_longopts(plugin_configs);
1029 while ((result = ws_getopt_long(argc, argv, ":", longopts.data(), &option_idx)) != -1) {
1031 switch (result) {
1033 case OPT_HELP:
1034 extcap_help_print(extcap_conf);
1035 ret = EXIT_SUCCESS;
1036 goto end;
1038 case OPT_VERSION:
1039 extcap_version_print(extcap_conf);
1040 ret = EXIT_SUCCESS;
1041 goto end;
1043 case OPT_PLUGIN_API_VERSION:
1044 fprintf(stdout, "Falco plugin API version %s\n", inspector.get_plugin_api_version());
1045 ret = EXIT_SUCCESS;
1046 goto end;
1048 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1049 case OPT_INCLUDE_CAPTURE_PROCESSES:
1050 syscall_config.include_capture_processes = get_bool_value(ws_optarg);
1051 break;
1053 case OPT_INCLUDE_SWITCH_CALLS:
1054 syscall_config.include_switch_calls = get_bool_value(ws_optarg);
1055 break;
1056 #endif
1058 case OPT_PLUGIN_SOURCE:
1059 plugin_source = ws_optarg;
1060 break;
1062 case ':':
1063 /* missing option argument */
1064 ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
1065 break;
1067 default:
1068 if (result >= OPT_SCHEMA_PROPERTIES_START) {
1069 bool found = false;
1070 for (auto &it : plugin_configs) {
1071 struct plugin_configuration *plugin_config = &it.second;
1072 for (auto &prop : plugin_config->property_list) {
1073 if (prop.option_index == result) {
1074 prop.current_value = ws_optarg;
1075 found = true;
1076 break;
1079 if (found) {
1080 break;
1083 if (!found) {
1084 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
1085 goto end;
1087 } else if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
1088 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
1089 goto end;
1094 extcap_cmdline_debug(argv, argc);
1096 if (extcap_base_handle_interface(extcap_conf)) {
1097 ret = EXIT_SUCCESS;
1098 goto end;
1101 if (extcap_conf->show_config) {
1102 #ifdef HAS_ENGINE_KMOD
1103 if (strcmp(extcap_conf->interface, KMOD_ENGINE) == 0)
1105 ret = show_syscall_config();
1107 else
1108 #endif
1109 #ifdef HAS_ENGINE_MODERN_BPF
1110 if (strcmp(extcap_conf->interface, MODERN_BPF_ENGINE) == 0)
1112 ret = show_syscall_config();
1114 else
1115 #endif
1117 ret = show_plugin_config(extcap_conf->interface, plugin_configs.at(extcap_conf->interface));
1119 goto end;
1122 if (extcap_conf->capture || extcap_conf->capture_filter) {
1123 bool builtin_capture = false;
1125 #ifdef DEBUG_SINSP
1126 inspector.set_debug_mode(true);
1127 inspector.set_log_stderr();
1128 #endif
1130 #ifdef HAS_ENGINE_KMOD
1131 if (strcmp(extcap_conf->interface, KMOD_ENGINE) == 0)
1133 try {
1134 inspector.open_kmod();
1135 builtin_capture = true;
1136 } catch (sinsp_exception &e) {
1137 ws_warning("Unable to open " KMOD_ENGINE ": %s", e.what());
1140 else
1141 #endif
1142 #ifdef HAS_ENGINE_MODERN_BPF
1143 if (strcmp(extcap_conf->interface, MODERN_BPF_ENGINE) == 0)
1145 try {
1146 inspector.open_modern_bpf();
1147 builtin_capture = true;
1148 } catch (sinsp_exception &e) {
1149 ws_warning("Unable to open " MODERN_BPF_ENGINE ": %s", e.what());
1152 else
1153 #endif
1155 if (plugin_source.empty()) {
1156 if (extcap_conf->capture) {
1157 ws_warning("Missing or invalid parameter: --plugin-source");
1158 } else {
1159 // XXX Can we bypass this somehow?
1160 fprintf(stdout, "Validating a capture filter requires a plugin source");
1162 goto end;
1165 std::shared_ptr<sinsp_plugin> plugin_interface;
1166 const auto plugin_manager = inspector.get_plugin_manager();
1167 for (auto &plugin : plugin_manager->plugins()) {
1168 if (plugin->name() == extcap_conf->interface) {
1169 plugin_interface = plugin;
1173 if (plugin_interface == nullptr) {
1174 ws_warning("Unable to find interface %s", extcap_conf->interface);
1175 goto end;
1178 try {
1179 std::string init_err;
1180 plugin_interface->init(plugin_configs[extcap_conf->interface].json_config().c_str(), init_err);
1181 if (!init_err.empty()) {
1182 ws_warning("%s", init_err.c_str());
1183 goto end;
1185 #if SINSP_CHECK_VERSION(0, 18, 0)
1186 inspector.open_plugin(extcap_conf->interface, plugin_source, sinsp_plugin_platform::SINSP_PLATFORM_HOSTINFO);
1187 #else
1188 inspector.open_plugin(extcap_conf->interface, plugin_source);
1189 #endif
1190 // scap_dump_open handles "-"
1191 } catch (sinsp_exception &e) {
1192 ws_warning("%s", e.what());
1193 goto end;
1197 if (!extcap_conf->capture) {
1198 // Check our filter syntax
1199 try {
1200 sinsp_filter_compiler compiler(&inspector, extcap_conf->capture_filter);
1201 compiler.compile();
1202 } catch (sinsp_exception &e) {
1203 fprintf(stdout, "%s", e.what());
1204 goto end;
1206 ret = EXIT_SUCCESS;
1207 goto end;
1210 sinsp_dumper dumper;
1211 try {
1212 dumper.open(&inspector, extcap_conf->fifo, false);
1213 } catch (sinsp_exception &e) {
1214 dumper.close();
1215 ws_warning("%s", e.what());
1216 goto end;
1219 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1220 std::string capture_filter = syscall_capture_filter(syscall_config, extcap_conf->capture_filter);
1221 if (!capture_filter.empty()) {
1222 ws_debug("Setting filter %s\n", capture_filter.c_str());
1223 try {
1224 inspector.set_filter(capture_filter);
1225 } catch (sinsp_exception &e) {
1226 fprintf(stdout, "%s", e.what());
1227 goto end;
1230 #endif
1232 if (builtin_capture) {
1233 inspector.start_capture();
1236 sinsp_evt *evt;
1237 ws_noisy("Starting capture loop.");
1238 while (!extcap_end_application) {
1239 try {
1240 int32_t res = inspector.next(&evt);
1241 switch (res) {
1242 case SCAP_TIMEOUT:
1243 case SCAP_FILTERED_EVENT:
1244 break;
1245 case SCAP_SUCCESS:
1246 dumper.dump(evt);
1247 dumper.flush();
1248 break;
1249 default:
1250 ws_noisy("Inspector exited with %d", res);
1251 extcap_end_application = true;
1252 break;
1254 } catch (sinsp_exception &e) {
1255 ws_warning("%s", e.what());
1256 goto end;
1259 ws_noisy("Closing dumper and inspector.");
1260 if (builtin_capture) {
1261 inspector.stop_capture();
1263 dumper.close();
1264 inspector.close();
1265 ret = EXIT_SUCCESS;
1266 } else {
1267 ws_debug("You should not come here... maybe some parameter missing?");
1270 end:
1271 /* clean up stuff */
1272 extcap_base_cleanup(&extcap_conf);
1273 return ret;