11/*
2- * Copyright 2011-2020 the Pacemaker project contributors
2+ * Copyright 2011-2025 the Pacemaker project contributors
33 *
44 * The version control history for this file may have further details.
55 *
2323
2424#include <crm/common/util.h>
2525
26- static int is_magic_value (char * p );
27- static bool check_md5_hash (char * hash , char * value );
28- static void add_secret_params (gpointer key , gpointer value , gpointer user_data );
29- static char * read_local_file (char * local_file );
30-
31- #define MAX_VALUE_LEN 255
32- #define MAGIC "lrm://"
33-
34- static int
35- is_magic_value (char * p )
26+ /*!
27+ * \internal
28+ * \brief Read file contents into a string, with trailing whitespace removed
29+ *
30+ * \param[in] filename Name of file to read
31+ *
32+ * \return File contents as a string, or \c NULL on failure or empty file
33+ *
34+ * \note It would be simpler to call \c pcmk__file_contents() directly without
35+ * trimming trailing whitespace. However, this would change hashes for
36+ * existing value files that have trailing whitespace. Similarly, if we
37+ * trim trailing whitespace, it would make sense to trim leading
38+ * whitespace, but this changes existing hashes.
39+ */
40+ static char *
41+ read_file_trimmed (const char * filename )
3642{
37- return !strcmp (p , MAGIC );
38- }
43+ char * p = NULL ;
44+ char * buf = NULL ;
45+ int rc = pcmk__file_contents (filename , & buf );
3946
40- static bool
41- check_md5_hash ( char * hash , char * value )
42- {
43- bool rc = false ;
44- char * hash2 = NULL ;
47+ if ( rc != pcmk_rc_ok ) {
48+ crm_err ( "Failed to read %s: %s" , filename , pcmk_rc_str ( rc ));
49+ free ( buf );
50+ return NULL ;
51+ }
4552
46- hash2 = crm_md5sum (value );
47- crm_debug ("hash: %s, calculated hash: %s" , hash , hash2 );
48- if (pcmk__str_eq (hash , hash2 , pcmk__str_casei )) {
49- rc = true;
53+ if (buf == NULL ) {
54+ crm_err ("File %s is empty" , filename );
55+ return NULL ;
5056 }
51- free (hash2 );
52- return rc ;
57+
58+ // Strip trailing white space
59+ for (p = buf + strlen (buf ) - 1 ; (p >= buf ) && isspace (* p ); p -- );
60+ * (p + 1 ) = '\0' ;
61+
62+ return buf ;
5363}
5464
55- static char *
56- read_local_file (char * local_file )
65+ /*!
66+ * \internal
67+ * \brief Read checksum from a file and compare against calculated checksum
68+ *
69+ * \param[in] filename File containing stored checksum
70+ * \param[in] secret_value String to calculate checksum from
71+ * \param[in] rsc_id Resource ID (for logging only)
72+ * \param[in] param Parameter name (for logging only)
73+ *
74+ * \return Standard Pacemaker return code
75+ */
76+ static int
77+ validate_hash (const char * filename , const char * secret_value ,
78+ const char * rsc_id , const char * param )
5779{
58- FILE * fp = fopen ( local_file , "r" ) ;
59- char buf [ MAX_VALUE_LEN + 1 ] ;
60- char * p ;
80+ char * stored = NULL ;
81+ char * calculated = NULL ;
82+ int rc = pcmk_rc_ok ;
6183
62- if (!fp ) {
63- if (errno != ENOENT ) {
64- crm_perror (LOG_ERR , "cannot open %s" , local_file );
65- }
66- return NULL ;
84+ stored = read_file_trimmed (filename );
85+ if (stored == NULL ) {
86+ crm_err ("Could not read md5 sum for resource %s parameter '%s' from "
87+ "file '%s'" ,
88+ rsc_id , param , filename );
89+ rc = ENOENT ;
90+ goto done ;
6791 }
6892
69- if (!fgets (buf , MAX_VALUE_LEN , fp )) {
70- crm_perror (LOG_ERR , "cannot read %s" , local_file );
71- fclose (fp );
72- return NULL ;
93+ calculated = crm_md5sum (secret_value );
94+ if (calculated == NULL ) {
95+ // Should be impossible
96+ rc = EINVAL ;
97+ goto done ;
7398 }
74- fclose (fp );
7599
76- // Strip trailing white space
77- for (p = buf + strlen (buf ) - 1 ; (p >= buf ) && isspace (* p ); p -- );
78- * (p + 1 ) = '\0' ;
79- return strdup (buf );
100+ crm_trace ("Stored hash: %s, calculated hash: %s" , stored , calculated );
101+
102+ if (!pcmk__str_eq (stored , calculated , pcmk__str_casei )) {
103+ crm_err ("Calculated md5 sum for resource %s parameter '%s' does not "
104+ "match stored md5 sum" ,
105+ rsc_id , param );
106+ rc = pcmk_rc_cib_corrupt ;
107+ }
108+
109+ done :
110+ free (stored );
111+ free (calculated );
112+ return rc ;
80113}
81114
82115/*!
@@ -95,98 +128,68 @@ read_local_file(char *local_file)
95128int
96129pcmk__substitute_secrets (const char * rsc_id , GHashTable * params )
97130{
98- char local_file [FILENAME_MAX + 1 ], * start_pname ;
99- char hash_file [FILENAME_MAX + 1 ], * hash ;
100- GList * secret_params = NULL , * l ;
101- char * key , * pvalue , * secret_value ;
131+ GHashTableIter iter ;
132+ char * param = NULL ;
133+ char * value = NULL ;
134+ GString * filename = NULL ;
135+ gsize dir_len = 0 ;
102136 int rc = pcmk_rc_ok ;
103137
104138 if (params == NULL ) {
105139 return pcmk_rc_ok ;
106140 }
107141
108- /* secret_params could be cached with the resource;
109- * there are also parameters sent with operations
110- * which cannot be cached
111- */
112- g_hash_table_foreach (params , add_secret_params , & secret_params );
113- if (secret_params == NULL ) { // No secret parameters found
114- return pcmk_rc_ok ;
115- }
116-
117- crm_debug ("Replace secret parameters for resource %s" , rsc_id );
142+ // Some params are sent with operations, so we cannot cache secret params
143+ g_hash_table_iter_init (& iter , params );
144+ while (g_hash_table_iter_next (& iter , (gpointer * ) & param ,
145+ (gpointer * ) & value )) {
146+ char * secret_value = NULL ;
147+ int hash_rc = pcmk_rc_ok ;
118148
119- if (snprintf (local_file , FILENAME_MAX , PCMK__CIB_SECRETS_DIR "/%s/" , rsc_id )
120- > FILENAME_MAX ) {
121- crm_err ("Can't replace secret parameters for %s: file name size exceeded" ,
122- rsc_id );
123- return ENAMETOOLONG ;
124- }
125- start_pname = local_file + strlen (local_file );
126-
127- for (l = g_list_first (secret_params ); l ; l = g_list_next (l )) {
128- key = (char * )(l -> data );
129- pvalue = g_hash_table_lookup (params , key );
130- if (!pvalue ) { /* this cannot really happen */
131- crm_err ("odd, no parameter %s for rsc %s found now" , key , rsc_id );
149+ if (!pcmk__str_eq (value , "lrm://" , pcmk__str_none )) {
150+ // Not a secret parameter
132151 continue ;
133152 }
134153
135- if ((strlen (key ) + strlen (local_file )) >= FILENAME_MAX - 2 ) {
136- crm_err ("%s: parameter name %s too big" , rsc_id , key );
137- rc = ENAMETOOLONG ;
138- continue ;
154+ if (filename == NULL ) {
155+ // First secret parameter. Fill in directory path for use with all.
156+ crm_debug ("Replacing secret parameters for resource %s" , rsc_id );
157+
158+ filename = g_string_sized_new (128 );
159+ pcmk__g_strcat (filename , PCMK__CIB_SECRETS_DIR "/" , rsc_id , "/" ,
160+ NULL );
161+ dir_len = filename -> len ;
162+
163+ } else {
164+ // Reset filename to the resource's secrets directory path
165+ g_string_truncate (filename , dir_len );
139166 }
140167
141- strcpy (start_pname , key );
142- secret_value = read_local_file (local_file );
143- if (!secret_value ) {
144- crm_err ("secret for rsc %s parameter %s not found in %s" ,
145- rsc_id , key , PCMK__CIB_SECRETS_DIR );
168+ // Path to file containing secret value for this parameter
169+ g_string_append (filename , param );
170+ secret_value = read_file_trimmed (filename -> str );
171+ if (secret_value == NULL ) {
172+ crm_err ("Secret value for resource %s parameter '%s' not found in "
173+ PCMK__CIB_SECRETS_DIR ,
174+ rsc_id , param );
146175 rc = ENOENT ;
147176 continue ;
148177 }
149178
150- strcpy (hash_file , local_file );
151- if (strlen (hash_file ) + 5 > FILENAME_MAX ) {
152- crm_err ("cannot build such a long name "
153- "for the sign file: %s.sign" , hash_file );
179+ // Path to file containing md5 sum for this parameter
180+ g_string_append (filename , ".sign" );
181+ hash_rc = validate_hash (filename -> str , secret_value , rsc_id , param );
182+ if (hash_rc != pcmk_rc_ok ) {
183+ rc = hash_rc ;
154184 free (secret_value );
155- rc = ENAMETOOLONG ;
156185 continue ;
157-
158- } else {
159- strcat (hash_file , ".sign" );
160- hash = read_local_file (hash_file );
161- if (hash == NULL ) {
162- crm_err ("md5 sum for rsc %s parameter %s "
163- "cannot be read from %s" , rsc_id , key , hash_file );
164- free (secret_value );
165- rc = ENOENT ;
166- continue ;
167-
168- } else if (!check_md5_hash (hash , secret_value )) {
169- crm_err ("md5 sum for rsc %s parameter %s "
170- "does not match" , rsc_id , key );
171- free (secret_value );
172- free (hash );
173- rc = pcmk_rc_cib_corrupt ;
174- continue ;
175- }
176- free (hash );
177186 }
178- g_hash_table_replace (params , strdup (key ), secret_value );
179- }
180- g_list_free (secret_params );
181- return rc ;
182- }
183187
184- static void
185- add_secret_params (gpointer key , gpointer value , gpointer user_data )
186- {
187- GList * * lp = (GList * * )user_data ;
188+ g_hash_table_iter_replace (& iter , (gpointer ) secret_value );
189+ }
188190
189- if (is_magic_value (( char * ) value ) ) {
190- * lp = g_list_append ( * lp , ( char * ) key );
191+ if (filename != NULL ) {
192+ g_string_free ( filename , TRUE );
191193 }
194+ return rc ;
192195}
0 commit comments