Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / extcap / falcodump.cpp
blobbee97c0c96746572b7414c30082bd558994ac861
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 <libsinsp/plugin_manager.h>
33 #include <libscap/scap_engines.h>
35 #define WS_LOG_DOMAIN "falcodump"
37 #include <extcap/extcap-base.h>
39 #include <wsutil/application_flavor.h>
40 #include <wsutil/file_util.h>
41 #include <wsutil/filesystem.h>
42 #include <wsutil/json_dumper.h>
43 #include <wsutil/privileges.h>
44 #include <wsutil/utf8_entities.h>
45 #include <wsutil/wsjson.h>
46 #include <wsutil/wslog.h>
48 #define FALCODUMP_VERSION_MAJOR "1"
49 #define FALCODUMP_VERSION_MINOR "0"
50 #define FALCODUMP_VERSION_RELEASE "0"
52 #define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
54 #define SINSP_CHECK_VERSION(major, minor, micro) \
55 (((SINSP_VERSION_MAJOR << 16) + (SINSP_VERSION_MINOR << 8) + SINSP_VERSION_MICRO) >= ((major << 16) + (minor << 8) + micro))
57 // We load our plugins and fetch their configs before we set our log level.
58 // #define DEBUG_JSON_PARSING
59 // #define DEBUG_SINSP
61 enum {
62 EXTCAP_BASE_OPTIONS_ENUM,
63 OPT_HELP,
64 OPT_VERSION,
65 OPT_PLUGIN_API_VERSION,
66 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
67 OPT_INCLUDE_CAPTURE_PROCESSES,
68 OPT_INCLUDE_SWITCH_CALLS,
69 #endif
70 OPT_PLUGIN_SOURCE,
71 OPT_SCHEMA_PROPERTIES_START,
74 // "s3DownloadConcurrency": {
75 // "type": "integer",
76 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)"
77 // },
78 struct config_properties {
79 std::string name;
80 std::string display; // "title" property || name
81 std::string option; // Command line option including leading dashes (lowercase display name)
82 int option_index; // Starts from OPT_SCHEMA_PROPERTIES_START
83 std::string type; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES"
84 std::string description;
85 std::string default_value;
86 std::vector<std::string>enum_values;
87 std::string current_value;
90 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
91 struct syscall_configuration {
92 bool include_capture_processes;
93 bool include_switch_calls;
95 #endif
97 struct plugin_configuration {
98 std::vector<struct config_properties> property_list;
100 std::string json_config() {
101 json_dumper dumper = {};
102 dumper.output_string = g_string_new(NULL);
104 json_dumper_begin_object(&dumper);
106 for (const auto &prop : property_list) {
107 if (prop.type == "BEGIN_CONFIG_PROPERTIES") {
108 json_dumper_set_member_name(&dumper, prop.name.c_str());
109 json_dumper_begin_object(&dumper);
110 continue;
111 } else if (prop.type == "END_CONFIG_PROPERTIES") {
112 json_dumper_end_object(&dumper);
113 continue;
116 if (prop.current_value == prop.default_value) {
117 continue;
120 json_dumper_set_member_name(&dumper, prop.name.c_str());
121 if (prop.type == "string" || prop.type == "selector") {
122 json_dumper_value_string(&dumper, prop.current_value.c_str());
123 } else {
124 json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str());
128 json_dumper_end_object(&dumper);
129 json_dumper_finish(&dumper);
130 std::string config_blob = dumper.output_string->str;
131 ws_debug("configuration: %s", dumper.output_string->str);
132 g_string_free(dumper.output_string, TRUE);
133 return config_blob;
137 // Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c.
138 // XXX Use g_file_get_contents or GMappedFile instead?
139 static int
140 fgetline(char *buf, int size, FILE *fp)
142 if (fgets(buf, size, fp)) {
143 int len = (int)strcspn(buf, "\r\n");
144 buf[len] = '\0';
145 return len;
147 return -1;
150 static const size_t MAX_AWS_LINELEN = 2048;
151 void print_cloudtrail_aws_profile_config(int arg_num, const char *display, const char *description) {
152 char buf[MAX_AWS_LINELEN];
153 char profile_name[MAX_AWS_LINELEN];
154 FILE *aws_fp;
155 std::set<std::string>profiles;
157 // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
158 char *cred_path = g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE"));
159 if (cred_path == NULL) {
160 cred_path = g_build_filename(g_get_home_dir(), ".aws", "credentials", (char *)NULL);
163 aws_fp = ws_fopen(cred_path, "r");
164 g_free(cred_path);
166 if (aws_fp != NULL) {
167 while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
168 if (sscanf(buf, "[%2047[^]]s]", profile_name) == 1) {
169 if (strcmp(profile_name, "default") == 0) {
170 continue;
172 profiles.insert(profile_name);
175 fclose(aws_fp);
178 char *conf_path = g_strdup(g_getenv("AWS_CONFIG_FILE"));
179 if (conf_path == NULL) {
180 conf_path = g_build_filename(g_get_home_dir(), ".aws", "config", (char *)NULL);
183 aws_fp = ws_fopen(conf_path, "r");
184 g_free(conf_path);
186 if (aws_fp != NULL) {
187 while (fgetline(buf, sizeof(buf), aws_fp) >= 0) {
188 if (sscanf(buf, "[profile %2047[^]]s]", profile_name) == 1) {
189 if (strcmp(profile_name, "default") == 0) {
190 continue;
192 profiles.insert(profile_name);
195 fclose(aws_fp);
198 const char *aws_profile_env = g_getenv("AWS_PROFILE");
199 for (auto &profile : profiles) {
200 if (aws_profile_env && profile == aws_profile_env) {
201 aws_profile_env = nullptr;
204 if (aws_profile_env) {
205 profiles.insert(aws_profile_env);
208 printf(
209 "arg {number=%d}"
210 "{call=--cloudtrail-aws-profile}"
211 "{display=%s}"
212 "{type=editselector}"
213 "{tooltip=%s}"
214 "{group=Capture}"
215 "\n",
216 arg_num, display, description);
217 printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num);
218 for (auto &profile : profiles) {
219 printf(
220 "value {arg=%d}"
221 "{value=%s}"
222 "{display=%s}"
223 "\n",
224 arg_num, profile.c_str(), profile.c_str());
228 void print_cloudtrail_aws_region_config(int arg_num, const char *display, const char *description) {
229 // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort
230 std::set<std::string> regions = {
231 "af-south-1",
232 "ap-east-1",
233 "ap-northeast-1",
234 "ap-northeast-2",
235 "ap-northeast-3",
236 "ap-south-1",
237 "ap-south-2",
238 "ap-southeast-1",
239 "ap-southeast-2",
240 "ap-southeast-3",
241 "ap-southeast-4",
242 "ca-central-1",
243 "ca-west-1",
244 "eu-central-1",
245 "eu-central-2",
246 "eu-north-1",
247 "eu-south-1",
248 "eu-south-2",
249 "eu-west-1",
250 "eu-west-2",
251 "eu-west-3",
252 "il-central-1",
253 "me-central-1",
254 "me-south-1",
255 "sa-east-1",
256 "us-east-1",
257 "us-east-2",
258 "us-west-1",
259 "us-west-2",
262 const char *aws_region_env = g_getenv("AWS_REGION");
263 for (auto &region : regions) {
264 if (aws_region_env && region == aws_region_env) {
265 aws_region_env = nullptr;
268 if (aws_region_env) {
269 regions.insert(aws_region_env);
272 printf(
273 "arg {number=%d}"
274 "{call=--cloudtrail-aws-region}"
275 "{display=%s}"
276 "{type=editselector}"
277 "{tooltip=%s}"
278 "{group=Capture}"
279 "\n",
280 arg_num, display, description);
281 printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num);
283 for (auto &region : regions) {
284 printf(
285 "value {arg=%d}"
286 "{value=%s}"
287 "{display=%s}"
288 "\n",
289 arg_num, region.c_str(), region.c_str());
294 // Load our plugins. This should match the behavior of the Falco Bridge dissector.
295 static void load_plugins(sinsp &inspector) {
296 WS_DIR *dir;
297 WS_DIRENT *file;
298 char *plugin_paths[] = {
299 // XXX Falco plugins should probably be installed in a path that reflects
300 // the Falco version or its plugin API version.
301 g_build_filename(get_plugins_dir(), "falco", NULL),
302 g_build_filename(get_plugins_pers_dir(), "falco", NULL)
305 for (size_t idx = 0; idx < 2; idx++) {
306 char *plugin_path = plugin_paths[idx];
307 if ((dir = ws_dir_open(plugin_path, 0, NULL)) != NULL) {
308 while ((file = ws_dir_read_name(dir)) != NULL) {
309 char *libname = g_build_filename(plugin_path, ws_dir_get_name(file), NULL);
310 try {
311 auto plugin = inspector.register_plugin(libname);
312 ws_debug("Registered plugin %s via %s", plugin->name().c_str(), libname);
313 } catch (sinsp_exception &e) {
314 ws_warning("%s", e.what());
316 g_free(libname);
318 ws_dir_close(dir);
320 g_free(plugin_path);
324 // Given a key, try to find its value in a JSON object.
325 // Returns (value, true) on success, or (err_str, false) on failure.
326 const std::pair<const std::string,bool> find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) {
327 std::vector<jsmntok_t> tokens;
328 int num_tokens = json_parse(object_blob.c_str(), NULL, 0);
330 switch (num_tokens) {
331 case JSMN_ERROR_INVAL:
332 return std::pair<std::string,bool>("invalid", false);
333 case JSMN_ERROR_PART:
334 return std::pair<std::string,bool>("incomplete", false);
335 default:
336 break;
339 tokens.resize(num_tokens);
340 json_parse(object_blob.c_str(), tokens.data(), num_tokens);
341 for (int idx = 0; idx < num_tokens - 1; idx++) {
342 jsmntok_t &k_tok = tokens[idx];
343 jsmntok_t &v_tok = tokens[idx+1];
344 std::string cur_key = object_blob.substr(k_tok.start, k_tok.end - k_tok.start);
345 if (cur_key == key && k_tok.type == JSMN_STRING && v_tok.type == value_type) {
346 std::string value = object_blob.substr(v_tok.start, v_tok.end - v_tok.start);
347 return std::pair<std::string,bool>(value, true);
349 #ifdef DEBUG_JSON_PARSING
350 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);
351 #endif
353 return std::pair<const std::string,bool>("", false);
356 // Given an RFC 6901-style JSON pointer, try to find its value in a JSON object.
357 // Returns (value, true) on success, or (err_str, false) on failure.
358 const std::pair<const std::string,bool> find_json_pointer_value(const std::string &object_blob, const std::string &pointer, int value_type) {
359 std::string blob = object_blob;
360 std::istringstream ob_stream(pointer);
361 std::string token;
362 while (std::getline(ob_stream, token, '/')) {
363 if (token == "#" || token.empty()) {
364 continue;
366 std::pair<std::string,bool> jv = find_json_object_value(blob, token, value_type);
367 if (!jv.second) {
368 #ifdef DEBUG_JSON_PARSING
369 ws_warning("JSON pointer %s not found at %s", blob.c_str(), token.c_str());
370 #endif
371 return std::pair<std::string,bool>("", false);
373 blob = jv.first;
375 #ifdef DEBUG_JSON_PARSING
376 ws_warning("JSON pointer %s = %s ... %s", pointer.c_str(), blob.substr(0, 10).c_str(), blob.substr(blob.size() - 10, 10).c_str());
377 #endif
378 return std::pair<const std::string,bool>(blob, true);
381 // Convert a JSON array to a string vector.
382 // Returns (vector, true) on success, or (err_str, false) on failure.
383 const std::pair<std::vector<std::string>,bool> get_json_array(const std::string &array_blob) {
384 std::vector<jsmntok_t> tokens;
385 int num_tokens = json_parse(array_blob.c_str(), NULL, 0);
387 switch (num_tokens) {
388 case JSMN_ERROR_INVAL:
389 return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false);
390 case JSMN_ERROR_PART:
392 return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false);
394 default:
395 break;
398 tokens.resize(num_tokens);
399 json_parse(array_blob.c_str(), tokens.data(), num_tokens);
400 std::vector<std::string> elements;
401 // First token is the full array.
402 for (int idx = 1; idx < num_tokens; idx++) {
403 jsmntok_t &el_tok = tokens[idx];
404 elements.push_back(array_blob.substr(el_tok.start, el_tok.end - el_tok.start));
406 #ifdef DEBUG_JSON_PARSING
407 ws_warning("%s: %d", array_blob.c_str(), (int)elements.size());
408 #endif
409 return std::pair<std::vector<std::string>,bool>(elements, true);
412 // Given a JSON blob containing a schema properties object, add each property to the
413 // given plugin config.
414 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) {
415 std::vector<jsmntok_t> tokens;
416 int num_tokens = json_parse(props_blob.c_str(), NULL, 0);
418 switch (num_tokens) {
419 case JSMN_ERROR_INVAL:
420 return std::pair<std::string,bool>("invalid", false);
421 case JSMN_ERROR_PART:
422 return std::pair<std::string,bool>("incomplete", false);
423 default:
424 break;
427 tokens.resize(num_tokens);
428 json_parse(props_blob.c_str(), tokens.data(), num_tokens);
430 if (tokens[0].type != JSMN_OBJECT) {
431 return std::pair<std::string,bool>("malformed", false);
434 char *plugin_name_lower = g_ascii_strdown(plugin_name.c_str(), -1);
436 // We have an object blob which contains a list of properties and property blobs, e.g.
437 // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } }
438 // Skip over the outer { ... } and process the contents as pairs.
439 for (int idx = 1; idx < num_tokens - 2; idx++) {
440 jsmntok_t &n_tok = tokens[idx];
441 jsmntok_t &p_tok = tokens[idx+1];
443 std::string name = props_blob.substr(n_tok.start, n_tok.end - n_tok.start);
444 std::string display = name;
445 std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start);
446 std::vector<std::string> enum_values;
448 // XXX Check for errors?
449 int prop_tokens = json_parse(property_blob.c_str(), NULL, 0);
450 switch (prop_tokens) {
451 case JSMN_ERROR_INVAL:
452 return std::pair<std::string,bool>("invalid property", false);
453 case JSMN_ERROR_PART:
454 return std::pair<std::string,bool>("incomplete property", false);
455 default:
456 break;
458 #ifdef DEBUG_JSON_PARSING
459 ws_warning("property %s [%d]\n", name.c_str(), prop_tokens);
460 #endif
462 std::pair<std::string,bool> jv = find_json_object_value(property_blob, "properties", JSMN_OBJECT);
463 if (jv.second) {
464 config_properties properties = {
465 name,
466 display,
469 "BEGIN_CONFIG_PROPERTIES",
472 enum_values,
475 property_list.push_back(properties);
476 get_schema_properties(jv.first, opt_idx, option_prefix + "-" + name, plugin_name, property_list);
477 properties = {
478 name,
479 display,
482 "END_CONFIG_PROPERTIES",
485 enum_values,
488 property_list.push_back(properties);
489 idx += prop_tokens;
490 continue;
493 jv = find_json_object_value(property_blob, "title", JSMN_STRING);
494 if (jv.second) {
495 display = jv.first;
497 // else split+capitalize "name"?
499 jv = find_json_object_value(property_blob, "type", JSMN_STRING);
500 if (!jv.second) {
501 return std::pair<std::string,bool>("missing type", false);
503 std::string type = jv.first;
504 jv = find_json_object_value(property_blob, "description", JSMN_STRING);
505 if (!jv.second) {
506 return std::pair<std::string,bool>("missing description", false);
508 std::string description = jv.first;
509 std::string default_value;
510 jv = find_json_object_value(property_blob, "default", JSMN_STRING);
511 if (jv.second) {
512 default_value = jv.first;
513 } else {
514 std::string default_pfx = "(Default: ";
515 size_t pfx_pos = description.rfind(default_pfx);
516 if (pfx_pos != std::string::npos) {
517 default_value = description.substr(pfx_pos + default_pfx.size());
518 pfx_pos = default_value.rfind(")");
519 default_value = default_value.erase(pfx_pos);
522 jv = find_json_object_value(property_blob, "enum", JSMN_ARRAY);
523 if (jv.second) {
524 const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first);
525 if (ja.second) {
526 enum_values = ja.first;
527 type = "selector";
530 #ifdef DEBUG_JSON_PARSING
531 ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size());
532 #endif
533 const char *call = g_ascii_strdown(name.c_str(), -1);
534 config_properties properties = {
535 name,
536 display,
537 std::string() + plugin_name_lower + option_prefix + "-" + call, // Command line option (lowercase plugin + display name)
538 opt_idx,
539 type,
540 description,
541 default_value,
542 enum_values,
543 default_value,
545 property_list.push_back(properties);
546 g_free((void *)call);
547 idx += prop_tokens;
548 opt_idx++;
550 g_free(plugin_name_lower);
551 return std::pair<std::string,bool>("",true);
554 // Wherein we try to implement a sufficiently complete JSON Schema parser.
555 // Given a plugin config schema like the following:
557 // "$schema": "http://json-schema.org/draft-04/schema#",
558 // "$ref": "#/definitions/PluginConfig",
559 // "definitions": {
560 // "PluginConfig": {
561 // "properties": {
562 // "s3DownloadConcurrency": {
563 // "type": "integer",
564 // "title": "S3 download concurrency",
565 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)",
566 // "default": 1
567 // },
568 // [ ... ]
569 // "aws": {
570 // "$schema": "http://json-schema.org/draft-04/schema#",
571 // "$ref": "#/definitions/PluginConfigAWS"
572 // }
573 // },
574 // "additionalProperties": true,
575 // "type": "object"
576 // },
577 // "PluginConfigAWS": {
578 // "properties": {
579 // "profile": {
580 // "type": "string",
581 // "title": "Shared AWS Config Profile",
582 // "description": "If non-empty",
583 // "default": "''"
584 // },
585 // [ ... ]
586 // },
587 // "additionalProperties": true,
588 // "type": "object"
589 // }
590 // }
592 // find the plugin properties and parse them using parse_schema_properties.
593 // https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
595 static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin, plugin_configuration &plugin_config)
597 ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON;
598 std::string schema_blob = plugin->get_init_schema(schema_type);
599 std::string config_name;
600 std::pair<std::string,bool> jv;
602 #ifdef DEBUG_JSON_PARSING
603 ws_warning("raw schema: %s\n", schema_blob.c_str());
604 #endif
606 int ref_cnt = 0;
607 std::string::size_type ref_pos = 0;
608 while ((ref_pos = schema_blob.find("\"$ref\"", ref_pos)) != std::string::npos) {
609 ref_cnt++;
610 ref_pos += 5;
613 // Dereference all of our $ref pairs.
614 // This is kind of janky, but makes subsequent parsing more simple.
615 for (int ref_idx = 0; ref_idx < ref_cnt; ref_idx++) {
616 jv = find_json_object_value(schema_blob, "$ref", JSMN_STRING);
617 if (!jv.second) {
618 break;
620 const std::string ref_pointer = jv.first;
621 jv = find_json_pointer_value(schema_blob, ref_pointer, JSMN_OBJECT);
622 if (!jv.second) {
623 ws_warning("Unable to find $ref %s.", ref_pointer.c_str());
624 return false;
626 const std::string ref_body = jv.first.substr(1, jv.first.size() - 2);
628 std::vector<jsmntok_t> tokens;
629 int num_tokens = json_parse(schema_blob.c_str(), NULL, 0);
631 switch (num_tokens) {
632 case JSMN_ERROR_INVAL:
633 ws_warning("Invalid schema.");
634 return false;
635 case JSMN_ERROR_PART:
637 ws_warning("Incomplete schema.");
638 return false;
640 default:
641 break;
644 #ifdef DEBUG_JSON_PARSING
645 ws_warning("searching for $ref: %s\n", ref_pointer.c_str());
646 #endif
647 tokens.resize(num_tokens);
648 json_parse(schema_blob.c_str(), tokens.data(), num_tokens);
649 std::vector<std::string> elements;
650 // First token is the full array.
651 for (int idx = 0; idx < num_tokens - 1; idx++) {
652 if (tokens[idx].type != JSMN_STRING && tokens[idx+1].type != JSMN_STRING) {
653 continue;
655 auto key_tok = &tokens[idx];
656 auto val_tok = &tokens[idx+1];
657 std::string key = schema_blob.substr(key_tok->start, key_tok->end - key_tok->start);
658 std::string value = schema_blob.substr(val_tok->start, val_tok->end - val_tok->start);
659 if (key != "$ref" || value != ref_pointer) {
660 continue;
662 try {
663 #ifdef DEBUG_JSON_PARSING
664 ws_warning("replacing: %s\n", schema_blob.substr(key_tok->start - 1, val_tok->end - key_tok->start + 2).c_str());
665 #endif
666 schema_blob.replace(key_tok->start - 1, val_tok->end - key_tok->start + 2, ref_body);
667 } catch (std::out_of_range const&) {
668 ws_warning("Unknown reference %s.", key.c_str());
669 return false;
674 #ifdef DEBUG_JSON_PARSING
675 ws_warning("cooked schema: %s\n", schema_blob.c_str());
676 #endif
678 // XXX Should each sub-schema ref be in its own category?
679 jv = find_json_object_value(schema_blob, "properties", JSMN_OBJECT);
680 if (!jv.second) {
681 ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), schema_blob.c_str());
682 return false;
684 int opt_idx = OPT_SCHEMA_PROPERTIES_START;
685 jv = get_schema_properties(jv.first, opt_idx, "", plugin->name(), plugin_config.property_list);
686 if (!jv.second) {
687 ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin->name().c_str(), jv.first.c_str());
688 return false;
691 return true;
694 // For each loaded plugin, get its name and properties.
695 static bool get_source_plugins(sinsp &inspector, std::map<std::string, struct plugin_configuration> &plugin_configs) {
696 const auto plugin_manager = inspector.get_plugin_manager();
698 // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail.
699 try {
700 for (auto &plugin : plugin_manager->plugins()) {
701 if (plugin->caps() & CAP_SOURCING) {
702 plugin_configuration plugin_config = {};
703 if (!get_plugin_config_schema(plugin, plugin_config)) {
704 return false;
706 plugin_configs[plugin->name()] = plugin_config;
709 } catch (sinsp_exception &e) {
710 ws_warning("%s", e.what());
711 return false;
713 return true;
716 // Build our command line options based on our source plugins.
717 static const std::vector<ws_option> get_longopts(const std::map<std::string, struct plugin_configuration> &plugin_configs) {
718 std::vector<ws_option> longopts;
719 struct ws_option base_longopts[] = {
720 EXTCAP_BASE_OPTIONS,
721 { "help", ws_no_argument, NULL, OPT_HELP},
722 { "version", ws_no_argument, NULL, OPT_VERSION},
723 { "plugin-api-version", ws_no_argument, NULL, OPT_PLUGIN_API_VERSION},
724 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
725 { "include-capture-processes", ws_required_argument, NULL, OPT_INCLUDE_CAPTURE_PROCESSES },
726 { "include-switch-calls", ws_required_argument, NULL, OPT_INCLUDE_SWITCH_CALLS },
727 #endif
728 { "plugin-source", ws_required_argument, NULL, OPT_PLUGIN_SOURCE },
729 { 0, 0, 0, 0}
731 int idx;
732 for (idx = 0; base_longopts[idx].name; idx++) {
733 longopts.push_back(base_longopts[idx]);
735 for (const auto &it : plugin_configs) {
736 const struct plugin_configuration plugin_config = it.second;
737 for (const auto &prop : plugin_config.property_list) {
738 ws_option option = { g_strdup(prop.option.c_str()), ws_required_argument, NULL, prop.option_index };
739 longopts.push_back(option);
742 longopts.push_back(base_longopts[idx]);
743 return longopts;
746 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
747 static bool
748 get_bool_value(const char *bool_str)
750 if (!bool_str) {
751 return false;
753 switch (bool_str[0]) {
754 case 'f':
755 case 'F':
756 case '0':
757 return false;
758 default:
759 return true;
763 // Show the configuration for a given plugin/interface.
764 static int show_syscall_config(void)
766 printf(
767 "arg {number=0}"
768 "{call=--include-capture-processes}"
769 "{display=Include capture processes}"
770 "{type=boolean}"
771 "{tooltip=Include system calls made by any capture processes (falcodump, dumpcap, and Stratoshark)}"
772 "{required=false}"
773 "{group=Capture}\n"
775 "arg {number=1}"
776 "{call=--include-switch-calls}"
777 "{display=Include \"switch\" calls}"
778 "{type=boolean}"
779 "{tooltip=Include \"switch\" system calls}"
780 "{required=false}"
781 "{group=Capture}\n"
783 "value {arg=0}{value=1}\n"
786 return EXIT_SUCCESS;
789 #include <fstream>
790 #include <iostream>
791 static const std::string syscall_capture_filter(const struct syscall_configuration &syscall_config, const char *capture_filter)
793 if (syscall_config.include_capture_processes && syscall_config.include_switch_calls) {
794 if (capture_filter) {
795 return std::string(capture_filter);
796 } else {
797 return std::string();
801 std::string filter;
803 if (capture_filter) {
804 filter = "(" + std::string(capture_filter) + ") and (";
807 if (!syscall_config.include_capture_processes) {
808 // We want to exclude Stratoshark and any of its children, including
809 // this one (falcodump).
811 std::string pid, comm, _s, ppid;
813 // Exclude this process only at a minimum.
814 std::ifstream stat_stream("/proc/self/stat");
815 stat_stream >> pid >> comm >> _s >> ppid;
816 std::string process_filter = "proc.pid != " + pid;
817 if (comm != "(falcodump)") {
818 ws_warning("Our process is named %s, not falcodump", comm.c_str());
820 stat_stream.close();
822 // If our parent is Stratoshark, exclude it and its direct children.
823 std::ifstream pstat_stream("/proc/" + ppid + "/stat");
824 pstat_stream >> _s >> comm;
825 if (comm == "(stratoshark)") {
826 // XXX Use proc.apid instead?
827 process_filter = "proc.pid != " + ppid + " and proc.ppid != " + ppid;
829 pstat_stream.close();
831 filter += process_filter;
834 if (!syscall_config.include_switch_calls) {
835 if (!syscall_config.include_capture_processes) {
836 filter += " and ";
838 filter += "evt.type != switch";
841 if (capture_filter) {
842 filter += ")";
845 return filter;
847 #endif // HAS_ENGINE_KMOD || HAS_ENGINE_MODERN_BPF
849 // Show the configuration for a given plugin/interface.
850 static int show_plugin_config(const std::string &interface, const struct plugin_configuration &plugin_config)
852 unsigned arg_num = 0;
853 // char* plugin_filter;
855 if (interface.empty()) {
856 ws_warning("ERROR: No interface specified.");
857 return EXIT_FAILURE;
860 printf(
861 "arg {number=%u}"
862 "{call=--plugin-source}"
863 "{display=Log data URL}"
864 "{type=string}"
865 "{tooltip=The plugin data source. This is usually a URL.}"
866 "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS "}"
867 "{required=true}"
868 "{group=Capture}\n",
869 arg_num++);
870 // if (plugin_filter)
871 // printf("{default=%s}", plugin_filter);
872 for (const auto &properties : plugin_config.property_list) {
873 if (properties.option_index < OPT_SCHEMA_PROPERTIES_START) {
874 continue;
876 std::string default_value;
877 if (!properties.default_value.empty()) {
878 default_value = "{default=" + properties.default_value + "}";
880 if (properties.option == "cloudtrail-aws-profile") {
881 print_cloudtrail_aws_profile_config(arg_num, properties.display.c_str(), properties.description.c_str());
882 } else if (properties.option == "cloudtrail-aws-region") {
883 print_cloudtrail_aws_region_config(arg_num, properties.display.c_str(), properties.description.c_str());
884 } else {
885 printf(
886 "arg {number=%u}"
887 "{call=--%s}"
888 "{display=%s}"
889 "{type=%s}"
890 "%s"
891 "{tooltip=%s}"
892 "{group=Capture}"
893 "\n",
894 arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str());
895 if (properties.enum_values.size() > 0) {
896 for (const auto &enum_val : properties.enum_values) {
897 printf(
898 "value {arg=%u}"
899 "{value=%s}"
900 "{display=%s}"
901 "%s"
902 "\n",
903 arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : "");
907 arg_num++;
909 extcap_config_debug(&arg_num);
911 return EXIT_SUCCESS;
914 int main(int argc, char **argv)
916 char* configuration_init_error;
917 int result;
918 int option_idx = 0;
919 int ret = EXIT_FAILURE;
920 extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
921 std::map<std::string, struct plugin_configuration> plugin_configs;
922 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
923 struct syscall_configuration syscall_config = {};
924 #endif
925 char* help_url;
926 char* help_header = NULL;
927 sinsp inspector;
928 std::string plugin_source;
930 /* Set the program name. */
931 g_set_prgname("falcodump");
933 /* Initialize log handler early so we can have proper logging during startup. */
934 extcap_log_init();
937 * Get credential information for later use.
939 init_process_policies();
942 * Attempt to get the pathname of the directory containing the
943 * executable file.
945 configuration_init_error = configuration_init(argv[0]);
946 set_application_flavor(APPLICATION_FLAVOR_STRATOSHARK);
947 if (configuration_init_error != NULL) {
948 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
949 configuration_init_error);
950 g_free(configuration_init_error);
953 // Plain eBPF requires extra configuration, so probe for kmod and modern BPF support only for now.
954 #ifdef HAS_ENGINE_KMOD
955 try {
956 inspector.open_kmod();
957 extcap_base_register_interface(extcap_conf, KMOD_ENGINE, "System calls via kernel module", 147, "USER0");
958 } catch (sinsp_exception &e) {
959 ws_warning("Unable to open kmod: %s", e.what());
961 inspector.close();
962 #endif
963 #ifdef HAS_ENGINE_MODERN_BPF
964 try {
965 inspector.open_modern_bpf();
966 extcap_base_register_interface(extcap_conf, MODERN_BPF_ENGINE, "System calls via modern eBPF", 147, "USER0");
967 } catch (sinsp_exception &e) {
968 ws_warning("Unable to open kmod: %s", e.what());
970 inspector.close();
971 #endif
973 load_plugins(inspector);
975 if (get_source_plugins(inspector, plugin_configs)) {
976 for (auto iter = plugin_configs.begin(); iter != plugin_configs.end(); ++iter) {
977 // Where we're going we don't need DLTs, so use USER0 (147).
978 // Additional info available via plugin->description() and plugin->event_source().
979 extcap_base_register_interface(extcap_conf, iter->first.c_str(), "Falco plugin", 147, "USER0");
981 } else {
982 ws_warning("Unable to load plugins.");
985 if (g_list_length(extcap_conf->interfaces) < 1) {
986 ws_debug("No source plugins found.");
987 goto end;
990 help_url = data_file_url("falcodump.html");
991 extcap_base_set_util_info(extcap_conf, argv[0], FALCODUMP_VERSION_MAJOR, FALCODUMP_VERSION_MINOR,
992 FALCODUMP_VERSION_RELEASE, help_url);
993 g_free(help_url);
995 help_header = ws_strdup_printf(
996 " %s --extcap-interfaces\n"
997 " %s --extcap-interface=%s --extcap-capture-filter=<filter>\n"
998 " %s --extcap-interface=%s --extcap-dlts\n"
999 " %s --extcap-interface=%s --extcap-config\n"
1000 " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url> [--extcap-capture-filter=<filter>]\n",
1001 argv[0],
1002 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
1003 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
1004 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER,
1005 argv[0], FALCODUMP_PLUGIN_PLACEHOLDER);
1006 extcap_help_add_header(extcap_conf, help_header);
1007 g_free(help_header);
1008 extcap_help_add_option(extcap_conf, "--help", "print this help");
1009 extcap_help_add_option(extcap_conf, "--version", "print the version");
1010 extcap_help_add_option(extcap_conf, "--plugin-api-version", "print the Falco plugin API version");
1011 extcap_help_add_option(extcap_conf, "--plugin-source", "plugin source URL");
1012 extcap_help_add_option(extcap_conf, "--include-capture-processes", "Include capture processes");
1013 extcap_help_add_option(extcap_conf, "--include-switch-calls", "Include \"switch\" calls");
1015 for (const auto &it : plugin_configs) {
1016 const struct plugin_configuration plugin_config = it.second;
1017 for (const auto &prop : plugin_config.property_list) {
1018 if (prop.option_index < OPT_SCHEMA_PROPERTIES_START) {
1019 continue;
1021 extcap_help_add_option(extcap_conf, g_strdup_printf("--%s", prop.option.c_str()), g_strdup(prop.description.c_str()));
1025 ws_opterr = 0;
1026 ws_optind = 0;
1028 if (argc == 1) {
1029 extcap_help_print(extcap_conf);
1030 goto end;
1033 static const std::vector<ws_option> longopts = get_longopts(plugin_configs);
1034 while ((result = ws_getopt_long(argc, argv, ":", longopts.data(), &option_idx)) != -1) {
1036 switch (result) {
1038 case OPT_HELP:
1039 extcap_help_print(extcap_conf);
1040 ret = EXIT_SUCCESS;
1041 goto end;
1043 case OPT_VERSION:
1044 extcap_version_print(extcap_conf);
1045 ret = EXIT_SUCCESS;
1046 goto end;
1048 case OPT_PLUGIN_API_VERSION:
1049 fprintf(stdout, "Falco plugin API version %s\n", inspector.get_plugin_api_version());
1050 ret = EXIT_SUCCESS;
1051 goto end;
1053 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1054 case OPT_INCLUDE_CAPTURE_PROCESSES:
1055 syscall_config.include_capture_processes = get_bool_value(ws_optarg);
1056 break;
1058 case OPT_INCLUDE_SWITCH_CALLS:
1059 syscall_config.include_switch_calls = get_bool_value(ws_optarg);
1060 break;
1061 #endif
1063 case OPT_PLUGIN_SOURCE:
1064 plugin_source = ws_optarg;
1065 break;
1067 case ':':
1068 /* missing option argument */
1069 ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]);
1070 break;
1072 default:
1073 if (result >= OPT_SCHEMA_PROPERTIES_START) {
1074 bool found = false;
1075 for (auto &it : plugin_configs) {
1076 struct plugin_configuration *plugin_config = &it.second;
1077 for (auto &prop : plugin_config->property_list) {
1078 if (prop.option_index == result) {
1079 prop.current_value = ws_optarg;
1080 found = true;
1081 break;
1084 if (found) {
1085 break;
1088 if (!found) {
1089 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
1090 goto end;
1092 } else if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) {
1093 ws_warning("Invalid option: %s", argv[ws_optind - 1]);
1094 goto end;
1099 extcap_cmdline_debug(argv, argc);
1101 if (extcap_base_handle_interface(extcap_conf)) {
1102 ret = EXIT_SUCCESS;
1103 goto end;
1106 if (extcap_conf->show_config) {
1107 #ifdef HAS_ENGINE_KMOD
1108 if (strcmp(extcap_conf->interface, KMOD_ENGINE) == 0)
1110 ret = show_syscall_config();
1112 else
1113 #endif
1114 #ifdef HAS_ENGINE_MODERN_BPF
1115 if (strcmp(extcap_conf->interface, MODERN_BPF_ENGINE) == 0)
1117 ret = show_syscall_config();
1119 else
1120 #endif
1122 ret = show_plugin_config(extcap_conf->interface, plugin_configs.at(extcap_conf->interface));
1124 goto end;
1127 if (extcap_conf->capture || extcap_conf->capture_filter) {
1128 bool builtin_capture = false;
1130 #ifdef DEBUG_SINSP
1131 inspector.set_debug_mode(true);
1132 inspector.set_log_stderr();
1133 #endif
1135 #ifdef HAS_ENGINE_KMOD
1136 if (strcmp(extcap_conf->interface, KMOD_ENGINE) == 0)
1138 try {
1139 inspector.open_kmod();
1140 builtin_capture = true;
1141 } catch (sinsp_exception &e) {
1142 ws_warning("Unable to open " KMOD_ENGINE ": %s", e.what());
1145 else
1146 #endif
1147 #ifdef HAS_ENGINE_MODERN_BPF
1148 if (strcmp(extcap_conf->interface, MODERN_BPF_ENGINE) == 0)
1150 try {
1151 inspector.open_modern_bpf();
1152 builtin_capture = true;
1153 } catch (sinsp_exception &e) {
1154 ws_warning("Unable to open " MODERN_BPF_ENGINE ": %s", e.what());
1157 else
1158 #endif
1160 if (plugin_source.empty()) {
1161 if (extcap_conf->capture) {
1162 ws_warning("Missing or invalid parameter: --plugin-source");
1163 } else {
1164 // XXX Can we bypass this somehow?
1165 fprintf(stdout, "Validating a capture filter requires a plugin source");
1167 goto end;
1170 std::shared_ptr<sinsp_plugin> plugin_interface;
1171 const auto plugin_manager = inspector.get_plugin_manager();
1172 for (auto &plugin : plugin_manager->plugins()) {
1173 if (plugin->name() == extcap_conf->interface) {
1174 plugin_interface = plugin;
1178 if (plugin_interface == nullptr) {
1179 ws_warning("Unable to find interface %s", extcap_conf->interface);
1180 goto end;
1183 try {
1184 std::string init_err;
1185 plugin_interface->init(plugin_configs[extcap_conf->interface].json_config().c_str(), init_err);
1186 if (!init_err.empty()) {
1187 ws_warning("%s", init_err.c_str());
1188 goto end;
1190 #if SINSP_CHECK_VERSION(0, 18, 0)
1191 inspector.open_plugin(extcap_conf->interface, plugin_source, sinsp_plugin_platform::SINSP_PLATFORM_HOSTINFO);
1192 #else
1193 inspector.open_plugin(extcap_conf->interface, plugin_source);
1194 #endif
1195 // scap_dump_open handles "-"
1196 } catch (sinsp_exception &e) {
1197 ws_warning("%s", e.what());
1198 goto end;
1202 if (!extcap_conf->capture) {
1203 // Check our filter syntax
1204 try {
1205 sinsp_filter_compiler compiler(&inspector, extcap_conf->capture_filter);
1206 compiler.compile();
1207 } catch (sinsp_exception &e) {
1208 fprintf(stdout, "%s", e.what());
1209 goto end;
1211 ret = EXIT_SUCCESS;
1212 goto end;
1215 sinsp_dumper dumper;
1216 try {
1217 dumper.open(&inspector, extcap_conf->fifo, false);
1218 } catch (sinsp_exception &e) {
1219 dumper.close();
1220 ws_warning("%s", e.what());
1221 goto end;
1224 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1225 std::string capture_filter = syscall_capture_filter(syscall_config, extcap_conf->capture_filter);
1226 if (!capture_filter.empty()) {
1227 ws_debug("Setting filter %s\n", capture_filter.c_str());
1228 try {
1229 inspector.set_filter(capture_filter);
1230 } catch (sinsp_exception &e) {
1231 fprintf(stdout, "%s", e.what());
1232 goto end;
1235 #endif
1237 if (builtin_capture) {
1238 inspector.start_capture();
1241 sinsp_evt *evt;
1242 ws_noisy("Starting capture loop.");
1243 while (!extcap_end_application) {
1244 try {
1245 int32_t res = inspector.next(&evt);
1246 switch (res) {
1247 case SCAP_TIMEOUT:
1248 case SCAP_FILTERED_EVENT:
1249 break;
1250 case SCAP_SUCCESS:
1251 dumper.dump(evt);
1252 dumper.flush();
1253 break;
1254 default:
1255 ws_noisy("Inspector exited with %d", res);
1256 extcap_end_application = true;
1257 break;
1259 } catch (sinsp_exception &e) {
1260 ws_warning("%s", e.what());
1261 goto end;
1264 ws_noisy("Closing dumper and inspector.");
1265 if (builtin_capture) {
1266 inspector.stop_capture();
1268 dumper.close();
1269 inspector.close();
1270 ret = EXIT_SUCCESS;
1271 } else {
1272 ws_debug("You should not come here... maybe some parameter missing?");
1275 end:
1276 /* clean up stuff */
1277 extcap_base_cleanup(&extcap_conf);
1278 return ret;