master
c 177 lines 5.88 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "systemd-internals.h"
4
5 #define JOURNAL_DIRECTORIES_JSON_NODE "journalDirectories"
6
7 static bool is_directory(const char *dir)
8 {
9 struct stat statbuf;
10 if (stat(dir, &statbuf) != 0) {
11 // Error in stat() means the path probably doesn't exist or can't be accessed.
12 return false;
13 }
14 // S_ISDIR macro is true if the path is a directory.
15 return S_ISDIR(statbuf.st_mode) ? true : false;
16 }
17
18 static const char *is_valid_dir(const char *dir)
19 {
20 if (strcmp(dir, "/") == 0)
21 return "/ is not acceptable";
22
23 if (!strstartswith(dir, "/"))
24 return "only directories starting with / are accepted";
25
26 if (strstr(dir, "/./"))
27 return "directory contains /./";
28
29 if (strstr(dir, "/../") || strendswith(dir, "/.."))
30 return "directory contains /../";
31
32 if (strstartswith(dir, "/dev/") || strcmp(dir, "/dev") == 0)
33 return "directory contains /dev";
34
35 if (strstartswith(dir, "/proc/") || strcmp(dir, "/proc") == 0)
36 return "directory contains /proc";
37
38 if (strstartswith(dir, "/sys/") || strcmp(dir, "/sys") == 0)
39 return "directory contains /sys";
40
41 if (strstartswith(dir, "/etc/") || strcmp(dir, "/etc") == 0)
42 return "directory contains /etc";
43
44 if (strstartswith(dir, "/lib/") || strcmp(dir, "/lib") == 0 || strstartswith(dir, "/lib32/") ||
45 strcmp(dir, "/lib32") == 0 || strstartswith(dir, "/lib64/") || strcmp(dir, "/lib64") == 0)
46 return "directory contains /lib";
47
48 return NULL;
49 }
50
51 static int systemd_journal_directories_dyncfg_update(BUFFER *result, BUFFER *payload)
52 {
53 if (!payload || !buffer_strlen(payload))
54 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "empty payload received");
55
56 CLEAN_JSON_OBJECT *jobj = json_tokener_parse(buffer_tostring(payload));
57 if (!jobj)
58 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "cannot parse json payload");
59
60 struct json_object *journalDirectories;
61 json_object_object_get_ex(jobj, JOURNAL_DIRECTORIES_JSON_NODE, &journalDirectories);
62
63 if (json_object_get_type(journalDirectories) != json_type_array)
64 return dyncfg_default_response(
65 result, HTTP_RESP_BAD_REQUEST, "member " JOURNAL_DIRECTORIES_JSON_NODE " is not an array");
66
67 size_t n_directories = json_object_array_length(journalDirectories);
68 if (n_directories > MAX_JOURNAL_DIRECTORIES)
69 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "too many directories configured");
70
71 // validate the directories
72 for (size_t i = 0; i < n_directories; i++) {
73 struct json_object *dir = json_object_array_get_idx(journalDirectories, i);
74 const char *s = json_object_get_string(dir);
75 if (s && *s) {
76 const char *msg = is_valid_dir(s);
77 if (msg)
78 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, msg);
79 }
80 }
81
82 size_t added = 0, not_found = 0;
83 for (size_t i = 0; i < n_directories; i++) {
84 struct json_object *dir = json_object_array_get_idx(journalDirectories, i);
85 const char *s = json_object_get_string(dir);
86 if (s && *s) {
87 string_freez(journal_directories[added].path);
88 journal_directories[added++].path = string_strdupz(s);
89
90 if (!is_directory(s))
91 not_found++;
92 }
93 }
94
95 if (!added)
96 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "no directories in the payload");
97 else {
98 for (size_t i = added; i < MAX_JOURNAL_DIRECTORIES; i++) {
99 string_freez(journal_directories[i].path);
100 journal_directories[i].path = NULL;
101 }
102 }
103
104 nd_journal_watcher_restart();
105
106 return dyncfg_default_response(
107 result, HTTP_RESP_OK, not_found ? "added, but some directories are not found in the filesystem" : "");
108 }
109
110 static int systemd_journal_directories_dyncfg_get(BUFFER *wb)
111 {
112 buffer_flush(wb);
113 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
114
115 buffer_json_member_add_array(wb, JOURNAL_DIRECTORIES_JSON_NODE);
116 for (size_t i = 0; i < MAX_JOURNAL_DIRECTORIES; i++) {
117 if (!journal_directories[i].path)
118 break;
119
120 buffer_json_add_array_item_string(wb, string2str(journal_directories[i].path));
121 }
122 buffer_json_array_close(wb);
123
124 buffer_json_finalize(wb);
125 return HTTP_RESP_OK;
126 }
127
128 static int systemd_journal_directories_dyncfg_cb(
129 const char *transaction,
130 const char *id,
131 DYNCFG_CMDS cmd,
132 const char *add_name __maybe_unused,
133 BUFFER *payload,
134 usec_t *stop_monotonic_ut __maybe_unused,
135 bool *cancelled __maybe_unused,
136 BUFFER *result,
137 HTTP_ACCESS access __maybe_unused,
138 const char *source __maybe_unused,
139 void *data __maybe_unused)
140 {
141 CLEAN_BUFFER *action = buffer_create(100, NULL);
142 dyncfg_cmds2buffer(cmd, action);
143
144 if (cmd == DYNCFG_CMD_GET)
145 return systemd_journal_directories_dyncfg_get(result);
146
147 if (cmd == DYNCFG_CMD_UPDATE)
148 return systemd_journal_directories_dyncfg_update(result, payload);
149
150 nd_log(
151 NDLS_COLLECTORS,
152 NDLP_ERR,
153 "DYNCFG: unhandled transaction '%s', id '%s' cmd '%s', payload: %s",
154 transaction,
155 id,
156 buffer_tostring(action),
157 payload ? buffer_tostring(payload) : "");
158
159 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "the command is not handled by this plugin");
160 }
161
162 void nd_systemd_journal_dyncfg_init(struct functions_evloop_globals *wg)
163 {
164 functions_evloop_dyncfg_add(
165 wg,
166 "systemd-journal:monitored-directories",
167 "/logs/systemd-journal",
168 DYNCFG_STATUS_RUNNING,
169 DYNCFG_TYPE_SINGLE,
170 DYNCFG_SOURCE_TYPE_INTERNAL,
171 "internal",
172 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE,
173 HTTP_ACCESS_NONE,
174 HTTP_ACCESS_NONE,
175 systemd_journal_directories_dyncfg_cb,
176 NULL);
177 }