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