1 /*
2 * Copyright 2023-2024 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <ftw.h>
13 #include <unistd.h>
14 #include <sys/stat.h>
15
16 #include <crm/cib.h>
17 #include <crm/cib/cib_types.h>
18 #include <crm/cib/internal.h>
19 #include <crm/crm.h>
20 #include <crm/common/mainloop.h>
21 #include <crm/common/xml.h>
22
23 #include "pacemaker-execd.h"
24
25 static pid_t schema_fetch_pid = 0;
26
27 static int
28 rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb)
29 {
30 /* Don't delete PCMK__REMOTE_SCHEMA_DIR . */
31 if (ftwb->level == 0) {
32 return 0;
33 }
34
35 if (remove(pathname) != 0) {
36 int rc = errno;
37 crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc));
38 return -1;
39 }
40
41 return 0;
42 }
43
44 static void
45 clean_up_extra_schema_files(void)
46 {
47 const char *remote_schema_dir = pcmk__remote_schema_dir();
48 struct stat sb;
49 int rc;
50
|
CID (unavailable; MK=8c4b80d0eaae5a177a05973003586960) (#1 of 2): Time of check time of use (TOCTOU): |
|
(1) Event fs_check_call: |
Calling function "stat" to perform check on "remote_schema_dir". |
| Also see events: |
[toctou] |
51 rc = stat(remote_schema_dir, &sb);
52
|
(2) Event path: |
Condition "rc == -1", taking false branch. |
53 if (rc == -1) {
54 if (errno == ENOENT) {
55 /* If the directory doesn't exist, try to make it first. */
56 if (mkdir(remote_schema_dir, 0755) != 0) {
57 rc = errno;
58 crm_err("Could not create directory for schemas: %s",
59 pcmk_rc_str(rc));
60 }
61
62 } else {
63 rc = errno;
64 crm_err("Could not create directory for schemas: %s",
65 pcmk_rc_str(rc));
66 }
67
|
(3) Event path: |
Condition "!((sb.st_mode & 61440) == 16384)", taking false branch. |
68 } else if (!S_ISDIR(sb.st_mode)) {
69 /* If something exists with the same name that's not a directory, that's
70 * an error.
71 */
72 crm_err("%s already exists but is not a directory", remote_schema_dir);
73
74 } else {
75 /* It's a directory - clear it out so we can download potentially new
76 * schema files.
77 */
|
(4) Event toctou: |
Calling function "nftw" that uses "remote_schema_dir" after a check function. This can cause a time-of-check, time-of-use race condition. |
| Also see events: |
[fs_check_call] |
78 rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
79
80 if (rc != 0) {
81 crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc));
82 }
83 }
84 }
85
86 static void
87 write_extra_schema_file(xmlNode *xml, void *user_data)
88 {
89 const char *remote_schema_dir = pcmk__remote_schema_dir();
90 const char *file = NULL;
91 char *path = NULL;
92 int rc;
93
94 file = crm_element_value(xml, PCMK_XA_PATH);
95 if (file == NULL) {
96 crm_warn("No destination path given in schema request");
97 return;
98 }
99
100 path = crm_strdup_printf("%s/%s", remote_schema_dir, file);
101
102 /* The schema is a CDATA node, which is a child of the <file> node. Traverse
103 * all children and look for the first CDATA child. There can't be more than
104 * one because we only have one file attribute on the parent.
105 */
106 for (xmlNode *child = xml->children; child != NULL; child = child->next) {
107 FILE *stream = NULL;
108
109 if (child->type != XML_CDATA_SECTION_NODE) {
110 continue;
111 }
112
113 stream = fopen(path, "w+");
114 if (stream == NULL) {
115 crm_warn("Could not write schema file %s: %s", path, strerror(errno));
116 } else {
117 rc = fprintf(stream, "%s", child->content);
118
119 if (rc < 0) {
120 crm_warn("Could not write schema file %s: %s", path, strerror(errno));
121 }
122
123 fclose(stream);
124 }
125
126 break;
127 }
128
129 free(path);
130 }
131
132 static void
133 get_schema_files(void)
134 {
135 int rc = pcmk_rc_ok;
136 cib_t *cib = NULL;
137 xmlNode *reply;
138
139 cib = cib_new();
140 if (cib == NULL) {
141 pcmk_common_cleanup();
142 _exit(CRM_EX_OSERR);
143 }
144
145 rc = cib->cmds->signon(cib, crm_system_name, cib_query);
146 rc = pcmk_legacy2rc(rc);
147 if (rc != pcmk_rc_ok) {
148 crm_err("Could not connect to the CIB manager: %s", pcmk_rc_str(rc));
149 pcmk_common_cleanup();
150 _exit(pcmk_rc2exitc(rc));
151 }
152
153 rc = cib->cmds->fetch_schemas(cib, &reply, pcmk__highest_schema_name(),
154 cib_sync_call);
155 if (rc != pcmk_ok) {
156 crm_err("Could not get schema files: %s", pcmk_strerror(rc));
157 rc = pcmk_legacy2rc(rc);
158
159 } else if (reply->children != NULL) {
160 /* The returned document looks something like this:
161 * <cib_command>
162 * <cib_calldata>
163 * <schemas>
164 * <schema version="pacemaker-1.1">
165 * <file path="foo-1.1">
166 * </file>
167 * <file path="bar-1.1">
168 * </file>
169 * ...
170 * </schema>
171 * <schema version="pacemaker-1.2">
172 * </schema>
173 * ...
174 * </schemas>
175 * </cib_calldata>
176 * </cib_command>
177 *
178 * All the <schemas> and <schema> tags are really just there for organizing
179 * the XML a little better. What we really care about are the <file> nodes,
180 * and specifically the path attributes and the CDATA children (not shown)
181 * of each. We can use an xpath query to reach down and get all the <file>
182 * nodes at once.
183 *
184 * If we already have the latest schema version, or we asked for one later
185 * than what the cluster supports, we'll get back an empty <schemas> node,
186 * so all this will continue to work. It just won't do anything.
187 */
188 crm_foreach_xpath_result(reply, "//" PCMK_XA_FILE,
189 write_extra_schema_file, NULL);
190 }
191
192 pcmk__xml_free(reply);
193 cib__clean_up_connection(&cib);
194 pcmk_common_cleanup();
195 _exit(pcmk_rc2exitc(rc));
196 }
197
198 /* Load any additional schema files when the child is finished fetching and
199 * saving them to disk.
200 */
201 static void
202 get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode)
203 {
204 const char *errmsg = "Could not load additional schema files";
205
206 if ((signo == 0) && (exitcode == 0)) {
207 const char *remote_schema_dir = pcmk__remote_schema_dir();
208
209 /* Don't just call pcmk__schema_init() here because that will load the
210 * base schemas again too. Instead just load the things we fetched.
211 */
212 pcmk__load_schemas_from_dir(remote_schema_dir);
213 pcmk__sort_schemas();
214 crm_info("Fetching extra schema files completed successfully");
215
216 } else {
217 if (signo == 0) {
218 crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
219
220 } else {
221 crm_err("%s: process %d terminated with signal %d (%s)%s",
222 errmsg, (int) pid, signo, strsignal(signo),
223 (core? " and dumped core" : ""));
224 }
225
226 /* Clean up any incomplete schema data we might have been downloading
227 * when the process timed out or crashed. We don't need to do any extra
228 * cleanup because we never loaded the extra schemas, and we don't need
229 * to call pcmk__schema_init() because that was called in
230 * remoted_request_cib_schema_files() before this function.
231 */
232 clean_up_extra_schema_files();
233 }
234 }
235
236 void
237 remoted_request_cib_schema_files(void)
238 {
239 pid_t pid;
240 int rc;
241
242 /* If a previous schema-fetch process is still running when we're called
243 * again, it's hung. Attempt to kill it before cleaning up the extra
244 * directory.
245 */
246 if (schema_fetch_pid != 0) {
247 if (mainloop_child_kill(schema_fetch_pid) == FALSE) {
248 crm_warn("Unable to kill pre-existing schema-fetch process");
249 return;
250 }
251
252 schema_fetch_pid = 0;
253 }
254
255 /* Clean up any extra schema files we downloaded from a previous cluster
256 * connection. After the files are gone, we need to wipe them from
257 * known_schemas, but there's no opposite operation for add_schema().
258 *
259 * Instead, unload all the schemas. This means we'll also forget about all
260 * installed schemas as well, which means that pcmk__highest_schema_name()
261 * would fail. So we need to load the base schemas right now.
262 */
263 clean_up_extra_schema_files();
264 pcmk__schema_cleanup();
265 pcmk__schema_init();
266
267 crm_info("Fetching extra schema files from cluster");
268 pid = fork();
269
270 switch (pid) {
271 case -1: {
272 rc = errno;
273 crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc));
274 break;
275 }
276
277 case 0:
278 /* child */
279 get_schema_files();
280 break;
281
282 default:
283 /* parent */
284 schema_fetch_pid = pid;
285 mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL,
286 mainloop_leave_pid_group,
287 get_schema_files_complete);
288 break;
289 }
290 }
291