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

   1 /*
   2  * Copyright 2015-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 <unistd.h>
  14 #include <string.h>
  15 #include <stdlib.h>
  16 #include <md5.h>
  17 
  18 #include <crm/crm.h>
  19 #include <crm/msg_xml.h>
  20 #include <crm/common/xml.h>
  21 #include "crmcommon_private.h"
  22 
  23 #define BEST_EFFORT_STATUS 0
  24 
  25 /*!
  26  * \internal
  27  * \brief Dump XML in a format used with v1 digests
  28  *
  29  * \param[in] xml  Root of XML to dump
  30  *
  31  * \return Newly allocated buffer containing dumped XML
  32  */
  33 static GString *
  34 dump_xml_for_digest(xmlNodePtr xml)
     /* [previous][next][first][last][top][bottom][index][help] */
  35 {
  36     GString *buffer = g_string_sized_new(1024);
  37 
  38     /* for compatibility with the old result which is used for v1 digests */
  39     g_string_append_c(buffer, ' ');
  40     pcmk__xml2text(xml, 0, buffer, 0);
  41     g_string_append_c(buffer, '\n');
  42 
  43     return buffer;
  44 }
  45 
  46 /*!
  47  * \brief Calculate and return v1 digest of XML tree
  48  *
  49  * \param[in] input Root of XML to digest
  50  * \param[in] sort Whether to sort the XML before calculating digest
  51  * \param[in] ignored Not used
  52  *
  53  * \return Newly allocated string containing digest
  54  * \note Example return value: "c048eae664dba840e1d2060f00299e9d"
  55  */
  56 static char *
  57 calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored)
     /* [previous][next][first][last][top][bottom][index][help] */
  58 {
  59     char *digest = NULL;
  60     GString *buffer = NULL;
  61     xmlNode *copy = NULL;
  62 
  63     if (sort) {
  64         crm_trace("Sorting xml...");
  65         copy = sorted_xml(input, NULL, TRUE);
  66         crm_trace("Done");
  67         input = copy;
  68     }
  69 
  70     buffer = dump_xml_for_digest(input);
  71     CRM_CHECK(buffer->len > 0, free_xml(copy);
  72               g_string_free(buffer, TRUE);
  73               return NULL);
  74 
  75     digest = crm_md5sum((const char *) buffer->str);
  76     crm_log_xml_trace(input, "digest:source");
  77 
  78     g_string_free(buffer, TRUE);
  79     free_xml(copy);
  80     return digest;
  81 }
  82 
  83 /*!
  84  * \brief Calculate and return v2 digest of XML tree
  85  *
  86  * \param[in] source Root of XML to digest
  87  * \param[in] do_filter Whether to filter certain XML attributes
  88  *
  89  * \return Newly allocated string containing digest
  90  */
  91 static char *
  92 calculate_xml_digest_v2(const xmlNode *source, gboolean do_filter)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     char *digest = NULL;
  95     GString *buffer = g_string_sized_new(1024);
  96 
  97     crm_trace("Begin digest %s", do_filter?"filtered":"");
  98     pcmk__xml2text(source, (do_filter? pcmk__xml_fmt_filtered : 0), buffer, 0);
  99 
 100     CRM_ASSERT(buffer != NULL);
 101     digest = crm_md5sum((const char *) buffer->str);
 102 
 103     pcmk__if_tracing(
 104         {
 105             char *trace_file = crm_strdup_printf("%s/digest-%s",
 106                                                  pcmk__get_tmpdir(), digest);
 107 
 108             crm_trace("Saving %s.%s.%s to %s",
 109                       crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
 110                       crm_element_value(source, XML_ATTR_GENERATION),
 111                       crm_element_value(source, XML_ATTR_NUMUPDATES),
 112                       trace_file);
 113             save_xml_to_file(source, "digest input", trace_file);
 114             free(trace_file);
 115         },
 116         {}
 117     );
 118     g_string_free(buffer, TRUE);
 119     crm_trace("End digest");
 120     return digest;
 121 }
 122 
 123 /*!
 124  * \brief Calculate and return digest of XML tree, suitable for storing on disk
 125  *
 126  * \param[in] input Root of XML to digest
 127  *
 128  * \return Newly allocated string containing digest
 129  */
 130 char *
 131 calculate_on_disk_digest(xmlNode *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 132 {
 133     /* Always use the v1 format for on-disk digests
 134      * a) it's a compatibility nightmare
 135      * b) we only use this once at startup, all other
 136      *    invocations are in a separate child process
 137      */
 138     return calculate_xml_digest_v1(input, FALSE, FALSE);
 139 }
 140 
 141 /*!
 142  * \brief Calculate and return digest of XML operation
 143  *
 144  * \param[in] input    Root of XML to digest
 145  * \param[in] version  Unused
 146  *
 147  * \return Newly allocated string containing digest
 148  */
 149 char *
 150 calculate_operation_digest(xmlNode *input, const char *version)
     /* [previous][next][first][last][top][bottom][index][help] */
 151 {
 152     /* We still need the sorting for operation digests */
 153     return calculate_xml_digest_v1(input, TRUE, FALSE);
 154 }
 155 
 156 /*!
 157  * \brief Calculate and return digest of XML tree
 158  *
 159  * \param[in] input      Root of XML to digest
 160  * \param[in] sort       Whether to sort XML before calculating digest
 161  * \param[in] do_filter  Whether to filter certain XML attributes
 162  * \param[in] version    CRM feature set version (used to select v1/v2 digest)
 163  *
 164  * \return Newly allocated string containing digest
 165  */
 166 char *
 167 calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
     /* [previous][next][first][last][top][bottom][index][help] */
 168                                gboolean do_filter, const char *version)
 169 {
 170     /*
 171      * @COMPAT digests (on-disk or in diffs/patchsets) created <1.1.4;
 172      * removing this affects even full-restart upgrades from old versions
 173      *
 174      * The sorting associated with v1 digest creation accounted for 23% of
 175      * the CIB manager's CPU usage on the server. v2 drops this.
 176      *
 177      * The filtering accounts for an additional 2.5% and we may want to
 178      * remove it in future.
 179      *
 180      * v2 also uses the xmlBuffer contents directly to avoid additional copying
 181      */
 182     if (version == NULL || compare_version("3.0.5", version) > 0) {
 183         crm_trace("Using v1 digest algorithm for %s",
 184                   pcmk__s(version, "unknown feature set"));
 185         return calculate_xml_digest_v1(input, sort, do_filter);
 186     }
 187     crm_trace("Using v2 digest algorithm for %s",
 188               pcmk__s(version, "unknown feature set"));
 189     return calculate_xml_digest_v2(input, do_filter);
 190 }
 191 
 192 /*!
 193  * \internal
 194  * \brief Check whether calculated digest of given XML matches expected digest
 195  *
 196  * \param[in] input     Root of XML tree to digest
 197  * \param[in] expected  Expected digest in on-disk format
 198  *
 199  * \return true if digests match, false on mismatch or error
 200  */
 201 bool
 202 pcmk__verify_digest(xmlNode *input, const char *expected)
     /* [previous][next][first][last][top][bottom][index][help] */
 203 {
 204     char *calculated = NULL;
 205     bool passed;
 206 
 207     if (input != NULL) {
 208         calculated = calculate_on_disk_digest(input);
 209         if (calculated == NULL) {
 210             crm_perror(LOG_ERR, "Could not calculate digest for comparison");
 211             return false;
 212         }
 213     }
 214     passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
 215     if (passed) {
 216         crm_trace("Digest comparison passed: %s", calculated);
 217     } else {
 218         crm_err("Digest comparison failed: expected %s, calculated %s",
 219                 expected, calculated);
 220     }
 221     free(calculated);
 222     return passed;
 223 }
 224 
 225 /*!
 226  * \internal
 227  * \brief Check whether an XML attribute should be excluded from CIB digests
 228  *
 229  * \param[in] name  XML attribute name
 230  *
 231  * \return true if XML attribute should be excluded from CIB digest calculation
 232  */
 233 bool
 234 pcmk__xa_filterable(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 235 {
 236     static const char *filter[] = {
 237         XML_ATTR_ORIGIN,
 238         XML_CIB_ATTR_WRITTEN,
 239         XML_ATTR_UPDATE_ORIG,
 240         XML_ATTR_UPDATE_CLIENT,
 241         XML_ATTR_UPDATE_USER,
 242     };
 243 
 244     for (int i = 0; i < PCMK__NELEM(filter); i++) {
 245         if (strcmp(name, filter[i]) == 0) {
 246             return true;
 247         }
 248     }
 249     return false;
 250 }
 251 
 252 char *
 253 crm_md5sum(const char *buffer)
     /* [previous][next][first][last][top][bottom][index][help] */
 254 {
 255     int lpc = 0, len = 0;
 256     char *digest = NULL;
 257     unsigned char raw_digest[MD5_DIGEST_SIZE];
 258 
 259     if (buffer == NULL) {
 260         buffer = "";
 261     }
 262     len = strlen(buffer);
 263 
 264     crm_trace("Beginning digest of %d bytes", len);
 265     digest = malloc(2 * MD5_DIGEST_SIZE + 1);
 266     if (digest) {
 267         md5_buffer(buffer, len, raw_digest);
 268         for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
 269             sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
 270         }
 271         digest[(2 * MD5_DIGEST_SIZE)] = 0;
 272         crm_trace("Digest %s.", digest);
 273 
 274     } else {
 275         crm_err("Could not create digest");
 276     }
 277     return digest;
 278 }

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