summaryrefslogtreecommitdiffstats
path: root/src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c')
-rw-r--r--src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c b/src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
new file mode 100644
index 000000000..469f9d2cf
--- /dev/null
+++ b/src/collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "systemd-internals.h"
+
+#define JOURNAL_DIRECTORIES_JSON_NODE "journalDirectories"
+
+static bool is_directory(const char *dir) {
+ struct stat statbuf;
+ if (stat(dir, &statbuf) != 0) {
+ // Error in stat() means the path probably doesn't exist or can't be accessed.
+ return false;
+ }
+ // S_ISDIR macro is true if the path is a directory.
+ return S_ISDIR(statbuf.st_mode) ? true : false;
+}
+
+static const char *is_valid_dir(const char *dir) {
+ if(strcmp(dir, "/") == 0)
+ return "/ is not acceptable";
+
+ if(!strstartswith(dir, "/"))
+ return "only directories starting with / are accepted";
+
+ if(strstr(dir, "/./"))
+ return "directory contains /./";
+
+ if(strstr(dir, "/../") || strendswith(dir, "/.."))
+ return "directory contains /../";
+
+ if(strstartswith(dir, "/dev/") || strcmp(dir, "/dev") == 0)
+ return "directory contains /dev";
+
+ if(strstartswith(dir, "/proc/") || strcmp(dir, "/proc") == 0)
+ return "directory contains /proc";
+
+ if(strstartswith(dir, "/sys/") || strcmp(dir, "/sys") == 0)
+ return "directory contains /sys";
+
+ if(strstartswith(dir, "/etc/") || strcmp(dir, "/etc") == 0)
+ return "directory contains /etc";
+
+ if(strstartswith(dir, "/lib/") || strcmp(dir, "/lib") == 0
+ || strstartswith(dir, "/lib32/") || strcmp(dir, "/lib32") == 0
+ || strstartswith(dir, "/lib64/") || strcmp(dir, "/lib64") == 0)
+ return "directory contains /lib";
+
+ return NULL;
+}
+
+static int systemd_journal_directories_dyncfg_update(BUFFER *result, BUFFER *payload) {
+ if(!payload || !buffer_strlen(payload))
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "empty payload received");
+
+ CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(payload));
+ if(!jobj)
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "cannot parse json payload");
+
+ struct json_object *journalDirectories;
+ json_object_object_get_ex(jobj, JOURNAL_DIRECTORIES_JSON_NODE, &journalDirectories);
+
+ size_t n_directories = json_object_array_length(journalDirectories);
+ if(n_directories > MAX_JOURNAL_DIRECTORIES)
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "too many directories configured");
+
+ // validate the directories
+ for(size_t i = 0; i < n_directories; i++) {
+ struct json_object *dir = json_object_array_get_idx(journalDirectories, i);
+ const char *s = json_object_get_string(dir);
+ if(s && *s) {
+ const char *msg = is_valid_dir(s);
+ if(msg)
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, msg);
+ }
+ }
+
+ size_t added = 0, not_found = 0;
+ for(size_t i = 0; i < n_directories; i++) {
+ struct json_object *dir = json_object_array_get_idx(journalDirectories, i);
+ const char *s = json_object_get_string(dir);
+ if(s && *s) {
+ string_freez(journal_directories[added].path);
+ journal_directories[added++].path = string_strdupz(s);
+
+ if(!is_directory(s))
+ not_found++;
+ }
+ }
+
+ if(!added)
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "no directories in the payload");
+ else {
+ for(size_t i = added; i < MAX_JOURNAL_DIRECTORIES; i++) {
+ string_freez(journal_directories[i].path);
+ journal_directories[i].path = NULL;
+ }
+ }
+
+ journal_watcher_restart();
+
+ return dyncfg_default_response(result, HTTP_RESP_OK, not_found ? "added, but some directories are not found in the filesystem" : "");
+}
+
+static int systemd_journal_directories_dyncfg_get(BUFFER *wb) {
+ buffer_flush(wb);
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
+
+ buffer_json_member_add_array(wb, JOURNAL_DIRECTORIES_JSON_NODE);
+ for(size_t i = 0; i < MAX_JOURNAL_DIRECTORIES ;i++) {
+ if(!journal_directories[i].path)
+ break;
+
+ buffer_json_add_array_item_string(wb, string2str(journal_directories[i].path));
+ }
+ buffer_json_array_close(wb);
+
+ buffer_json_finalize(wb);
+ return HTTP_RESP_OK;
+}
+
+static int systemd_journal_directories_dyncfg_cb(const char *transaction,
+ const char *id,
+ DYNCFG_CMDS cmd,
+ const char *add_name __maybe_unused,
+ BUFFER *payload,
+ usec_t *stop_monotonic_ut __maybe_unused,
+ bool *cancelled __maybe_unused,
+ BUFFER *result,
+ HTTP_ACCESS access __maybe_unused,
+ const char *source __maybe_unused,
+ void *data __maybe_unused) {
+ CLEAN_BUFFER *action = buffer_create(100, NULL);
+ dyncfg_cmds2buffer(cmd, action);
+
+ if(cmd == DYNCFG_CMD_GET)
+ return systemd_journal_directories_dyncfg_get(result);
+
+ if(cmd == DYNCFG_CMD_UPDATE)
+ return systemd_journal_directories_dyncfg_update(result, payload);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "DYNCFG: unhandled transaction '%s', id '%s' cmd '%s', payload: %s",
+ transaction, id, buffer_tostring(action), payload ? buffer_tostring(payload) : "");
+
+ return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "the command is not handled by this plugin");
+}
+
+// ----------------------------------------------------------------------------
+
+void systemd_journal_dyncfg_init(struct functions_evloop_globals *wg) {
+ functions_evloop_dyncfg_add(
+ wg,
+ "systemd-journal:monitored-directories",
+ "/logs/systemd-journal",
+ DYNCFG_STATUS_RUNNING,
+ DYNCFG_TYPE_SINGLE,
+ DYNCFG_SOURCE_TYPE_INTERNAL,
+ "internal",
+ DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE,
+ HTTP_ACCESS_NONE,
+ HTTP_ACCESS_NONE,
+ systemd_journal_directories_dyncfg_cb,
+ NULL);
+}