LCOV - code coverage report
Current view: top level - common - digest.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 97 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 11 0.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2015-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 <stdio.h>
      13             : #include <unistd.h>
      14             : #include <string.h>
      15             : #include <stdlib.h>
      16             : #include <md5.h>
      17             : 
      18             : #include <crm/crm.h>
      19             : #include <crm/common/xml.h>
      20             : #include "crmcommon_private.h"
      21             : 
      22             : #define BEST_EFFORT_STATUS 0
      23             : 
      24             : /*!
      25             :  * \internal
      26             :  * \brief Dump XML in a format used with v1 digests
      27             :  *
      28             :  * \param[in] xml  Root of XML to dump
      29             :  *
      30             :  * \return Newly allocated buffer containing dumped XML
      31             :  */
      32             : static GString *
      33           0 : dump_xml_for_digest(xmlNodePtr xml)
      34             : {
      35           0 :     GString *buffer = g_string_sized_new(1024);
      36             : 
      37             :     /* for compatibility with the old result which is used for v1 digests */
      38             :     g_string_append_c(buffer, ' ');
      39           0 :     pcmk__xml_string(xml, 0, buffer, 0);
      40             :     g_string_append_c(buffer, '\n');
      41             : 
      42           0 :     return buffer;
      43             : }
      44             : 
      45             : /*!
      46             :  * \brief Calculate and return v1 digest of XML tree
      47             :  *
      48             :  * \param[in] input Root of XML to digest
      49             :  * \param[in] sort Whether to sort the XML before calculating digest
      50             :  * \param[in] ignored Not used
      51             :  *
      52             :  * \return Newly allocated string containing digest
      53             :  * \note Example return value: "c048eae664dba840e1d2060f00299e9d"
      54             :  */
      55             : static char *
      56           0 : calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored)
      57             : {
      58           0 :     char *digest = NULL;
      59           0 :     GString *buffer = NULL;
      60           0 :     xmlNode *copy = NULL;
      61             : 
      62           0 :     if (sort) {
      63           0 :         crm_trace("Sorting xml...");
      64           0 :         copy = sorted_xml(input, NULL, TRUE);
      65           0 :         crm_trace("Done");
      66           0 :         input = copy;
      67             :     }
      68             : 
      69           0 :     buffer = dump_xml_for_digest(input);
      70           0 :     CRM_CHECK(buffer->len > 0, free_xml(copy);
      71             :               g_string_free(buffer, TRUE);
      72             :               return NULL);
      73             : 
      74           0 :     digest = crm_md5sum((const char *) buffer->str);
      75           0 :     crm_log_xml_trace(input, "digest:source");
      76             : 
      77           0 :     g_string_free(buffer, TRUE);
      78           0 :     free_xml(copy);
      79           0 :     return digest;
      80             : }
      81             : 
      82             : /*!
      83             :  * \brief Calculate and return v2 digest of XML tree
      84             :  *
      85             :  * \param[in] source Root of XML to digest
      86             :  * \param[in] do_filter Whether to filter certain XML attributes
      87             :  *
      88             :  * \return Newly allocated string containing digest
      89             :  */
      90             : static char *
      91           0 : calculate_xml_digest_v2(const xmlNode *source, gboolean do_filter)
      92             : {
      93           0 :     char *digest = NULL;
      94           0 :     GString *buf = g_string_sized_new(1024);
      95             : 
      96           0 :     crm_trace("Begin digest %s", do_filter?"filtered":"");
      97             : 
      98           0 :     pcmk__xml_string(source, (do_filter? pcmk__xml_fmt_filtered : 0), buf, 0);
      99           0 :     digest = crm_md5sum(buf->str);
     100             : 
     101           0 :     pcmk__if_tracing(
     102             :         {
     103             :             char *trace_file = crm_strdup_printf("%s/digest-%s",
     104             :                                                  pcmk__get_tmpdir(), digest);
     105             : 
     106             :             crm_trace("Saving %s.%s.%s to %s",
     107             :                       crm_element_value(source, PCMK_XA_ADMIN_EPOCH),
     108             :                       crm_element_value(source, PCMK_XA_EPOCH),
     109             :                       crm_element_value(source, PCMK_XA_NUM_UPDATES),
     110             :                       trace_file);
     111             :             save_xml_to_file(source, "digest input", trace_file);
     112             :             free(trace_file);
     113             :         },
     114             :         {}
     115             :     );
     116           0 :     crm_trace("End digest");
     117           0 :     g_string_free(buf, TRUE);
     118           0 :     return digest;
     119             : }
     120             : 
     121             : /*!
     122             :  * \brief Calculate and return digest of XML tree, suitable for storing on disk
     123             :  *
     124             :  * \param[in] input Root of XML to digest
     125             :  *
     126             :  * \return Newly allocated string containing digest
     127             :  */
     128             : char *
     129           0 : calculate_on_disk_digest(xmlNode *input)
     130             : {
     131             :     /* Always use the v1 format for on-disk digests
     132             :      * a) it's a compatibility nightmare
     133             :      * b) we only use this once at startup, all other
     134             :      *    invocations are in a separate child process
     135             :      */
     136           0 :     return calculate_xml_digest_v1(input, FALSE, FALSE);
     137             : }
     138             : 
     139             : /*!
     140             :  * \brief Calculate and return digest of XML operation
     141             :  *
     142             :  * \param[in] input    Root of XML to digest
     143             :  * \param[in] version  Unused
     144             :  *
     145             :  * \return Newly allocated string containing digest
     146             :  */
     147             : char *
     148           0 : calculate_operation_digest(xmlNode *input, const char *version)
     149             : {
     150             :     /* We still need the sorting for operation digests */
     151           0 :     return calculate_xml_digest_v1(input, TRUE, FALSE);
     152             : }
     153             : 
     154             : /*!
     155             :  * \brief Calculate and return digest of XML tree
     156             :  *
     157             :  * \param[in] input      Root of XML to digest
     158             :  * \param[in] sort       Whether to sort XML before calculating digest
     159             :  * \param[in] do_filter  Whether to filter certain XML attributes
     160             :  * \param[in] version    CRM feature set version (used to select v1/v2 digest)
     161             :  *
     162             :  * \return Newly allocated string containing digest
     163             :  */
     164             : char *
     165           0 : calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
     166             :                                gboolean do_filter, const char *version)
     167             : {
     168             :     /*
     169             :      * @COMPAT digests (on-disk or in diffs/patchsets) created <1.1.4;
     170             :      * removing this affects even full-restart upgrades from old versions
     171             :      *
     172             :      * The sorting associated with v1 digest creation accounted for 23% of
     173             :      * the CIB manager's CPU usage on the server. v2 drops this.
     174             :      *
     175             :      * The filtering accounts for an additional 2.5% and we may want to
     176             :      * remove it in future.
     177             :      *
     178             :      * v2 also uses the xmlBuffer contents directly to avoid additional copying
     179             :      */
     180           0 :     if (version == NULL || compare_version("3.0.5", version) > 0) {
     181           0 :         crm_trace("Using v1 digest algorithm for %s",
     182             :                   pcmk__s(version, "unknown feature set"));
     183           0 :         return calculate_xml_digest_v1(input, sort, do_filter);
     184             :     }
     185           0 :     crm_trace("Using v2 digest algorithm for %s",
     186             :               pcmk__s(version, "unknown feature set"));
     187           0 :     return calculate_xml_digest_v2(input, do_filter);
     188             : }
     189             : 
     190             : /*!
     191             :  * \internal
     192             :  * \brief Check whether calculated digest of given XML matches expected digest
     193             :  *
     194             :  * \param[in] input     Root of XML tree to digest
     195             :  * \param[in] expected  Expected digest in on-disk format
     196             :  *
     197             :  * \return true if digests match, false on mismatch or error
     198             :  */
     199             : bool
     200           0 : pcmk__verify_digest(xmlNode *input, const char *expected)
     201             : {
     202           0 :     char *calculated = NULL;
     203             :     bool passed;
     204             : 
     205           0 :     if (input != NULL) {
     206           0 :         calculated = calculate_on_disk_digest(input);
     207           0 :         if (calculated == NULL) {
     208           0 :             crm_perror(LOG_ERR, "Could not calculate digest for comparison");
     209           0 :             return false;
     210             :         }
     211             :     }
     212           0 :     passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
     213           0 :     if (passed) {
     214           0 :         crm_trace("Digest comparison passed: %s", calculated);
     215             :     } else {
     216           0 :         crm_err("Digest comparison failed: expected %s, calculated %s",
     217             :                 expected, calculated);
     218             :     }
     219           0 :     free(calculated);
     220           0 :     return passed;
     221             : }
     222             : 
     223             : /*!
     224             :  * \internal
     225             :  * \brief Check whether an XML attribute should be excluded from CIB digests
     226             :  *
     227             :  * \param[in] name  XML attribute name
     228             :  *
     229             :  * \return true if XML attribute should be excluded from CIB digest calculation
     230             :  */
     231             : bool
     232           0 : pcmk__xa_filterable(const char *name)
     233             : {
     234             :     static const char *filter[] = {
     235             :         PCMK_XA_CRM_DEBUG_ORIGIN,
     236             :         PCMK_XA_CIB_LAST_WRITTEN,
     237             :         PCMK_XA_UPDATE_ORIGIN,
     238             :         PCMK_XA_UPDATE_CLIENT,
     239             :         PCMK_XA_UPDATE_USER,
     240             :     };
     241             : 
     242           0 :     for (int i = 0; i < PCMK__NELEM(filter); i++) {
     243           0 :         if (strcmp(name, filter[i]) == 0) {
     244           0 :             return true;
     245             :         }
     246             :     }
     247           0 :     return false;
     248             : }
     249             : 
     250             : char *
     251           0 : crm_md5sum(const char *buffer)
     252             : {
     253           0 :     int lpc = 0, len = 0;
     254           0 :     char *digest = NULL;
     255             :     unsigned char raw_digest[MD5_DIGEST_SIZE];
     256             : 
     257           0 :     if (buffer == NULL) {
     258           0 :         buffer = "";
     259             :     }
     260           0 :     len = strlen(buffer);
     261             : 
     262           0 :     crm_trace("Beginning digest of %d bytes", len);
     263           0 :     digest = malloc(2 * MD5_DIGEST_SIZE + 1);
     264           0 :     if (digest) {
     265           0 :         md5_buffer(buffer, len, raw_digest);
     266           0 :         for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
     267           0 :             sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
     268             :         }
     269           0 :         digest[(2 * MD5_DIGEST_SIZE)] = 0;
     270           0 :         crm_trace("Digest %s.", digest);
     271             : 
     272             :     } else {
     273           0 :         crm_err("Could not create digest");
     274             :     }
     275           0 :     return digest;
     276             : }
     277             : 
     278             : // Return true if a is an attribute that should be filtered
     279             : static bool
     280           0 : should_filter_for_digest(xmlAttrPtr a, void *user_data)
     281             : {
     282           0 :     if (strncmp((const char *) a->name, CRM_META "_",
     283             :                 sizeof(CRM_META " ") - 1) == 0) {
     284           0 :         return true;
     285             :     }
     286           0 :     return pcmk__str_any_of((const char *) a->name,
     287             :                             PCMK_XA_ID,
     288             :                             PCMK_XA_CRM_FEATURE_SET,
     289             :                             PCMK__XA_OP_DIGEST,
     290             :                             PCMK__META_ON_NODE,
     291             :                             PCMK__META_ON_NODE_UUID,
     292             :                             "pcmk_external_ip",
     293             :                             NULL);
     294             : }
     295             : 
     296             : /*!
     297             :  * \internal
     298             :  * \brief Remove XML attributes not needed for operation digest
     299             :  *
     300             :  * \param[in,out] param_set  XML with operation parameters
     301             :  */
     302             : void
     303           0 : pcmk__filter_op_for_digest(xmlNode *param_set)
     304             : {
     305           0 :     char *key = NULL;
     306           0 :     char *timeout = NULL;
     307           0 :     guint interval_ms = 0;
     308             : 
     309           0 :     if (param_set == NULL) {
     310           0 :         return;
     311             :     }
     312             : 
     313             :     /* Timeout is useful for recurring operation digests, so grab it before
     314             :      * removing meta-attributes
     315             :      */
     316           0 :     key = crm_meta_name(PCMK_META_INTERVAL);
     317           0 :     if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
     318           0 :         interval_ms = 0;
     319             :     }
     320           0 :     free(key);
     321           0 :     key = NULL;
     322           0 :     if (interval_ms != 0) {
     323           0 :         key = crm_meta_name(PCMK_META_TIMEOUT);
     324           0 :         timeout = crm_element_value_copy(param_set, key);
     325             :     }
     326             : 
     327             :     // Remove all CRM_meta_* attributes and certain other attributes
     328           0 :     pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL);
     329             : 
     330             :     // Add timeout back for recurring operation digests
     331           0 :     if (timeout != NULL) {
     332           0 :         crm_xml_add(param_set, key, timeout);
     333             :     }
     334           0 :     free(timeout);
     335           0 :     free(key);
     336             : }

Generated by: LCOV version 1.14