1 /*
2  * Copyright 2011-2020 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 <unistd.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <time.h>
21 
22 #include <glib.h>
23 
24 #include <crm/common/util.h>
25 
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
is_magic_value(char * p)35 is_magic_value(char *p)
36 {
37     return !strcmp(p, MAGIC);
38 }
39 
40 static bool
check_md5_hash(char * hash,char * value)41 check_md5_hash(char *hash, char *value)
42 {
43     bool rc = false;
44     char *hash2 = NULL;
45 
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;
50     }
51     free(hash2);
52     return rc;
53 }
54 
55 static char *
read_local_file(char * local_file)56 read_local_file(char *local_file)
57 {
58     FILE *fp = fopen(local_file, "r");
59     char buf[MAX_VALUE_LEN+1];
60     char *p;
61 
62     if (!fp) {
63         if (errno != ENOENT) {
64             crm_perror(LOG_ERR, "cannot open %s" , local_file);
65         }
66         return NULL;
67     }
68 
69     if (!fgets(buf, MAX_VALUE_LEN, fp)) {
70         crm_perror(LOG_ERR, "cannot read %s", local_file);
71         fclose(fp);
72         return NULL;
73     }
74     fclose(fp);
75 
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);
80 }
81 
82 /*!
83  * \internal
84  * \brief Read secret parameter values from file
85  *
86  * Given a table of resource parameters, if any of their values are the
87  * magic string indicating a CIB secret, replace that string with the
88  * secret read from the file appropriate to the given resource.
89  *
90  * \param[in]     rsc_id  Resource whose parameters are being checked
91  * \param[in,out] params  Resource parameters to check
92  *
93  * \return Standard Pacemaker return code
94  */
95 int
pcmk__substitute_secrets(const char * rsc_id,GHashTable * params)96 pcmk__substitute_secrets(const char *rsc_id, GHashTable *params)
97 {
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;
102     int rc = pcmk_rc_ok;
103 
104     if (params == NULL) {
105         return pcmk_rc_ok;
106     }
107 
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);
118 
119     if (snprintf(local_file, FILENAME_MAX, LRM_CIBSECRETS_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);
132             continue;
133         }
134 
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;
139         }
140 
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, LRM_CIBSECRETS_DIR);
146             rc = ENOENT;
147             continue;
148         }
149 
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);
154             free(secret_value);
155             rc = ENAMETOOLONG;
156             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);
177         }
178         g_hash_table_replace(params, strdup(key), secret_value);
179     }
180     g_list_free(secret_params);
181     return rc;
182 }
183 
184 static void
add_secret_params(gpointer key,gpointer value,gpointer user_data)185 add_secret_params(gpointer key, gpointer value, gpointer user_data)
186 {
187     GList **lp = (GList **)user_data;
188 
189     if (is_magic_value((char *)value)) {
190         *lp = g_list_append(*lp, (char *)key);
191     }
192 }
193