root/lib/common/digest.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dump_xml_for_digest
  2. calculate_xml_digest_v1
  3. calculate_xml_digest_v2
  4. calculate_on_disk_digest
  5. calculate_operation_digest
  6. calculate_xml_versioned_digest
  7. pcmk__verify_digest
  8. pcmk__xa_filterable
  9. crm_md5sum
  10. should_filter_for_digest
  11. pcmk__filter_op_for_digest

   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 dump_xml_for_digest(xmlNodePtr xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  34 {
  35     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     pcmk__xml_string(xml, 0, buffer, 0);
  40     g_string_append_c(buffer, '\n');
  41 
  42     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 calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored)
     /* [previous][next][first][last][top][bottom][index][help] */
  57 {
  58     char *digest = NULL;
  59     GString *buffer = NULL;
  60     xmlNode *copy = NULL;
  61 
  62     if (sort) {
  63         crm_trace("Sorting xml...");
  64         copy = sorted_xml(input, NULL, TRUE);
  65         crm_trace("Done");
  66         input = copy;
  67     }
  68 
  69     buffer = dump_xml_for_digest(input);
  70     CRM_CHECK(buffer->len > 0, free_xml(copy);
  71               g_string_free(buffer, TRUE);
  72               return NULL);
  73 
  74     digest = crm_md5sum((const char *) buffer->str);
  75     crm_log_xml_trace(input, "digest:source");
  76 
  77     g_string_free(buffer, TRUE);
  78     free_xml(copy);
  79     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 calculate_xml_digest_v2(const xmlNode *source, gboolean do_filter)
     /* [previous][next][first][last][top][bottom][index][help] */
  92 {
  93     char *digest = NULL;
  94     GString *buf = g_string_sized_new(1024);
  95 
  96     crm_trace("Begin digest %s", do_filter?"filtered":"");
  97 
  98     pcmk__xml_string(source, (do_filter? pcmk__xml_fmt_filtered : 0), buf, 0);
  99     digest = crm_md5sum(buf->str);
 100 
 101     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     crm_trace("End digest");
 117     g_string_free(buf, TRUE);
 118     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 calculate_on_disk_digest(xmlNode *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 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     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 calculate_operation_digest(xmlNode *input, const char *version)
     /* [previous][next][first][last][top][bottom][index][help] */
 149 {
 150     /* We still need the sorting for operation digests */
 151     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 calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
     /* [previous][next][first][last][top][bottom][index][help] */
 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     if (version == NULL || compare_version("3.0.5", version) > 0) {
 181         crm_trace("Using v1 digest algorithm for %s",
 182                   pcmk__s(version, "unknown feature set"));
 183         return calculate_xml_digest_v1(input, sort, do_filter);
 184     }
 185     crm_trace("Using v2 digest algorithm for %s",
 186               pcmk__s(version, "unknown feature set"));
 187     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 pcmk__verify_digest(xmlNode *input, const char *expected)
     /* [previous][next][first][last][top][bottom][index][help] */
 201 {
 202     char *calculated = NULL;
 203     bool passed;
 204 
 205     if (input != NULL) {
 206         calculated = calculate_on_disk_digest(input);
 207         if (calculated == NULL) {
 208             crm_perror(LOG_ERR, "Could not calculate digest for comparison");
 209             return false;
 210         }
 211     }
 212     passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
 213     if (passed) {
 214         crm_trace("Digest comparison passed: %s", calculated);
 215     } else {
 216         crm_err("Digest comparison failed: expected %s, calculated %s",
 217                 expected, calculated);
 218     }
 219     free(calculated);
 220     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 pcmk__xa_filterable(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 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     for (int i = 0; i < PCMK__NELEM(filter); i++) {
 243         if (strcmp(name, filter[i]) == 0) {
 244             return true;
 245         }
 246     }
 247     return false;
 248 }
 249 
 250 char *
 251 crm_md5sum(const char *buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 252 {
 253     int lpc = 0, len = 0;
 254     char *digest = NULL;
 255     unsigned char raw_digest[MD5_DIGEST_SIZE];
 256 
 257     if (buffer == NULL) {
 258         buffer = "";
 259     }
 260     len = strlen(buffer);
 261 
 262     crm_trace("Beginning digest of %d bytes", len);
 263     digest = malloc(2 * MD5_DIGEST_SIZE + 1);
 264     if (digest) {
 265         md5_buffer(buffer, len, raw_digest);
 266         for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
 267             sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
 268         }
 269         digest[(2 * MD5_DIGEST_SIZE)] = 0;
 270         crm_trace("Digest %s.", digest);
 271 
 272     } else {
 273         crm_err("Could not create digest");
 274     }
 275     return digest;
 276 }
 277 
 278 // Return true if a is an attribute that should be filtered
 279 static bool
 280 should_filter_for_digest(xmlAttrPtr a, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 281 {
 282     if (strncmp((const char *) a->name, CRM_META "_",
 283                 sizeof(CRM_META " ") - 1) == 0) {
 284         return true;
 285     }
 286     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 pcmk__filter_op_for_digest(xmlNode *param_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 304 {
 305     char *key = NULL;
 306     char *timeout = NULL;
 307     guint interval_ms = 0;
 308 
 309     if (param_set == NULL) {
 310         return;
 311     }
 312 
 313     /* Timeout is useful for recurring operation digests, so grab it before
 314      * removing meta-attributes
 315      */
 316     key = crm_meta_name(PCMK_META_INTERVAL);
 317     if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
 318         interval_ms = 0;
 319     }
 320     free(key);
 321     key = NULL;
 322     if (interval_ms != 0) {
 323         key = crm_meta_name(PCMK_META_TIMEOUT);
 324         timeout = crm_element_value_copy(param_set, key);
 325     }
 326 
 327     // Remove all CRM_meta_* attributes and certain other attributes
 328     pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL);
 329 
 330     // Add timeout back for recurring operation digests
 331     if (timeout != NULL) {
 332         crm_xml_add(param_set, key, timeout);
 333     }
 334     free(timeout);
 335     free(key);
 336 }

/* [previous][next][first][last][top][bottom][index][help] */