1 /*
2  * Copyright (C) 2015-2017 Andrew Beekhof <andrew@beekhof.net>
3  *
4  * This source code is licensed under the GNU Lesser General Public License
5  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
6  */
7 
8 #include <crm_internal.h>
9 #include <crm/crm.h>
10 #include <crm/msg_xml.h>
11 #include <crm/pengine/rules.h>
12 #include <crm/common/alerts_internal.h>
13 #include <crm/pengine/rules_internal.h>
14 
15 #ifdef RHEL7_COMPAT
16 /* @COMPAT An early implementation of alerts was backported to RHEL 7,
17  * even though it was never in an upstream release.
18  */
19 static char *notify_script = NULL;
20 static char *notify_target = NULL;
21 
22 void
pe_enable_legacy_alerts(const char * script,const char * target)23 pe_enable_legacy_alerts(const char *script, const char *target)
24 {
25     static bool need_warning = TRUE;
26 
27     free(notify_script);
28     notify_script = (script && *script && strcmp(script, "/dev/null"))?
29                     strdup(script) : NULL;
30 
31     free(notify_target);
32     notify_target = target && *target? strdup(target): NULL;
33 
34     if (notify_script || notify_target) {
35         if (need_warning) {
36             crm_warn("Support for 'notification-agent' and 'notification-target' cluster options"
37                      " is deprecated and will be removed in a future release"
38                      " (use alerts feature instead)");
39             need_warning = FALSE;
40         }
41     }
42 }
43 #endif
44 
45 static void
get_meta_attrs_from_cib(xmlNode * basenode,crm_alert_entry_t * entry,guint * max_timeout)46 get_meta_attrs_from_cib(xmlNode *basenode, crm_alert_entry_t *entry,
47                         guint *max_timeout)
48 {
49     GHashTable *config_hash = crm_str_table_new();
50     crm_time_t *now = crm_time_new(NULL);
51     const char *value = NULL;
52 
53     unpack_instance_attributes(basenode, basenode, XML_TAG_META_SETS, NULL,
54                                config_hash, NULL, FALSE, now);
55     crm_time_free(now);
56 
57     value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TIMEOUT);
58     if (value) {
59         entry->timeout = crm_get_msec(value);
60         if (entry->timeout <= 0) {
61             if (entry->timeout == 0) {
62                 crm_trace("Alert %s uses default timeout of %dmsec",
63                           entry->id, CRM_ALERT_DEFAULT_TIMEOUT_MS);
64             } else {
65                 crm_warn("Alert %s has invalid timeout value '%s', using default %dmsec",
66                          entry->id, (char*)value, CRM_ALERT_DEFAULT_TIMEOUT_MS);
67             }
68             entry->timeout = CRM_ALERT_DEFAULT_TIMEOUT_MS;
69         } else {
70             crm_trace("Alert %s uses timeout of %dmsec",
71                       entry->id, entry->timeout);
72         }
73         if (entry->timeout > *max_timeout) {
74             *max_timeout = entry->timeout;
75         }
76     }
77     value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TSTAMP_FORMAT);
78     if (value) {
79         /* hard to do any checks here as merely anything can
80          * can be a valid time-format-string
81          */
82         entry->tstamp_format = strdup(value);
83         crm_trace("Alert %s uses timestamp format '%s'",
84                   entry->id, entry->tstamp_format);
85     }
86 
87     g_hash_table_destroy(config_hash);
88 }
89 
90 static void
get_envvars_from_cib(xmlNode * basenode,crm_alert_entry_t * entry)91 get_envvars_from_cib(xmlNode *basenode, crm_alert_entry_t *entry)
92 {
93     xmlNode *child;
94 
95     if ((basenode == NULL) || (entry == NULL)) {
96         return;
97     }
98 
99     child = first_named_child(basenode, XML_TAG_ATTR_SETS);
100     if (child == NULL) {
101         return;
102     }
103 
104     if (entry->envvars == NULL) {
105         entry->envvars = crm_str_table_new();
106     }
107 
108     for (child = first_named_child(child, XML_CIB_TAG_NVPAIR); child != NULL;
109          child = crm_next_same_xml(child)) {
110 
111         const char *name = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
112         const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
113 
114         if (value == NULL) {
115             value = "";
116         }
117         g_hash_table_insert(entry->envvars, strdup(name), strdup(value));
118         crm_trace("Alert %s: added environment variable %s='%s'",
119                   entry->id, name, value);
120     }
121 }
122 
123 static void
unpack_alert_filter(xmlNode * basenode,crm_alert_entry_t * entry)124 unpack_alert_filter(xmlNode *basenode, crm_alert_entry_t *entry)
125 {
126     xmlNode *select = first_named_child(basenode, XML_CIB_TAG_ALERT_SELECT);
127     xmlNode *event_type = NULL;
128     uint32_t flags = crm_alert_none;
129 
130     for (event_type = __xml_first_child_element(select); event_type != NULL;
131          event_type = __xml_next_element(event_type)) {
132 
133         const char *tagname = crm_element_name(event_type);
134 
135         if (tagname == NULL) {
136             continue;
137 
138         } else if (!strcmp(tagname, XML_CIB_TAG_ALERT_FENCING)) {
139             flags |= crm_alert_fencing;
140 
141         } else if (!strcmp(tagname, XML_CIB_TAG_ALERT_NODES)) {
142             flags |= crm_alert_node;
143 
144         } else if (!strcmp(tagname, XML_CIB_TAG_ALERT_RESOURCES)) {
145             flags |= crm_alert_resource;
146 
147         } else if (!strcmp(tagname, XML_CIB_TAG_ALERT_ATTRIBUTES)) {
148             xmlNode *attr;
149             const char *attr_name;
150             int nattrs = 0;
151 
152             flags |= crm_alert_attribute;
153             for (attr = first_named_child(event_type, XML_CIB_TAG_ALERT_ATTR);
154                  attr != NULL;
155                  attr = crm_next_same_xml(attr)) {
156 
157                 attr_name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
158                 if (attr_name) {
159                     if (nattrs == 0) {
160                         g_strfreev(entry->select_attribute_name);
161                         entry->select_attribute_name = NULL;
162                     }
163                     ++nattrs;
164                     entry->select_attribute_name = realloc_safe(entry->select_attribute_name,
165                                                                 (nattrs + 1) * sizeof(char*));
166                     entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
167                     entry->select_attribute_name[nattrs] = NULL;
168                 }
169             }
170         }
171     }
172 
173     if (flags != crm_alert_none) {
174         entry->flags = flags;
175         crm_debug("Alert %s receives events: attributes:%s, fencing:%s, nodes:%s, resources:%s",
176                   entry->id,
177                   (flags & crm_alert_attribute)?
178                     (entry->select_attribute_name? "some" : "all") : "no",
179                   (flags & crm_alert_fencing)? "yes" : "no",
180                   (flags & crm_alert_node)? "yes" : "no",
181                   (flags & crm_alert_resource)? "yes" : "no");
182     }
183 }
184 
185 static void
unpack_alert(xmlNode * alert,crm_alert_entry_t * entry,guint * max_timeout)186 unpack_alert(xmlNode *alert, crm_alert_entry_t *entry, guint *max_timeout)
187 {
188     get_envvars_from_cib(alert, entry);
189     get_meta_attrs_from_cib(alert, entry, max_timeout);
190     unpack_alert_filter(alert, entry);
191 }
192 
193 /*!
194  * \internal
195  * \brief Unpack a CIB alerts section
196  *
197  * \param[in] alerts  XML of alerts section
198  *
199  * \return  List of unpacked alert entries
200  *
201  * \note Unlike most unpack functions, this is not used by the pengine itself,
202  *       but is supplied for use by daemons that need to send alerts.
203  */
204 GListPtr
pe_unpack_alerts(xmlNode * alerts)205 pe_unpack_alerts(xmlNode *alerts)
206 {
207     xmlNode *alert;
208     crm_alert_entry_t *entry;
209     guint max_timeout = 0;
210     GListPtr alert_list = NULL;
211 
212     if (alerts) {
213 #ifdef RHEL7_COMPAT
214         if (notify_script) {
215             crm_warn("Ignoring deprecated notification configuration because alerts available");
216         }
217 #endif
218     } else {
219 #ifdef RHEL7_COMPAT
220         if (notify_script) {
221             entry = crm_alert_entry_new("legacy_notification", notify_script);
222             entry->recipient = strdup(notify_target);
223             entry->tstamp_format = strdup(CRM_ALERT_DEFAULT_TSTAMP_FORMAT);
224             alert_list = g_list_prepend(alert_list, entry);
225             crm_warn("Deprecated notification syntax in use (alerts syntax is preferable)");
226         }
227 #endif
228         return alert_list;
229     }
230 
231     for (alert = first_named_child(alerts, XML_CIB_TAG_ALERT);
232          alert != NULL; alert = crm_next_same_xml(alert)) {
233 
234         xmlNode *recipient;
235         int recipients = 0;
236         const char *alert_id = ID(alert);
237         const char *alert_path = crm_element_value(alert, XML_ALERT_ATTR_PATH);
238 
239         /* The schema should enforce this, but to be safe ... */
240         if ((alert_id == NULL) || (alert_path == NULL)) {
241             crm_warn("Ignoring invalid alert without id and path");
242             continue;
243         }
244 
245         entry = crm_alert_entry_new(alert_id, alert_path);
246 
247         unpack_alert(alert, entry, &max_timeout);
248 
249         if (entry->tstamp_format == NULL) {
250             entry->tstamp_format = strdup(CRM_ALERT_DEFAULT_TSTAMP_FORMAT);
251         }
252 
253         crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %u vars",
254                   entry->id, entry->path, entry->timeout, entry->tstamp_format,
255                   (entry->envvars? g_hash_table_size(entry->envvars) : 0));
256 
257         for (recipient = first_named_child(alert, XML_CIB_TAG_ALERT_RECIPIENT);
258              recipient != NULL; recipient = crm_next_same_xml(recipient)) {
259 
260             crm_alert_entry_t *recipient_entry = crm_dup_alert_entry(entry);
261 
262             recipients++;
263             recipient_entry->recipient = strdup(crm_element_value(recipient,
264                                                 XML_ALERT_ATTR_REC_VALUE));
265             unpack_alert(recipient, recipient_entry, &max_timeout);
266             alert_list = g_list_prepend(alert_list, recipient_entry);
267             crm_debug("Alert %s has recipient %s with value %s and %d envvars",
268                       entry->id, ID(recipient), recipient_entry->recipient,
269                       (recipient_entry->envvars?
270                        g_hash_table_size(recipient_entry->envvars) : 0));
271         }
272 
273         if (recipients == 0) {
274             alert_list = g_list_prepend(alert_list, entry);
275         } else {
276             crm_free_alert_entry(entry);
277         }
278     }
279     return alert_list;
280 }
281 
282 /*!
283  * \internal
284  * \brief Free an alert list generated by pe_unpack_alerts()
285  *
286  * \param[in] alert_list  Alert list to free
287  */
288 void
pe_free_alert_list(GListPtr alert_list)289 pe_free_alert_list(GListPtr alert_list)
290 {
291     if (alert_list) {
292         g_list_free_full(alert_list, (GDestroyNotify) crm_free_alert_entry);
293     }
294 }
295