1    	/*
2    	 * Copyright 2004-2023 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 <stdio.h>
13   	#include <string.h>
14   	#include <dirent.h>
15   	#include <errno.h>
16   	#include <sys/stat.h>
17   	#include <stdarg.h>
18   	
19   	#include <libxml/relaxng.h>
20   	#include <libxslt/xslt.h>
21   	#include <libxslt/transform.h>
22   	#include <libxslt/security.h>
23   	#include <libxslt/xsltutils.h>
24   	
25   	#include <crm/msg_xml.h>
26   	#include <crm/common/xml.h>
27   	#include <crm/common/xml_internal.h>  /* PCMK__XML_LOG_BASE */
28   	
29   	typedef struct {
30   	    unsigned char v[2];
31   	} schema_version_t;
32   	
33   	#define SCHEMA_ZERO { .v = { 0, 0 } }
34   	
35   	#define schema_scanf(s, prefix, version, suffix) \
36   	    sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
37   	
38   	#define schema_strdup_printf(prefix, version, suffix) \
39   	    crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
40   	
41   	typedef struct {
42   	    xmlRelaxNGPtr rng;
43   	    xmlRelaxNGValidCtxtPtr valid;
44   	    xmlRelaxNGParserCtxtPtr parser;
45   	} relaxng_ctx_cache_t;
46   	
47   	enum schema_validator_e {
48   	    schema_validator_none,
49   	    schema_validator_rng
50   	};
51   	
52   	struct schema_s {
53   	    char *name;
54   	    char *transform;
55   	    void *cache;
56   	    enum schema_validator_e validator;
57   	    int after_transform;
58   	    schema_version_t version;
59   	    char *transform_enter;
60   	    bool transform_onleave;
61   	};
62   	
63   	static struct schema_s *known_schemas = NULL;
64   	static int xml_schema_max = 0;
65   	static bool silent_logging = FALSE;
66   	
67   	static void
68   	xml_log(int priority, const char *fmt, ...)
69   	G_GNUC_PRINTF(2, 3);
70   	
71   	static void
72   	xml_log(int priority, const char *fmt, ...)
73   	{
74   	    va_list ap;
75   	
76   	    va_start(ap, fmt);
77   	    if (silent_logging == FALSE) {
78   	        /* XXX should not this enable dechunking as well? */
79   	        PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
80   	    }
81   	    va_end(ap);
82   	}
83   	
84   	static int
85   	xml_latest_schema_index(void)
86   	{
87   	    // @COMPAT: pacemaker-next is deprecated since 2.1.5
88   	    return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
89   	}
90   	
91   	static int
92   	xml_minimum_schema_index(void)
93   	{
94   	    static int best = 0;
95   	    if (best == 0) {
96   	        int lpc = 0;
97   	
98   	        best = xml_latest_schema_index();
99   	        for (lpc = best; lpc > 0; lpc--) {
100  	            if (known_schemas[lpc].version.v[0]
101  	                < known_schemas[best].version.v[0]) {
102  	                return best;
103  	            } else {
104  	                best = lpc;
105  	            }
106  	        }
107  	        best = xml_latest_schema_index();
108  	    }
109  	    return best;
110  	}
111  	
112  	const char *
113  	xml_latest_schema(void)
114  	{
115  	    return get_schema_name(xml_latest_schema_index());
116  	}
117  	
118  	static inline bool
119  	version_from_filename(const char *filename, schema_version_t *version)
120  	{
121  	    int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
122  	
123  	    return (rc == 2);
124  	}
125  	
126  	static int
127  	schema_filter(const struct dirent *a)
128  	{
129  	    int rc = 0;
130  	    schema_version_t version = SCHEMA_ZERO;
131  	
132  	    if (strstr(a->d_name, "pacemaker-") != a->d_name) {
133  	        /* crm_trace("%s - wrong prefix", a->d_name); */
134  	
135  	    } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
136  	        /* crm_trace("%s - wrong suffix", a->d_name); */
137  	
138  	    } else if (!version_from_filename(a->d_name, &version)) {
139  	        /* crm_trace("%s - wrong format", a->d_name); */
140  	
141  	    } else {
142  	        /* crm_debug("%s - candidate", a->d_name); */
143  	        rc = 1;
144  	    }
145  	
146  	    return rc;
147  	}
148  	
149  	static int
150  	schema_sort(const struct dirent **a, const struct dirent **b)
151  	{
152  	    schema_version_t a_version = SCHEMA_ZERO;
153  	    schema_version_t b_version = SCHEMA_ZERO;
154  	
155  	    if (!version_from_filename(a[0]->d_name, &a_version)
156  	        || !version_from_filename(b[0]->d_name, &b_version)) {
157  	        // Shouldn't be possible, but makes static analysis happy
158  	        return 0;
159  	    }
160  	
161  	    for (int i = 0; i < 2; ++i) {
162  	        if (a_version.v[i] < b_version.v[i]) {
163  	            return -1;
164  	        } else if (a_version.v[i] > b_version.v[i]) {
165  	            return 1;
166  	        }
167  	    }
168  	    return 0;
169  	}
170  	
171  	/*!
172  	 * \internal
173  	 * \brief Add given schema + auxiliary data to internal bookkeeping.
174  	 *
175  	 * \note When providing \p version, should not be called directly but
176  	 *       through \c add_schema_by_version.
177  	 */
178  	static void
179  	add_schema(enum schema_validator_e validator, const schema_version_t *version,
180  	           const char *name, const char *transform,
181  	           const char *transform_enter, bool transform_onleave,
182  	           int after_transform)
183  	{
184  	    int last = xml_schema_max;
185  	    bool have_version = FALSE;
186  	
187  	    xml_schema_max++;
188  	    known_schemas = pcmk__realloc(known_schemas,
189  	                                  xml_schema_max * sizeof(struct schema_s));
190  	    CRM_ASSERT(known_schemas != NULL);
191  	    memset(known_schemas+last, 0, sizeof(struct schema_s));
192  	    known_schemas[last].validator = validator;
193  	    known_schemas[last].after_transform = after_transform;
194  	
195  	    for (int i = 0; i < 2; ++i) {
196  	        known_schemas[last].version.v[i] = version->v[i];
197  	        if (version->v[i]) {
198  	            have_version = TRUE;
199  	        }
200  	    }
201  	    if (have_version) {
202  	        known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
203  	    } else {
204  	        CRM_ASSERT(name);
205  	        schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
206  	        known_schemas[last].name = strdup(name);
207  	    }
208  	
209  	    if (transform) {
210  	        known_schemas[last].transform = strdup(transform);
211  	    }
212  	    if (transform_enter) {
213  	        known_schemas[last].transform_enter = strdup(transform_enter);
214  	    }
215  	    known_schemas[last].transform_onleave = transform_onleave;
216  	    if (after_transform == 0) {
217  	        after_transform = xml_schema_max;  /* upgrade is a one-way */
218  	    }
219  	    known_schemas[last].after_transform = after_transform;
220  	
221  	    if (known_schemas[last].after_transform < 0) {
222  	        crm_debug("Added supported schema %d: %s",
223  	                  last, known_schemas[last].name);
224  	
225  	    } else if (known_schemas[last].transform) {
226  	        crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
227  	                  last, known_schemas[last].name,
228  	                  known_schemas[last].after_transform,
229  	                  known_schemas[last].transform);
230  	
231  	    } else {
232  	        crm_debug("Added supported schema %d: %s (upgrades to %d)",
233  	                  last, known_schemas[last].name,
234  	                  known_schemas[last].after_transform);
235  	    }
236  	}
237  	
238  	/*!
239  	 * \internal
240  	 * \brief Add version-specified schema + auxiliary data to internal bookkeeping.
241  	 * \return Standard Pacemaker return value (the only possible values are
242  	 * \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
243  	 *
244  	 * \note There's no reliance on the particular order of schemas entering here.
245  	 *
246  	 * \par A bit of theory
247  	 * We track 3 XSLT stylesheets that differ per usage:
248  	 * - "upgrade":
249  	 *   . sparsely spread over the sequence of all available schemas,
250  	 *     as they are only relevant when major version of the schema
251  	 *     is getting bumped -- in that case, it MUST be set
252  	 *   . name convention:  upgrade-X.Y.xsl
253  	 * - "upgrade-enter":
254  	 *   . may only accompany "upgrade" occurrence, but doesn't need to
255  	 *     be present anytime such one is, i.e., it MAY not be set when
256  	 *     "upgrade" is
257  	 *   . name convention:  upgrade-X.Y-enter.xsl,
258  	 *     when not present: upgrade-enter.xsl
259  	 * - "upgrade-leave":
260  	 *   . like "upgrade-enter", but SHOULD be present whenever
261  	 *     "upgrade-enter" is (and vice versa, but that's only
262  	 *     to prevent confusion based on observing the files,
263  	 *     it would get ignored regardless)
264  	 *   . name convention:  (see "upgrade-enter")
265  	 */
266  	static int
267  	add_schema_by_version(const schema_version_t *version, int next,
268  	                      bool transform_expected)
269  	{
270  	    bool transform_onleave = FALSE;
271  	    int rc = pcmk_rc_ok;
272  	    struct stat s;
273  	    char *xslt = NULL,
274  	         *transform_upgrade = NULL,
275  	         *transform_enter = NULL;
276  	
277  	    /* prologue for further transform_expected handling */
278  	    if (transform_expected) {
279  	        /* check if there's suitable "upgrade" stylesheet */
280  	        transform_upgrade = schema_strdup_printf("upgrade-", *version, );
281  	        xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
282  	                                       transform_upgrade);
283  	    }
284  	
285  	    if (!transform_expected) {
286  	        /* jump directly to the end */
287  	
288  	    } else if (stat(xslt, &s) == 0) {
289  	        /* perhaps there's also a targeted "upgrade-enter" stylesheet */
290  	        transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
291  	        free(xslt);
292  	        xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
293  	                                       transform_enter);
294  	        if (stat(xslt, &s) != 0) {
295  	            /* or initially, at least a generic one */
296  	            crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
297  	            free(xslt);
298  	            free(transform_enter);
299  	            transform_enter = strdup("upgrade-enter");
300  	            xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
301  	                                           transform_enter);
302  	            if (stat(xslt, &s) != 0) {
303  	                crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
304  	                free(xslt);
305  	                xslt = NULL;
306  	            }
307  	        }
308  	        /* xslt contains full path to "upgrade-enter" stylesheet */
309  	        if (xslt != NULL) {
310  	            /* then there should be "upgrade-leave" counterpart (enter->leave) */
311  	            memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
312  	            transform_onleave = (stat(xslt, &s) == 0);
313  	            free(xslt);
314  	        } else {
315  	            free(transform_enter);
316  	            transform_enter = NULL;
317  	        }
318  	
319  	    } else {
320  	        crm_err("Upgrade transform %s not found", xslt);
321  	        free(xslt);
322  	        free(transform_upgrade);
323  	        transform_upgrade = NULL;
324  	        next = -1;
325  	        rc = ENOENT;
326  	    }
327  	
328  	    add_schema(schema_validator_rng, version, NULL,
329  	               transform_upgrade, transform_enter, transform_onleave, next);
330  	
331  	    free(transform_upgrade);
332  	    free(transform_enter);
333  	
334  	    return rc;
335  	}
336  	
337  	static void
338  	wrap_libxslt(bool finalize)
339  	{
340  	    static xsltSecurityPrefsPtr secprefs;
341  	    int ret = 0;
342  	
343  	    /* security framework preferences */
344  	    if (!finalize) {
345  	        CRM_ASSERT(secprefs == NULL);
346  	        secprefs = xsltNewSecurityPrefs();
347  	        ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
348  	                                   xsltSecurityForbid)
349  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
350  	                                     xsltSecurityForbid)
351  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
352  	                                     xsltSecurityForbid)
353  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
354  	                                     xsltSecurityForbid);
355  	        if (ret != 0) {
356  	            return;
357  	        }
358  	    } else {
359  	        xsltFreeSecurityPrefs(secprefs);
360  	        secprefs = NULL;
361  	    }
362  	
363  	    /* cleanup only */
364  	    if (finalize) {
365  	        xsltCleanupGlobals();
366  	    }
367  	}
368  	
369  	/*!
370  	 * \internal
371  	 * \brief Load pacemaker schemas into cache
372  	 *
373  	 * \note This currently also serves as an entry point for the
374  	 *       generic initialization of the libxslt library.
375  	 */
376  	void
377  	crm_schema_init(void)
378  	{
379  	    int lpc, max;
380  	    char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
381  	    struct dirent **namelist = NULL;
382  	    const schema_version_t zero = SCHEMA_ZERO;
383  	
384  	    wrap_libxslt(false);
385  	
386  	    max = scandir(base, &namelist, schema_filter, schema_sort);
387  	    if (max < 0) {
388  	        crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
389  	        free(base);
390  	
391  	    } else {
392  	        free(base);
393  	        for (lpc = 0; lpc < max; lpc++) {
394  	            bool transform_expected = FALSE;
395  	            int next = 0;
396  	            schema_version_t version = SCHEMA_ZERO;
397  	
398  	            if (!version_from_filename(namelist[lpc]->d_name, &version)) {
399  	                // Shouldn't be possible, but makes static analysis happy
400  	                crm_err("Skipping schema '%s': could not parse version",
401  	                        namelist[lpc]->d_name);
402  	                continue;
403  	            }
404  	            if ((lpc + 1) < max) {
405  	                schema_version_t next_version = SCHEMA_ZERO;
406  	
407  	                if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
408  	                        && (version.v[0] < next_version.v[0])) {
409  	                    transform_expected = TRUE;
410  	                }
411  	
412  	            } else {
413  	                next = -1;
414  	            }
415  	            if (add_schema_by_version(&version, next, transform_expected)
416  	                    == ENOENT) {
417  	                break;
418  	            }
419  	        }
420  	
421  	        for (lpc = 0; lpc < max; lpc++) {
422  	            free(namelist[lpc]);
423  	        }
424  	        free(namelist);
425  	    }
426  	
427  	    // @COMPAT: Deprecated since 2.1.5
428  	    add_schema(schema_validator_rng, &zero, "pacemaker-next",
429  	               NULL, NULL, FALSE, -1);
430  	
431  	    add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
432  	               NULL, NULL, FALSE, -1);
433  	}
434  	
435  	static gboolean
436  	validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
437  	                      relaxng_ctx_cache_t **cached_ctx)
438  	{
439  	    int rc = 0;
440  	    gboolean valid = TRUE;
441  	    relaxng_ctx_cache_t *ctx = NULL;
442  	
443  	    CRM_CHECK(doc != NULL, return FALSE);
444  	    CRM_CHECK(relaxng_file != NULL, return FALSE);
445  	
446  	    if (cached_ctx && *cached_ctx) {
447  	        ctx = *cached_ctx;
448  	
449  	    } else {
450  	        crm_debug("Creating RNG parser context");
451  	        ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
452  	
453  	        ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
454  	        CRM_CHECK(ctx->parser != NULL, goto cleanup);
455  	
456  	        if (error_handler) {
457  	            xmlRelaxNGSetParserErrors(ctx->parser,
458  	                                      (xmlRelaxNGValidityErrorFunc) error_handler,
459  	                                      (xmlRelaxNGValidityWarningFunc) error_handler,
460  	                                      error_handler_context);
461  	        } else {
462  	            xmlRelaxNGSetParserErrors(ctx->parser,
463  	                                      (xmlRelaxNGValidityErrorFunc) fprintf,
464  	                                      (xmlRelaxNGValidityWarningFunc) fprintf,
465  	                                      stderr);
466  	        }
467  	
468  	        ctx->rng = xmlRelaxNGParse(ctx->parser);
469  	        CRM_CHECK(ctx->rng != NULL,
470  	                  crm_err("Could not find/parse %s", relaxng_file);
471  	                  goto cleanup);
472  	
473  	        ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
474  	        CRM_CHECK(ctx->valid != NULL, goto cleanup);
475  	
476  	        if (error_handler) {
477  	            xmlRelaxNGSetValidErrors(ctx->valid,
478  	                                     (xmlRelaxNGValidityErrorFunc) error_handler,
479  	                                     (xmlRelaxNGValidityWarningFunc) error_handler,
480  	                                     error_handler_context);
481  	        } else {
482  	            xmlRelaxNGSetValidErrors(ctx->valid,
483  	                                     (xmlRelaxNGValidityErrorFunc) fprintf,
484  	                                     (xmlRelaxNGValidityWarningFunc) fprintf,
485  	                                     stderr);
486  	        }
487  	    }
488  	
489  	    rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
490  	    if (rc > 0) {
491  	        valid = FALSE;
492  	
493  	    } else if (rc < 0) {
494  	        crm_err("Internal libxml error during validation");
495  	    }
496  	
497  	  cleanup:
498  	
499  	    if (cached_ctx) {
500  	        *cached_ctx = ctx;
501  	
502  	    } else {
503  	        if (ctx->parser != NULL) {
504  	            xmlRelaxNGFreeParserCtxt(ctx->parser);
505  	        }
506  	        if (ctx->valid != NULL) {
507  	            xmlRelaxNGFreeValidCtxt(ctx->valid);
508  	        }
509  	        if (ctx->rng != NULL) {
510  	            xmlRelaxNGFree(ctx->rng);
511  	        }
512  	        free(ctx);
513  	    }
514  	
515  	    return valid;
516  	}
517  	
518  	/*!
519  	 * \internal
520  	 * \brief Clean up global memory associated with XML schemas
521  	 */
522  	void
523  	crm_schema_cleanup(void)
524  	{
525  	    int lpc;
526  	    relaxng_ctx_cache_t *ctx = NULL;
527  	
528  	    for (lpc = 0; lpc < xml_schema_max; lpc++) {
529  	
530  	        switch (known_schemas[lpc].validator) {
531  	            case schema_validator_none: // not cached
532  	                break;
533  	            case schema_validator_rng: // cached
534  	                ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
535  	                if (ctx == NULL) {
536  	                    break;
537  	                }
538  	                if (ctx->parser != NULL) {
539  	                    xmlRelaxNGFreeParserCtxt(ctx->parser);
540  	                }
541  	                if (ctx->valid != NULL) {
542  	                    xmlRelaxNGFreeValidCtxt(ctx->valid);
543  	                }
544  	                if (ctx->rng != NULL) {
545  	                    xmlRelaxNGFree(ctx->rng);
546  	                }
547  	                free(ctx);
548  	                known_schemas[lpc].cache = NULL;
549  	                break;
550  	        }
551  	        free(known_schemas[lpc].name);
552  	        free(known_schemas[lpc].transform);
553  	        free(known_schemas[lpc].transform_enter);
554  	    }
555  	    free(known_schemas);
556  	    known_schemas = NULL;
557  	
558  	    wrap_libxslt(true);
559  	}
560  	
561  	static gboolean
562  	validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
563  	{
564  	    gboolean valid = FALSE;
565  	    char *file = NULL;
566  	    struct schema_s *schema = NULL;
567  	    relaxng_ctx_cache_t **cache = NULL;
568  	
569  	    if (method < 0) {
570  	        return FALSE;
571  	    }
572  	
573  	    schema = &(known_schemas[method]);
574  	    if (schema->validator == schema_validator_none) {
575  	        return TRUE;
576  	    }
577  	
578  	    if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
579  	        crm_warn("The pacemaker-next schema is deprecated and will be removed "
580  	                 "in a future release.");
581  	    }
582  	
583  	    file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
584  	                                   schema->name);
585  	
586  	    crm_trace("Validating with %s (type=%d)",
587  	              pcmk__s(file, "missing schema"), schema->validator);
588  	    switch (schema->validator) {
589  	        case schema_validator_rng:
590  	            cache = (relaxng_ctx_cache_t **) &(schema->cache);
591  	            valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
592  	            break;
593  	        default:
594  	            crm_err("Unknown validator type: %d",
595  	                    known_schemas[method].validator);
596  	            break;
597  	    }
598  	
599  	    free(file);
600  	    return valid;
601  	}
602  	
603  	static bool
604  	validate_with_silent(xmlNode *xml, int method)
605  	{
606  	    bool rc, sl_backup = silent_logging;
607  	    silent_logging = TRUE;
608  	    rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
609  	    silent_logging = sl_backup;
610  	    return rc;
611  	}
612  	
613  	static void
614  	dump_file(const char *filename)
615  	{
616  	
617  	    FILE *fp = NULL;
618  	    int ch, line = 0;
619  	
620  	    CRM_CHECK(filename != NULL, return);
621  	
622  	    fp = fopen(filename, "r");
623  	    if (fp == NULL) {
624  	        crm_perror(LOG_ERR, "Could not open %s for reading", filename);
625  	        return;
626  	    }
627  	
628  	    fprintf(stderr, "%4d ", ++line);
629  	    do {
630  	        ch = getc(fp);
631  	        if (ch == EOF) {
632  	            putc('\n', stderr);
633  	            break;
634  	        } else if (ch == '\n') {
635  	            fprintf(stderr, "\n%4d ", ++line);
636  	        } else {
637  	            putc(ch, stderr);
638  	        }
639  	    } while (1);
640  	
641  	    fclose(fp);
642  	}
643  	
644  	gboolean
645  	validate_xml_verbose(const xmlNode *xml_blob)
646  	{
647  	    int fd = 0;
648  	    xmlDoc *doc = NULL;
649  	    xmlNode *xml = NULL;
650  	    gboolean rc = FALSE;
651  	    char *filename = NULL;
652  	
653  	    filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
654  	
655  	    umask(S_IWGRP | S_IWOTH | S_IROTH);
656  	    fd = mkstemp(filename);
657  	    write_xml_fd(xml_blob, filename, fd, FALSE);
658  	
659  	    dump_file(filename);
660  	
661  	    doc = xmlReadFile(filename, NULL, 0);
662  	    xml = xmlDocGetRootElement(doc);
663  	    rc = validate_xml(xml, NULL, FALSE);
664  	    free_xml(xml);
665  	
666  	    unlink(filename);
667  	    free(filename);
668  	
669  	    return rc;
670  	}
671  	
672  	gboolean
673  	validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
674  	{
675  	    return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
676  	}
677  	
678  	gboolean
679  	pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
680  	{
681  	    int version = 0;
682  	
683  	    CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
684  	
685  	    if (validation == NULL) {
686  	        validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
687  	    }
688  	
689  	    if (validation == NULL) {
690  	        int lpc = 0;
691  	        bool valid = FALSE;
692  	
693  	        for (lpc = 0; lpc < xml_schema_max; lpc++) {
694  	            if (validate_with(xml_blob, lpc, NULL, NULL)) {
695  	                valid = TRUE;
696  	                crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
697  	                            known_schemas[lpc].name);
698  	                crm_info("XML validated against %s", known_schemas[lpc].name);
699  	                if(known_schemas[lpc].after_transform == 0) {
700  	                    break;
701  	                }
702  	            }
703  	        }
704  	
705  	        return valid;
706  	    }
707  	
708  	    version = get_schema_version(validation);
709  	    if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
710  	        return TRUE;
711  	    } else if (version < xml_schema_max) {
712  	        return validate_with(xml_blob, version, error_handler, error_handler_context);
713  	    }
714  	
715  	    crm_err("Unknown validator: %s", validation);
716  	    return FALSE;
717  	}
718  	
719  	static void
720  	cib_upgrade_err(void *ctx, const char *fmt, ...)
721  	G_GNUC_PRINTF(2, 3);
722  	
723  	/* With this arrangement, an attempt to identify the message severity
724  	   as explicitly signalled directly from XSLT is performed in rather
725  	   a smart way (no reliance on formatting string + arguments being
726  	   always specified as ["%s", purposeful_string], as it can also be
727  	   ["%s: %s", some_prefix, purposeful_string] etc. so every argument
728  	   pertaining %s specifier is investigated), and if such a mark found,
729  	   the respective level is determined and, when the messages are to go
730  	   to the native logs, the mark itself gets dropped
731  	   (by the means of string shift).
732  	
733  	   NOTE: whether the native logging is the right sink is decided per
734  	         the ctx parameter -- NULL denotes this case, otherwise it
735  	         carries a pointer to the numeric expression of the desired
736  	         target logging level (messages with higher level will be
737  	         suppressed)
738  	
739  	   NOTE: on some architectures, this string shift may not have any
740  	         effect, but that's an acceptable tradeoff
741  	
742  	   The logging level for not explicitly designated messages
743  	   (suspicious, likely internal errors or some runaways) is
744  	   LOG_WARNING.
745  	 */
746  	static void
747  	cib_upgrade_err(void *ctx, const char *fmt, ...)
748  	{
749  	    va_list ap, aq;
750  	    char *arg_cur;
751  	
752  	    bool found = FALSE;
753  	    const char *fmt_iter = fmt;
754  	    uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
755  	    const unsigned * log_level = (const unsigned *) ctx;
756  	    enum {
757  	        escan_seennothing,
758  	        escan_seenpercent,
759  	    } scan_state = escan_seennothing;
760  	
761  	    va_start(ap, fmt);
762  	    va_copy(aq, ap);
763  	
764  	    while (!found && *fmt_iter != '\0') {
765  	        /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
766  	        switch (*fmt_iter++) {
767  	        case '%':
768  	            if (scan_state == escan_seennothing) {
769  	                scan_state = escan_seenpercent;
770  	            } else if (scan_state == escan_seenpercent) {
771  	                scan_state = escan_seennothing;
772  	            }
773  	            break;
774  	        case 's':
775  	            if (scan_state == escan_seenpercent) {
776  	                scan_state = escan_seennothing;
777  	                arg_cur = va_arg(aq, char *);
778  	                if (arg_cur != NULL) {
779  	                    switch (arg_cur[0]) {
780  	                    case 'W':
781  	                        if (!strncmp(arg_cur, "WARNING: ",
782  	                                     sizeof("WARNING: ") - 1)) {
783  	                            msg_log_level = LOG_WARNING;
784  	                        }
785  	                        if (ctx == NULL) {
786  	                            memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
787  	                                    strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
788  	                        }
789  	                        found = TRUE;
790  	                        break;
791  	                    case 'I':
792  	                        if (!strncmp(arg_cur, "INFO: ",
793  	                                     sizeof("INFO: ") - 1)) {
794  	                            msg_log_level = LOG_INFO;
795  	                        }
796  	                        if (ctx == NULL) {
797  	                            memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
798  	                                    strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
799  	                        }
800  	                        found = TRUE;
801  	                        break;
802  	                    case 'D':
803  	                        if (!strncmp(arg_cur, "DEBUG: ",
804  	                                     sizeof("DEBUG: ") - 1)) {
805  	                            msg_log_level = LOG_DEBUG;
806  	                        }
807  	                        if (ctx == NULL) {
808  	                            memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
809  	                                    strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
810  	                        }
811  	                        found = TRUE;
812  	                        break;
813  	                    }
814  	                }
815  	            }
816  	            break;
817  	        case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
818  	        case '0': case '1': case '2': case '3': case '4':
819  	        case '5': case '6': case '7': case '8': case '9':
820  	        case '*':
821  	            break;
822  	        case 'l':
823  	        case 'z':
824  	        case 't':
825  	        case 'j':
826  	        case 'd': case 'i':
827  	        case 'o':
828  	        case 'u':
829  	        case 'x': case 'X':
830  	        case 'e': case 'E':
831  	        case 'f': case 'F':
832  	        case 'g': case 'G':
833  	        case 'a': case 'A':
834  	        case 'c':
835  	        case 'p':
836  	            if (scan_state == escan_seenpercent) {
837  	                (void) va_arg(aq, void *);  /* skip forward */
838  	                scan_state = escan_seennothing;
839  	            }
840  	            break;
841  	        default:
842  	            scan_state = escan_seennothing;
843  	            break;
844  	        }
845  	    }
846  	
847  	    if (log_level != NULL) {
848  	        /* intention of the following offset is:
849  	           cibadmin -V -> start showing INFO labelled messages */
850  	        if (*log_level + 4 >= msg_log_level) {
851  	            vfprintf(stderr, fmt, ap);
852  	        }
853  	    } else {
854  	        PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
855  	    }
856  	
857  	    va_end(aq);
858  	    va_end(ap);
859  	}
860  	
861  	static xmlNode *
862  	apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
863  	{
864  	    char *xform = NULL;
865  	    xmlNode *out = NULL;
866  	    xmlDocPtr res = NULL;
867  	    xsltStylesheet *xslt = NULL;
868  	
869  	    xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
870  	                                    transform);
871  	
872  	    /* for capturing, e.g., what's emitted via <xsl:message> */
873  	    if (to_logs) {
874  	        xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
875  	    } else {
876  	        xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
877  	    }
878  	
879  	    xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
880  	    CRM_CHECK(xslt != NULL, goto cleanup);
881  	
882  	    res = xsltApplyStylesheet(xslt, xml->doc, NULL);
883  	    CRM_CHECK(res != NULL, goto cleanup);
884  	
885  	    xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
886  	
887  	    out = xmlDocGetRootElement(res);
888  	
889  	  cleanup:
890  	    if (xslt) {
891  	        xsltFreeStylesheet(xslt);
892  	    }
893  	
894  	    free(xform);
895  	
896  	    return out;
897  	}
898  	
899  	/*!
900  	 * \internal
901  	 * \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
902  	 *
903  	 * \note Only emits warnings about enter/leave phases in case of issues.
904  	 */
905  	static xmlNode *
906  	apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
907  	{
908  	    bool transform_onleave = schema->transform_onleave;
909  	    char *transform_leave;
910  	    xmlNode *upgrade = NULL,
911  	            *final = NULL;
912  	
(1) Event path: Condition "schema->transform_enter", taking true branch.
913  	    if (schema->transform_enter) {
(2) Event path: Switch case default.
(3) Event path: Condition "trace_cs == NULL", taking true branch.
(4) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(5) Event path: Breaking from switch.
914  	        crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
915  	                  schema->name, schema->transform_enter);
916  	        upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
(6) Event path: Condition "upgrade == NULL", taking false branch.
917  	        if (upgrade == NULL) {
918  	            crm_warn("Upgrade-enter transformation %s.xsl failed",
919  	                     schema->transform_enter);
920  	            transform_onleave = FALSE;
921  	        }
922  	    }
(7) Event path: Condition "upgrade == NULL", taking false branch.
923  	    if (upgrade == NULL) {
924  	        upgrade = xml;
925  	    }
926  	
(8) Event path: Switch case default.
(9) Event path: Condition "trace_cs == NULL", taking true branch.
(10) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(11) Event path: Breaking from switch.
927  	    crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
928  	              schema->name, schema->transform);
929  	    final = apply_transformation(upgrade, schema->transform, to_logs);
(12) Event path: Condition "upgrade != xml", taking true branch.
930  	    if (upgrade != xml) {
931  	        free_xml(upgrade);
932  	        upgrade = NULL;
933  	    }
934  	
(13) Event path: Condition "final != NULL", taking true branch.
(14) Event path: Condition "transform_onleave", taking true branch.
935  	    if (final != NULL && transform_onleave) {
936  	        upgrade = final;
937  	        /* following condition ensured in add_schema_by_version */
(15) Event path: Condition "!(schema->transform_enter != NULL)", taking false branch.
938  	        CRM_ASSERT(schema->transform_enter != NULL);
939  	        transform_leave = strdup(schema->transform_enter);
940  	        /* enter -> leave */
CID (unavailable; MK=9a76472bef8604239b67b7e3f3437d48) (#1 of 1): Dereference null return value (NULL_RETURNS):
(16) Event returned_null: "strrchr" returns "NULL" (checked 12 out of 14 times).
(17) Event dereference: Passing null pointer "strrchr(transform_leave, 45)" to "memcpy", which dereferences it.
Also see events: [example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked][example_assign][example_checked]
941  	        memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
942  	        crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
943  	                  schema->name, transform_leave);
944  	        final = apply_transformation(upgrade, transform_leave, to_logs);
945  	        if (final == NULL) {
946  	            crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
947  	            final = upgrade;
948  	        } else {
949  	            free_xml(upgrade);
950  	        }
951  	        free(transform_leave);
952  	    }
953  	
954  	    return final;
955  	}
956  	
957  	const char *
958  	get_schema_name(int version)
959  	{
960  	    if (version < 0 || version >= xml_schema_max) {
961  	        return "unknown";
962  	    }
963  	    return known_schemas[version].name;
964  	}
965  	
966  	int
967  	get_schema_version(const char *name)
968  	{
969  	    int lpc = 0;
970  	
971  	    if (name == NULL) {
972  	        name = PCMK__VALUE_NONE;
973  	    }
974  	    for (; lpc < xml_schema_max; lpc++) {
975  	        if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
976  	            return lpc;
977  	        }
978  	    }
979  	    return -1;
980  	}
981  	
982  	/* set which validation to use */
983  	int
984  	update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
985  	                  gboolean to_logs)
986  	{
987  	    xmlNode *xml = NULL;
988  	    char *value = NULL;
989  	    int max_stable_schemas = xml_latest_schema_index();
990  	    int lpc = 0, match = -1, rc = pcmk_ok;
991  	    int next = -1;  /* -1 denotes "inactive" value */
992  	    xmlRelaxNGValidityErrorFunc error_handler = 
993  	        to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
994  	
995  	    CRM_CHECK(best != NULL, return -EINVAL);
996  	    *best = 0;
997  	
998  	    CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
999  	              && ((*xml_blob)->doc != NULL),
1000 	              return -EINVAL);
1001 	
1002 	    xml = *xml_blob;
1003 	    value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
1004 	
1005 	    if (value != NULL) {
1006 	        match = get_schema_version(value);
1007 	
1008 	        lpc = match;
1009 	        if (lpc >= 0 && transform == FALSE) {
1010 	            *best = lpc++;
1011 	
1012 	        } else if (lpc < 0) {
1013 	            crm_debug("Unknown validation schema");
1014 	            lpc = 0;
1015 	        }
1016 	    }
1017 	
1018 	    if (match >= max_stable_schemas) {
1019 	        /* nothing to do */
1020 	        free(value);
1021 	        *best = match;
1022 	        return pcmk_ok;
1023 	    }
1024 	
1025 	    while (lpc <= max_stable_schemas) {
1026 	        crm_debug("Testing '%s' validation (%d of %d)",
1027 	                  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1028 	                  lpc, max_stable_schemas);
1029 	
1030 	        if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
1031 	            if (next != -1) {
1032 	                crm_info("Configuration not valid for schema: %s",
1033 	                         known_schemas[lpc].name);
1034 	                next = -1;
1035 	            } else {
1036 	                crm_trace("%s validation failed",
1037 	                          known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1038 	            }
1039 	            if (*best) {
1040 	                /* we've satisfied the validation, no need to check further */
1041 	                break;
1042 	            }
1043 	            rc = -pcmk_err_schema_validation;
1044 	
1045 	        } else {
1046 	            if (next != -1) {
1047 	                crm_debug("Configuration valid for schema: %s",
1048 	                          known_schemas[next].name);
1049 	                next = -1;
1050 	            }
1051 	            rc = pcmk_ok;
1052 	        }
1053 	
1054 	        if (rc == pcmk_ok) {
1055 	            *best = lpc;
1056 	        }
1057 	
1058 	        if (rc == pcmk_ok && transform) {
1059 	            xmlNode *upgrade = NULL;
1060 	            next = known_schemas[lpc].after_transform;
1061 	
1062 	            if (next <= lpc) {
1063 	                /* There is no next version, or next would regress */
1064 	                crm_trace("Stopping at %s", known_schemas[lpc].name);
1065 	                break;
1066 	
1067 	            } else if (max > 0 && (lpc == max || next > max)) {
1068 	                crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1069 	                          known_schemas[lpc].name, lpc, next, max);
1070 	                break;
1071 	
1072 	            } else if (known_schemas[lpc].transform == NULL
1073 	                       /* possibly avoid transforming when readily valid
1074 	                          (in general more restricted when crossing the major
1075 	                          version boundary, as X.0 "transitional" version is
1076 	                          expected to be more strict than it's successors that
1077 	                          may re-allow constructs from previous major line) */
1078 	                       || validate_with_silent(xml, next)) {
1079 	                crm_debug("%s-style configuration is also valid for %s",
1080 	                           known_schemas[lpc].name, known_schemas[next].name);
1081 	
1082 	                lpc = next;
1083 	
1084 	            } else {
1085 	                crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1086 	                           known_schemas[lpc].name, known_schemas[next].name,
1087 	                           known_schemas[lpc].transform);
1088 	
1089 	                upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1090 	                if (upgrade == NULL) {
1091 	                    crm_err("Transformation %s.xsl failed",
1092 	                            known_schemas[lpc].transform);
1093 	                    rc = -pcmk_err_transform_failed;
1094 	
1095 	                } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) {
1096 	                    crm_info("Transformation %s.xsl successful",
1097 	                             known_schemas[lpc].transform);
1098 	                    lpc = next;
1099 	                    *best = next;
1100 	                    free_xml(xml);
1101 	                    xml = upgrade;
1102 	                    rc = pcmk_ok;
1103 	
1104 	                } else {
1105 	                    crm_err("Transformation %s.xsl did not produce a valid configuration",
1106 	                            known_schemas[lpc].transform);
1107 	                    crm_log_xml_info(upgrade, "transform:bad");
1108 	                    free_xml(upgrade);
1109 	                    rc = -pcmk_err_schema_validation;
1110 	                }
1111 	                next = -1;
1112 	            }
1113 	        }
1114 	
1115 	        if (transform == FALSE || rc != pcmk_ok) {
1116 	            /* we need some progress! */
1117 	            lpc++;
1118 	        }
1119 	    }
1120 	
1121 	    if (*best > match && *best) {
1122 	        crm_info("%s the configuration from %s to %s",
1123 	                   transform?"Transformed":"Upgraded",
1124 	                   value ? value : "<none>", known_schemas[*best].name);
1125 	        crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1126 	    }
1127 	
1128 	    *xml_blob = xml;
1129 	    free(value);
1130 	    return rc;
1131 	}
1132 	
1133 	gboolean
1134 	cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1135 	{
1136 	    gboolean rc = TRUE;
1137 	    const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1138 	    char *const orig_value = strdup(value == NULL ? "(none)" : value);
1139 	
1140 	    int version = get_schema_version(value);
1141 	    int orig_version = version;
1142 	    int min_version = xml_minimum_schema_index();
1143 	
1144 	    if (version < min_version) {
1145 	        // Current configuration schema is not acceptable, try to update
1146 	        xmlNode *converted = NULL;
1147 	
1148 	        converted = copy_xml(*xml);
1149 	        update_validation(&converted, &version, 0, TRUE, to_logs);
1150 	
1151 	        value = crm_element_value(converted, XML_ATTR_VALIDATION);
1152 	        if (version < min_version) {
1153 	            // Updated configuration schema is still not acceptable
1154 	
1155 	            if (version < orig_version || orig_version == -1) {
1156 	                // We couldn't validate any schema at all
1157 	                if (to_logs) {
1158 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1159 	                                     "schema %s) to at least %s because it "
1160 	                                     "does not validate with any schema from "
1161 	                                     "%s to %s",
1162 	                                     orig_value,
1163 	                                     get_schema_name(min_version),
1164 	                                     get_schema_name(orig_version),
1165 	                                     xml_latest_schema());
1166 	                } else {
1167 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1168 	                                    "schema %s) to at least %s because it "
1169 	                                    "does not validate with any schema from "
1170 	                                    "%s to %s\n",
1171 	                                    orig_value,
1172 	                                    get_schema_name(min_version),
1173 	                                    get_schema_name(orig_version),
1174 	                                    xml_latest_schema());
1175 	                }
1176 	            } else {
1177 	                // We updated configuration successfully, but still too low
1178 	                if (to_logs) {
1179 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1180 	                                     "schema %s) to at least %s because it "
1181 	                                     "would not upgrade past %s",
1182 	                                     orig_value,
1183 	                                     get_schema_name(min_version),
1184 	                                     pcmk__s(value, "unspecified version"));
1185 	                } else {
1186 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1187 	                                    "schema %s) to at least %s because it "
1188 	                                    "would not upgrade past %s\n",
1189 	                                    orig_value,
1190 	                                    get_schema_name(min_version),
1191 	                                    pcmk__s(value, "unspecified version"));
1192 	                }
1193 	            }
1194 	
1195 	            free_xml(converted);
1196 	            converted = NULL;
1197 	            rc = FALSE;
1198 	
1199 	        } else {
1200 	            // Updated configuration schema is acceptable
1201 	            free_xml(*xml);
1202 	            *xml = converted;
1203 	
1204 	            if (version < xml_latest_schema_index()) {
1205 	                if (to_logs) {
1206 	                    pcmk__config_warn("Configuration with schema %s was "
1207 	                                      "internally upgraded to acceptable (but "
1208 	                                      "not most recent) %s",
1209 	                                      orig_value, get_schema_name(version));
1210 	                }
1211 	            } else {
1212 	                if (to_logs) {
1213 	                    crm_info("Configuration with schema %s was internally "
1214 	                             "upgraded to latest version %s",
1215 	                             orig_value, get_schema_name(version));
1216 	                }
1217 	            }
1218 	        }
1219 	
1220 	    } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
1221 	        // Schema validation is disabled
1222 	        if (to_logs) {
1223 	            pcmk__config_warn("Schema validation of configuration is disabled "
1224 	                              "(enabling is encouraged and prevents common "
1225 	                              "misconfigurations)");
1226 	
1227 	        } else {
1228 	            fprintf(stderr, "Schema validation of configuration is disabled "
1229 	                            "(enabling is encouraged and prevents common "
1230 	                            "misconfigurations)\n");
1231 	        }
1232 	    }
1233 	
1234 	    if (best_version) {
1235 	        *best_version = version;
1236 	    }
1237 	
1238 	    free(orig_value);
1239 	    return rc;
1240 	}
1241