Merge remote-tracking branch 'github/pr/2277'
[collectd.git] / src / bind.c
1 /**
2  * collectd - src/bind.c
3  * Copyright (C) 2009       Bruno PrĂ©mont
4  * Copyright (C) 2009,2010  Florian Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Bruno PrĂ©mont <bonbons at linux-vserver.org>
21  *   Florian Forster <octo at collectd.org>
22  **/
23
24 #include "config.h"
25
26 #if STRPTIME_NEEDS_STANDARDS
27 #ifndef _ISOC99_SOURCE
28 #define _ISOC99_SOURCE 1
29 #endif
30 #ifndef _POSIX_C_SOURCE
31 #define _POSIX_C_SOURCE 200112L
32 #endif
33 #ifndef _XOPEN_SOURCE
34 #define _XOPEN_SOURCE 500
35 #endif
36 #endif /* STRPTIME_NEEDS_STANDARDS */
37
38 #if TIMEGM_NEEDS_BSD
39 #ifndef _BSD_SOURCE
40 #define _BSD_SOURCE 1
41 #endif
42 #endif /* TIMEGM_NEEDS_BSD */
43
44 #include "collectd.h"
45
46 #include "common.h"
47 #include "plugin.h"
48
49 #include <time.h>
50
51 /* Some versions of libcurl don't include this themselves and then don't have
52  * fd_set available. */
53 #if HAVE_SYS_SELECT_H
54 #include <sys/select.h>
55 #endif
56
57 #include <curl/curl.h>
58 #include <libxml/parser.h>
59 #include <libxml/xpath.h>
60
61 #ifndef BIND_DEFAULT_URL
62 #define BIND_DEFAULT_URL "http://localhost:8053/"
63 #endif
64
65 /*
66  * Some types used for the callback functions. `translation_table_ptr_t' and
67  * `list_info_ptr_t' are passed to the callbacks in the `void *user_data'
68  * pointer.
69  */
70 typedef int (*list_callback_t)(const char *name, value_t value,
71                                time_t current_time, void *user_data);
72
73 struct cb_view_s {
74   char *name;
75
76   int qtypes;
77   int resolver_stats;
78   int cacherrsets;
79
80   char **zones;
81   size_t zones_num;
82 };
83 typedef struct cb_view_s cb_view_t;
84
85 struct translation_info_s {
86   const char *xml_name;
87   const char *type;
88   const char *type_instance;
89 };
90 typedef struct translation_info_s translation_info_t;
91
92 struct translation_table_ptr_s {
93   const translation_info_t *table;
94   size_t table_length;
95   const char *plugin_instance;
96 };
97 typedef struct translation_table_ptr_s translation_table_ptr_t;
98
99 struct list_info_ptr_s {
100   const char *plugin_instance;
101   const char *type;
102 };
103 typedef struct list_info_ptr_s list_info_ptr_t;
104
105 /* FIXME: Enabled by default for backwards compatibility. */
106 /* TODO: Remove time parsing code. */
107 static _Bool config_parse_time = 1;
108
109 static char *url = NULL;
110 static int global_opcodes = 1;
111 static int global_qtypes = 1;
112 static int global_server_stats = 1;
113 static int global_zone_maint_stats = 1;
114 static int global_resolver_stats = 0;
115 static int global_memory_stats = 1;
116 static int timeout = -1;
117
118 static cb_view_t *views = NULL;
119 static size_t views_num = 0;
120
121 static CURL *curl = NULL;
122
123 static char *bind_buffer = NULL;
124 static size_t bind_buffer_size = 0;
125 static size_t bind_buffer_fill = 0;
126 static char bind_curl_error[CURL_ERROR_SIZE];
127
128 /* Translation table for the `nsstats' values. */
129 static const translation_info_t nsstats_translation_table[] = /* {{{ */
130     {
131         /* Requests */
132         {"Requestv4", "dns_request", "IPv4"},
133         {"Requestv6", "dns_request", "IPv6"},
134         {"ReqEdns0", "dns_request", "EDNS0"},
135         {"ReqBadEDNSVer", "dns_request", "BadEDNSVer"},
136         {"ReqTSIG", "dns_request", "TSIG"},
137         {"ReqSIG0", "dns_request", "SIG0"},
138         {"ReqBadSIG", "dns_request", "BadSIG"},
139         {"ReqTCP", "dns_request", "TCP"},
140         /* Rejects */
141         {"AuthQryRej", "dns_reject", "authoritative"},
142         {"RecQryRej", "dns_reject", "recursive"},
143         {"XfrRej", "dns_reject", "transfer"},
144         {"UpdateRej", "dns_reject", "update"},
145         /* Responses */
146         {"Response", "dns_response", "normal"},
147         {"TruncatedResp", "dns_response", "truncated"},
148         {"RespEDNS0", "dns_response", "EDNS0"},
149         {"RespTSIG", "dns_response", "TSIG"},
150         {"RespSIG0", "dns_response", "SIG0"},
151         /* Queries */
152         {"QryAuthAns", "dns_query", "authoritative"},
153         {"QryNoauthAns", "dns_query", "nonauth"},
154         {"QryReferral", "dns_query", "referral"},
155         {"QryRecursion", "dns_query", "recursion"},
156         {"QryDuplicate", "dns_query", "duplicate"},
157         {"QryDropped", "dns_query", "dropped"},
158         {"QryFailure", "dns_query", "failure"},
159         /* Response codes */
160         {"QrySuccess", "dns_rcode", "tx-NOERROR"},
161         {"QryNxrrset", "dns_rcode", "tx-NXRRSET"},
162         {"QrySERVFAIL", "dns_rcode", "tx-SERVFAIL"},
163         {"QryFORMERR", "dns_rcode", "tx-FORMERR"},
164         {"QryNXDOMAIN", "dns_rcode", "tx-NXDOMAIN"}
165 #if 0
166   { "XfrReqDone",      "type",         "type_instance" },
167   { "UpdateReqFwd",    "type",         "type_instance" },
168   { "UpdateRespFwd",   "type",         "type_instance" },
169   { "UpdateFwdFail",   "type",         "type_instance" },
170   { "UpdateDone",      "type",         "type_instance" },
171   { "UpdateFail",      "type",         "type_instance" },
172   { "UpdateBadPrereq", "type",         "type_instance" },
173 #endif
174 };
175 static int nsstats_translation_table_length =
176     STATIC_ARRAY_SIZE(nsstats_translation_table);
177 /* }}} */
178
179 /* Translation table for the `zonestats' values. */
180 static const translation_info_t zonestats_translation_table[] = /* {{{ */
181     {
182         /* Notify's */
183         {"NotifyOutv4", "dns_notify", "tx-IPv4"},
184         {"NotifyOutv6", "dns_notify", "tx-IPv6"},
185         {"NotifyInv4", "dns_notify", "rx-IPv4"},
186         {"NotifyInv6", "dns_notify", "rx-IPv6"},
187         {"NotifyRej", "dns_notify", "rejected"},
188         /* SOA/AXFS/IXFS requests */
189         {"SOAOutv4", "dns_opcode", "SOA-IPv4"},
190         {"SOAOutv6", "dns_opcode", "SOA-IPv6"},
191         {"AXFRReqv4", "dns_opcode", "AXFR-IPv4"},
192         {"AXFRReqv6", "dns_opcode", "AXFR-IPv6"},
193         {"IXFRReqv4", "dns_opcode", "IXFR-IPv4"},
194         {"IXFRReqv6", "dns_opcode", "IXFR-IPv6"},
195         /* Domain transfers */
196         {"XfrSuccess", "dns_transfer", "success"},
197         {"XfrFail", "dns_transfer", "failure"}};
198 static int zonestats_translation_table_length =
199     STATIC_ARRAY_SIZE(zonestats_translation_table);
200 /* }}} */
201
202 /* Translation table for the `resstats' values. */
203 static const translation_info_t resstats_translation_table[] = /* {{{ */
204     {
205         /* Generic resolver information */
206         {"Queryv4", "dns_query", "IPv4"},
207         {"Queryv6", "dns_query", "IPv6"},
208         {"Responsev4", "dns_response", "IPv4"},
209         {"Responsev6", "dns_response", "IPv6"},
210         /* Received response codes */
211         {"NXDOMAIN", "dns_rcode", "rx-NXDOMAIN"},
212         {"SERVFAIL", "dns_rcode", "rx-SERVFAIL"},
213         {"FORMERR", "dns_rcode", "rx-FORMERR"},
214         {"OtherError", "dns_rcode", "rx-OTHER"},
215         {"EDNS0Fail", "dns_rcode", "rx-EDNS0Fail"},
216         /* Received responses */
217         {"Mismatch", "dns_response", "mismatch"},
218         {"Truncated", "dns_response", "truncated"},
219         {"Lame", "dns_response", "lame"},
220         {"Retry", "dns_query", "retry"},
221 #if 0
222   { "GlueFetchv4",     "type", "type_instance" },
223   { "GlueFetchv6",     "type", "type_instance" },
224   { "GlueFetchv4Fail", "type", "type_instance" },
225   { "GlueFetchv6Fail", "type", "type_instance" },
226 #endif
227         /* DNSSEC information */
228         {"ValAttempt", "dns_resolver", "DNSSEC-attempt"},
229         {"ValOk", "dns_resolver", "DNSSEC-okay"},
230         {"ValNegOk", "dns_resolver", "DNSSEC-negokay"},
231         {"ValFail", "dns_resolver", "DNSSEC-fail"}};
232 static int resstats_translation_table_length =
233     STATIC_ARRAY_SIZE(resstats_translation_table);
234 /* }}} */
235
236 /* Translation table for the `memory/summary' values. */
237 static const translation_info_t memsummary_translation_table[] = /* {{{ */
238     {{"TotalUse", "memory", "TotalUse"},
239      {"InUse", "memory", "InUse"},
240      {"BlockSize", "memory", "BlockSize"},
241      {"ContextSize", "memory", "ContextSize"},
242      {"Lost", "memory", "Lost"}};
243 static int memsummary_translation_table_length =
244     STATIC_ARRAY_SIZE(memsummary_translation_table);
245 /* }}} */
246
247 static void submit(time_t ts, const char *plugin_instance, /* {{{ */
248                    const char *type, const char *type_instance, value_t value) {
249   value_list_t vl = VALUE_LIST_INIT;
250
251   vl.values = &value;
252   vl.values_len = 1;
253   if (config_parse_time)
254     vl.time = TIME_T_TO_CDTIME_T(ts);
255   sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
256   if (plugin_instance) {
257     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
258     replace_special(vl.plugin_instance, sizeof(vl.plugin_instance));
259   }
260   sstrncpy(vl.type, type, sizeof(vl.type));
261   if (type_instance) {
262     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
263     replace_special(vl.type_instance, sizeof(vl.type_instance));
264   }
265   plugin_dispatch_values(&vl);
266 } /* }}} void submit */
267
268 static size_t bind_curl_callback(void *buf, size_t size, /* {{{ */
269                                  size_t nmemb,
270                                  void __attribute__((unused)) * stream) {
271   size_t len = size * nmemb;
272
273   if (len == 0)
274     return len;
275
276   if ((bind_buffer_fill + len) >= bind_buffer_size) {
277     char *temp;
278
279     temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
280     if (temp == NULL) {
281       ERROR("bind plugin: realloc failed.");
282       return 0;
283     }
284     bind_buffer = temp;
285     bind_buffer_size = bind_buffer_fill + len + 1;
286   }
287
288   memcpy(bind_buffer + bind_buffer_fill, (char *)buf, len);
289   bind_buffer_fill += len;
290   bind_buffer[bind_buffer_fill] = 0;
291
292   return len;
293 } /* }}} size_t bind_curl_callback */
294
295 /*
296  * Callback, that's called with a translation table.
297  * (Plugin instance is fixed, type and type instance come from lookup table.)
298  */
299 static int bind_xml_table_callback(const char *name, value_t value, /* {{{ */
300                                    time_t current_time, void *user_data) {
301   translation_table_ptr_t *table = (translation_table_ptr_t *)user_data;
302
303   if (table == NULL)
304     return -1;
305
306   for (size_t i = 0; i < table->table_length; i++) {
307     if (strcmp(table->table[i].xml_name, name) != 0)
308       continue;
309
310     submit(current_time, table->plugin_instance, table->table[i].type,
311            table->table[i].type_instance, value);
312     break;
313   }
314
315   return 0;
316 } /* }}} int bind_xml_table_callback */
317
318 /*
319  * Callback, that's used for lists.
320  * (Plugin instance and type are fixed, xml name is used as type instance.)
321  */
322 static int bind_xml_list_callback(const char *name, /* {{{ */
323                                   value_t value, time_t current_time,
324                                   void *user_data) {
325   list_info_ptr_t *list_info = (list_info_ptr_t *)user_data;
326
327   if (list_info == NULL)
328     return -1;
329
330   submit(current_time, list_info->plugin_instance, list_info->type,
331          /* type instance = */ name, value);
332
333   return 0;
334 } /* }}} int bind_xml_list_callback */
335
336 static int bind_xml_read_derive(xmlDoc *doc, xmlNode *node, /* {{{ */
337                                 derive_t *ret_value) {
338   char *str_ptr;
339   value_t value;
340   int status;
341
342   str_ptr = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
343   if (str_ptr == NULL) {
344     ERROR("bind plugin: bind_xml_read_derive: xmlNodeListGetString failed.");
345     return -1;
346   }
347
348   status = parse_value(str_ptr, &value, DS_TYPE_DERIVE);
349   if (status != 0) {
350     ERROR("bind plugin: Parsing string \"%s\" to derive value failed.",
351           str_ptr);
352     xmlFree(str_ptr);
353     return -1;
354   }
355
356   xmlFree(str_ptr);
357   *ret_value = value.derive;
358   return 0;
359 } /* }}} int bind_xml_read_derive */
360
361 static int bind_xml_read_gauge(xmlDoc *doc, xmlNode *node, /* {{{ */
362                                gauge_t *ret_value) {
363   char *str_ptr, *end_ptr;
364   double value;
365
366   str_ptr = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
367   if (str_ptr == NULL) {
368     ERROR("bind plugin: bind_xml_read_gauge: xmlNodeListGetString failed.");
369     return -1;
370   }
371
372   errno = 0;
373   value = strtod(str_ptr, &end_ptr);
374   xmlFree(str_ptr);
375   if (str_ptr == end_ptr || errno) {
376     if (errno && (value < 0))
377       ERROR("bind plugin: bind_xml_read_gauge: strtod failed with underflow.");
378     else if (errno && (value > 0))
379       ERROR("bind plugin: bind_xml_read_gauge: strtod failed with overflow.");
380     else
381       ERROR("bind plugin: bind_xml_read_gauge: strtod failed.");
382     return -1;
383   }
384
385   *ret_value = (gauge_t)value;
386   return 0;
387 } /* }}} int bind_xml_read_gauge */
388
389 static int bind_xml_read_timestamp(const char *xpath_expression, /* {{{ */
390                                    xmlDoc *doc, xmlXPathContext *xpathCtx,
391                                    time_t *ret_value) {
392   xmlXPathObject *xpathObj = NULL;
393   xmlNode *node;
394   char *str_ptr;
395   char *tmp;
396   struct tm tm = {0};
397
398   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
399   if (xpathObj == NULL) {
400     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
401           xpath_expression);
402     return -1;
403   }
404
405   if ((xpathObj->nodesetval == NULL) || (xpathObj->nodesetval->nodeNr < 1)) {
406     xmlXPathFreeObject(xpathObj);
407     return -1;
408   }
409
410   if (xpathObj->nodesetval->nodeNr != 1) {
411     NOTICE("bind plugin: Evaluating the XPath expression `%s' returned "
412            "%i nodes. Only handling the first one.",
413            xpath_expression, xpathObj->nodesetval->nodeNr);
414   }
415
416   node = xpathObj->nodesetval->nodeTab[0];
417
418   if (node->xmlChildrenNode == NULL) {
419     ERROR("bind plugin: bind_xml_read_timestamp: "
420           "node->xmlChildrenNode == NULL");
421     xmlXPathFreeObject(xpathObj);
422     return -1;
423   }
424
425   str_ptr = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
426   if (str_ptr == NULL) {
427     ERROR("bind plugin: bind_xml_read_timestamp: xmlNodeListGetString failed.");
428     xmlXPathFreeObject(xpathObj);
429     return -1;
430   }
431
432   tmp = strptime(str_ptr, "%Y-%m-%dT%T", &tm);
433   xmlFree(str_ptr);
434   if (tmp == NULL) {
435     ERROR("bind plugin: bind_xml_read_timestamp: strptime failed.");
436     xmlXPathFreeObject(xpathObj);
437     return -1;
438   }
439
440 #if HAVE_TIMEGM
441   time_t t = timegm(&tm);
442   if (t == ((time_t)-1)) {
443     char errbuf[1024];
444     ERROR("bind plugin: timegm() failed: %s",
445           sstrerror(errno, errbuf, sizeof(errbuf)));
446     return -1;
447   }
448   *ret_value = t;
449 #else
450   time_t t = mktime(&tm);
451   if (t == ((time_t)-1)) {
452     char errbuf[1024];
453     ERROR("bind plugin: mktime() failed: %s",
454           sstrerror(errno, errbuf, sizeof(errbuf)));
455     return -1;
456   }
457   /* mktime assumes that tm is local time. Luckily, it also sets timezone to
458    * the offset used for the conversion, and we undo the conversion to convert
459    * back to UTC. */
460   *ret_value = t - timezone;
461 #endif
462
463   xmlXPathFreeObject(xpathObj);
464   return 0;
465 } /* }}} int bind_xml_read_timestamp */
466
467 /*
468  * bind_parse_generic_name_value
469  *
470  * Reads statistics in the form:
471  * <foo>
472  *   <name>QUERY</name>
473  *   <counter>123</counter>
474  * </foo>
475  */
476 static int bind_parse_generic_name_value(const char *xpath_expression, /* {{{ */
477                                          list_callback_t list_callback,
478                                          void *user_data, xmlDoc *doc,
479                                          xmlXPathContext *xpathCtx,
480                                          time_t current_time, int ds_type) {
481   xmlXPathObject *xpathObj = NULL;
482   int num_entries;
483
484   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
485   if (xpathObj == NULL) {
486     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
487           xpath_expression);
488     return -1;
489   }
490
491   num_entries = 0;
492   /* Iterate over all matching nodes. */
493   for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr);
494        i++) {
495     xmlNode *name_node = NULL;
496     xmlNode *counter = NULL;
497     xmlNode *parent;
498
499     parent = xpathObj->nodesetval->nodeTab[i];
500     DEBUG("bind plugin: bind_parse_generic_name_value: parent->name = %s;",
501           (char *)parent->name);
502
503     /* Iterate over all child nodes. */
504     for (xmlNode *child = parent->xmlChildrenNode; child != NULL;
505          child = child->next) {
506       if (child->type != XML_ELEMENT_NODE)
507         continue;
508
509       if (xmlStrcmp(BAD_CAST "name", child->name) == 0)
510         name_node = child;
511       else if (xmlStrcmp(BAD_CAST "counter", child->name) == 0)
512         counter = child;
513     }
514
515     if ((name_node != NULL) && (counter != NULL)) {
516       char *name =
517           (char *)xmlNodeListGetString(doc, name_node->xmlChildrenNode, 1);
518       value_t value;
519       int status;
520
521       if (ds_type == DS_TYPE_GAUGE)
522         status = bind_xml_read_gauge(doc, counter, &value.gauge);
523       else
524         status = bind_xml_read_derive(doc, counter, &value.derive);
525       if (status != 0)
526         continue;
527
528       status = (*list_callback)(name, value, current_time, user_data);
529       if (status == 0)
530         num_entries++;
531
532       xmlFree(name);
533     }
534   }
535
536   DEBUG("bind plugin: Found %d %s for XPath expression `%s'", num_entries,
537         (num_entries == 1) ? "entry" : "entries", xpath_expression);
538
539   xmlXPathFreeObject(xpathObj);
540
541   return 0;
542 } /* }}} int bind_parse_generic_name_value */
543
544 /*
545  * bind_parse_generic_value_list
546  *
547  * Reads statistics in the form:
548  * <foo>
549  *   <name0>123</name0>
550  *   <name1>234</name1>
551  *   <name2>345</name2>
552  *   :
553  * </foo>
554  */
555 static int bind_parse_generic_value_list(const char *xpath_expression, /* {{{ */
556                                          list_callback_t list_callback,
557                                          void *user_data, xmlDoc *doc,
558                                          xmlXPathContext *xpathCtx,
559                                          time_t current_time, int ds_type) {
560   xmlXPathObject *xpathObj = NULL;
561   int num_entries;
562
563   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
564   if (xpathObj == NULL) {
565     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
566           xpath_expression);
567     return -1;
568   }
569
570   num_entries = 0;
571   /* Iterate over all matching nodes. */
572   for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr);
573        i++) {
574     /* Iterate over all child nodes. */
575     for (xmlNode *child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
576          child != NULL; child = child->next) {
577       char *node_name;
578       value_t value;
579       int status;
580
581       if (child->type != XML_ELEMENT_NODE)
582         continue;
583
584       node_name = (char *)child->name;
585
586       if (ds_type == DS_TYPE_GAUGE)
587         status = bind_xml_read_gauge(doc, child, &value.gauge);
588       else
589         status = bind_xml_read_derive(doc, child, &value.derive);
590       if (status != 0)
591         continue;
592
593       status = (*list_callback)(node_name, value, current_time, user_data);
594       if (status == 0)
595         num_entries++;
596     }
597   }
598
599   DEBUG("bind plugin: Found %d %s for XPath expression `%s'", num_entries,
600         (num_entries == 1) ? "entry" : "entries", xpath_expression);
601
602   xmlXPathFreeObject(xpathObj);
603
604   return 0;
605 } /* }}} int bind_parse_generic_value_list */
606
607 /*
608  * bind_parse_generic_name_attr_value_list
609  *
610  * Reads statistics in the form:
611  * <foo>
612  *   <counter name="name0">123</counter>
613  *   <counter name="name1">234</counter>
614  *   <counter name="name2">345</counter>
615  *   :
616  * </foo>
617  */
618 static int bind_parse_generic_name_attr_value_list(
619     const char *xpath_expression, /* {{{ */
620     list_callback_t list_callback, void *user_data, xmlDoc *doc,
621     xmlXPathContext *xpathCtx, time_t current_time, int ds_type) {
622   xmlXPathObject *xpathObj = NULL;
623   int num_entries;
624
625   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
626   if (xpathObj == NULL) {
627     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
628           xpath_expression);
629     return -1;
630   }
631
632   num_entries = 0;
633   /* Iterate over all matching nodes. */
634   for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr);
635        i++) {
636     /* Iterate over all child nodes. */
637     for (xmlNode *child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
638          child != NULL; child = child->next) {
639       if (child->type != XML_ELEMENT_NODE)
640         continue;
641
642       if (strncmp("counter", (char *)child->name, strlen("counter")) != 0)
643         continue;
644
645       char *attr_name;
646       value_t value;
647       int status;
648
649       attr_name = (char *)xmlGetProp(child, BAD_CAST "name");
650       if (attr_name == NULL) {
651         DEBUG("bind plugin: found <counter> without name.");
652         continue;
653       }
654       if (ds_type == DS_TYPE_GAUGE)
655         status = bind_xml_read_gauge(doc, child, &value.gauge);
656       else
657         status = bind_xml_read_derive(doc, child, &value.derive);
658       if (status != 0)
659         continue;
660
661       status = (*list_callback)(attr_name, value, current_time, user_data);
662       if (status == 0)
663         num_entries++;
664     }
665   }
666
667   DEBUG("bind plugin: Found %d %s for XPath expression `%s'", num_entries,
668         (num_entries == 1) ? "entry" : "entries", xpath_expression);
669
670   xmlXPathFreeObject(xpathObj);
671
672   return 0;
673 } /* }}} int bind_parse_generic_name_attr_value_list */
674
675 static int bind_xml_stats_handle_zone(int version, xmlDoc *doc, /* {{{ */
676                                       xmlXPathContext *path_ctx, xmlNode *node,
677                                       cb_view_t *view, time_t current_time) {
678   xmlXPathObject *path_obj;
679   char *zone_name = NULL;
680   size_t j;
681
682   if (version >= 3) {
683     char *n = (char *)xmlGetProp(node, BAD_CAST "name");
684     char *c = (char *)xmlGetProp(node, BAD_CAST "rdataclass");
685     if (n && c) {
686       zone_name = (char *)xmlMalloc(strlen(n) + strlen(c) + 2);
687       snprintf(zone_name, strlen(n) + strlen(c) + 2, "%s/%s", n, c);
688     }
689     xmlFree(n);
690     xmlFree(c);
691   } else {
692     path_obj = xmlXPathEvalExpression(BAD_CAST "name", path_ctx);
693     if (path_obj == NULL) {
694       ERROR("bind plugin: xmlXPathEvalExpression failed.");
695       return -1;
696     }
697
698     for (int i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr);
699          i++) {
700       zone_name = (char *)xmlNodeListGetString(
701           doc, path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
702       if (zone_name != NULL)
703         break;
704     }
705     xmlXPathFreeObject(path_obj);
706   }
707
708   if (zone_name == NULL) {
709     ERROR("bind plugin: Could not determine zone name.");
710     return -1;
711   }
712
713   for (j = 0; j < view->zones_num; j++) {
714     if (strcasecmp(zone_name, view->zones[j]) == 0)
715       break;
716   }
717
718   xmlFree(zone_name);
719   zone_name = NULL;
720
721   if (j >= view->zones_num)
722     return 0;
723
724   zone_name = view->zones[j];
725
726   DEBUG("bind plugin: bind_xml_stats_handle_zone: Found zone `%s'.", zone_name);
727
728   { /* Parse the <counters> tag {{{ */
729     char plugin_instance[DATA_MAX_NAME_LEN];
730     translation_table_ptr_t table_ptr = {nsstats_translation_table,
731                                          nsstats_translation_table_length,
732                                          plugin_instance};
733
734     ssnprintf(plugin_instance, sizeof(plugin_instance), "%s-zone-%s",
735               view->name, zone_name);
736
737     if (version == 3) {
738       list_info_ptr_t list_info = {plugin_instance,
739                                    /* type = */ "dns_qtype"};
740       bind_parse_generic_name_attr_value_list(
741           /* xpath = */ "counters[@type='rcode']",
742           /* callback = */ bind_xml_table_callback,
743           /* user_data = */ &table_ptr, doc, path_ctx, current_time,
744           DS_TYPE_COUNTER);
745       bind_parse_generic_name_attr_value_list(
746           /* xpath = */ "counters[@type='qtype']",
747           /* callback = */ bind_xml_list_callback,
748           /* user_data = */ &list_info, doc, path_ctx, current_time,
749           DS_TYPE_COUNTER);
750     } else {
751       bind_parse_generic_value_list(/* xpath = */ "counters",
752                                     /* callback = */ bind_xml_table_callback,
753                                     /* user_data = */ &table_ptr, doc, path_ctx,
754                                     current_time, DS_TYPE_COUNTER);
755     }
756   } /* }}} */
757
758   return 0;
759 } /* }}} int bind_xml_stats_handle_zone */
760
761 static int bind_xml_stats_search_zones(int version, xmlDoc *doc, /* {{{ */
762                                        xmlXPathContext *path_ctx, xmlNode *node,
763                                        cb_view_t *view, time_t current_time) {
764   xmlXPathObject *zone_nodes = NULL;
765   xmlXPathContext *zone_path_context;
766
767   zone_path_context = xmlXPathNewContext(doc);
768   if (zone_path_context == NULL) {
769     ERROR("bind plugin: xmlXPathNewContext failed.");
770     return -1;
771   }
772
773   zone_nodes = xmlXPathEvalExpression(BAD_CAST "zones/zone", path_ctx);
774   if (zone_nodes == NULL) {
775     ERROR("bind plugin: Cannot find any <view> tags.");
776     xmlXPathFreeContext(zone_path_context);
777     return -1;
778   }
779
780   for (int i = 0; i < zone_nodes->nodesetval->nodeNr; i++) {
781     node = zone_nodes->nodesetval->nodeTab[i];
782     assert(node != NULL);
783
784     zone_path_context->node = node;
785
786     bind_xml_stats_handle_zone(version, doc, zone_path_context, node, view,
787                                current_time);
788   }
789
790   xmlXPathFreeObject(zone_nodes);
791   xmlXPathFreeContext(zone_path_context);
792   return 0;
793 } /* }}} int bind_xml_stats_search_zones */
794
795 static int bind_xml_stats_handle_view(int version, xmlDoc *doc, /* {{{ */
796                                       xmlXPathContext *path_ctx, xmlNode *node,
797                                       time_t current_time) {
798   char *view_name = NULL;
799   cb_view_t *view;
800   size_t j;
801
802   if (version == 3) {
803     view_name = (char *)xmlGetProp(node, BAD_CAST "name");
804
805     if (view_name == NULL) {
806       ERROR("bind plugin: Could not determine view name.");
807       return -1;
808     }
809
810     for (j = 0; j < views_num; j++) {
811       if (strcasecmp(view_name, views[j].name) == 0)
812         break;
813     }
814
815     xmlFree(view_name);
816     view_name = NULL;
817   } else {
818     xmlXPathObject *path_obj;
819     path_obj = xmlXPathEvalExpression(BAD_CAST "name", path_ctx);
820     if (path_obj == NULL) {
821       ERROR("bind plugin: xmlXPathEvalExpression failed.");
822       return -1;
823     }
824
825     for (int i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr);
826          i++) {
827       view_name = (char *)xmlNodeListGetString(
828           doc, path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
829       if (view_name != NULL)
830         break;
831     }
832
833     if (view_name == NULL) {
834       ERROR("bind plugin: Could not determine view name.");
835       xmlXPathFreeObject(path_obj);
836       return -1;
837     }
838
839     for (j = 0; j < views_num; j++) {
840       if (strcasecmp(view_name, views[j].name) == 0)
841         break;
842     }
843
844     xmlFree(view_name);
845     xmlXPathFreeObject(path_obj);
846
847     view_name = NULL;
848     path_obj = NULL;
849   }
850
851   if (j >= views_num)
852     return 0;
853
854   view = views + j;
855
856   DEBUG("bind plugin: bind_xml_stats_handle_view: Found view `%s'.",
857         view->name);
858
859   if (view->qtypes != 0) /* {{{ */
860   {
861     char plugin_instance[DATA_MAX_NAME_LEN];
862     list_info_ptr_t list_info = {plugin_instance,
863                                  /* type = */ "dns_qtype"};
864
865     ssnprintf(plugin_instance, sizeof(plugin_instance), "%s-qtypes",
866               view->name);
867     if (version == 3) {
868       bind_parse_generic_name_attr_value_list(
869           /* xpath = */ "counters[@type='resqtype']",
870           /* callback = */ bind_xml_list_callback,
871           /* user_data = */ &list_info, doc, path_ctx, current_time,
872           DS_TYPE_COUNTER);
873     } else {
874       bind_parse_generic_name_value(/* xpath = */ "rdtype",
875                                     /* callback = */ bind_xml_list_callback,
876                                     /* user_data = */ &list_info, doc, path_ctx,
877                                     current_time, DS_TYPE_COUNTER);
878     }
879   } /* }}} */
880
881   if (view->resolver_stats != 0) /* {{{ */
882   {
883     char plugin_instance[DATA_MAX_NAME_LEN];
884     translation_table_ptr_t table_ptr = {resstats_translation_table,
885                                          resstats_translation_table_length,
886                                          plugin_instance};
887
888     ssnprintf(plugin_instance, sizeof(plugin_instance), "%s-resolver_stats",
889               view->name);
890     if (version == 3) {
891       bind_parse_generic_name_attr_value_list(
892           "counters[@type='resstats']",
893           /* callback = */ bind_xml_table_callback,
894           /* user_data = */ &table_ptr, doc, path_ctx, current_time,
895           DS_TYPE_COUNTER);
896     } else {
897       bind_parse_generic_name_value("resstat",
898                                     /* callback = */ bind_xml_table_callback,
899                                     /* user_data = */ &table_ptr, doc, path_ctx,
900                                     current_time, DS_TYPE_COUNTER);
901     }
902   } /* }}} */
903
904   /* Record types in the cache */
905   if (view->cacherrsets != 0) /* {{{ */
906   {
907     char plugin_instance[DATA_MAX_NAME_LEN];
908     list_info_ptr_t list_info = {plugin_instance,
909                                  /* type = */ "dns_qtype_cached"};
910
911     ssnprintf(plugin_instance, sizeof(plugin_instance), "%s-cache_rr_sets",
912               view->name);
913
914     bind_parse_generic_name_value(/* xpath = */ "cache/rrset",
915                                   /* callback = */ bind_xml_list_callback,
916                                   /* user_data = */ &list_info, doc, path_ctx,
917                                   current_time, DS_TYPE_GAUGE);
918   } /* }}} */
919
920   if (view->zones_num > 0)
921     bind_xml_stats_search_zones(version, doc, path_ctx, node, view,
922                                 current_time);
923
924   return 0;
925 } /* }}} int bind_xml_stats_handle_view */
926
927 static int bind_xml_stats_search_views(int version, xmlDoc *doc, /* {{{ */
928                                        xmlXPathContext *xpathCtx,
929                                        xmlNode *statsnode,
930                                        time_t current_time) {
931   xmlXPathObject *view_nodes = NULL;
932   xmlXPathContext *view_path_context;
933
934   view_path_context = xmlXPathNewContext(doc);
935   if (view_path_context == NULL) {
936     ERROR("bind plugin: xmlXPathNewContext failed.");
937     return -1;
938   }
939
940   view_nodes = xmlXPathEvalExpression(BAD_CAST "views/view", xpathCtx);
941   if (view_nodes == NULL) {
942     ERROR("bind plugin: Cannot find any <view> tags.");
943     xmlXPathFreeContext(view_path_context);
944     return -1;
945   }
946
947   for (int i = 0; i < view_nodes->nodesetval->nodeNr; i++) {
948     xmlNode *node;
949
950     node = view_nodes->nodesetval->nodeTab[i];
951     assert(node != NULL);
952
953     view_path_context->node = node;
954
955     bind_xml_stats_handle_view(version, doc, view_path_context, node,
956                                current_time);
957   }
958
959   xmlXPathFreeObject(view_nodes);
960   xmlXPathFreeContext(view_path_context);
961   return 0;
962 } /* }}} int bind_xml_stats_search_views */
963
964 static void bind_xml_stats_v3(xmlDoc *doc, /* {{{ */
965                               xmlXPathContext *xpathCtx, xmlNode *statsnode,
966                               time_t current_time) {
967   /* XPath:     server/counters[@type='opcode']
968    * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
969    * Layout v3:
970    *   <counters type="opcode">
971    *     <counter name="A">1</counter>
972    *     :
973    *   </counters>
974    */
975   if (global_opcodes != 0) {
976     list_info_ptr_t list_info = {/* plugin instance = */ "global-opcodes",
977                                  /* type = */ "dns_opcode"};
978     bind_parse_generic_name_attr_value_list(
979         /* xpath = */ "server/counters[@type='opcode']",
980         /* callback = */ bind_xml_list_callback,
981         /* user_data = */ &list_info, doc, xpathCtx, current_time,
982         DS_TYPE_COUNTER);
983   }
984
985   /* XPath:     server/counters[@type='qtype']
986    * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
987    *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
988    *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
989    * Layout v3:
990    *   <counters type="opcode">
991    *     <counter name="A">1</counter>
992    *     :
993    *   </counters>
994    */
995   if (global_qtypes != 0) {
996     list_info_ptr_t list_info = {/* plugin instance = */ "global-qtypes",
997                                  /* type = */ "dns_qtype"};
998
999     bind_parse_generic_name_attr_value_list(
1000         /* xpath = */ "server/counters[@type='qtype']",
1001         /* callback = */ bind_xml_list_callback,
1002         /* user_data = */ &list_info, doc, xpathCtx, current_time,
1003         DS_TYPE_COUNTER);
1004   }
1005
1006   /* XPath:     server/counters[@type='nsstat']
1007    * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
1008    *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
1009    *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
1010    *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
1011    *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
1012    *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
1013    *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
1014    *            UpdateBadPrereq
1015    * Layout v3:
1016    *   <counters type="nsstat"
1017    *     <counter name="Requestv4">1</counter>
1018    *     <counter name="Requestv6">0</counter>
1019    *     :
1020    *   </counter>
1021    */
1022   if (global_server_stats) {
1023     translation_table_ptr_t table_ptr = {
1024         nsstats_translation_table, nsstats_translation_table_length,
1025         /* plugin_instance = */ "global-server_stats"};
1026
1027     bind_parse_generic_name_attr_value_list(
1028         "server/counters[@type='nsstat']",
1029         /* callback = */ bind_xml_table_callback,
1030         /* user_data = */ &table_ptr, doc, xpathCtx, current_time,
1031         DS_TYPE_COUNTER);
1032   }
1033
1034   /* XPath:     server/zonestats, server/zonestat,
1035    * server/counters[@type='zonestat']
1036    * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
1037    *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
1038    *            XfrSuccess, XfrFail
1039    * Layout v3:
1040    *   <counters type="zonestat"
1041    *     <counter name="NotifyOutv4">0</counter>
1042    *     <counter name="NotifyOutv6">0</counter>
1043    *     :
1044    *   </counter>
1045    */
1046   if (global_zone_maint_stats) {
1047     translation_table_ptr_t table_ptr = {
1048         zonestats_translation_table, zonestats_translation_table_length,
1049         /* plugin_instance = */ "global-zone_maint_stats"};
1050
1051     bind_parse_generic_name_attr_value_list(
1052         "server/counters[@type='zonestat']",
1053         /* callback = */ bind_xml_table_callback,
1054         /* user_data = */ &table_ptr, doc, xpathCtx, current_time,
1055         DS_TYPE_COUNTER);
1056   }
1057
1058   /* XPath:     server/resstats, server/counters[@type='resstat']
1059    * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
1060    *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
1061    *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
1062    *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
1063    * Layout v3:
1064    *   <counters type="resstat"
1065    *     <counter name="Queryv4">0</counter>
1066    *     <counter name="Queryv6">0</counter>
1067    *     :
1068    *   </counter>
1069    */
1070   if (global_resolver_stats != 0) {
1071     translation_table_ptr_t table_ptr = {
1072         resstats_translation_table, resstats_translation_table_length,
1073         /* plugin_instance = */ "global-resolver_stats"};
1074
1075     bind_parse_generic_name_attr_value_list(
1076         "server/counters[@type='resstat']",
1077         /* callback = */ bind_xml_table_callback,
1078         /* user_data = */ &table_ptr, doc, xpathCtx, current_time,
1079         DS_TYPE_COUNTER);
1080   }
1081 } /* }}} bind_xml_stats_v3 */
1082
1083 static void bind_xml_stats_v1_v2(int version, xmlDoc *doc, /* {{{ */
1084                                  xmlXPathContext *xpathCtx, xmlNode *statsnode,
1085                                  time_t current_time) {
1086   /* XPath:     server/requests/opcode, server/counters[@type='opcode']
1087    * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
1088    * Layout V1 and V2:
1089    *   <opcode>
1090    *     <name>A</name>
1091    *     <counter>1</counter>
1092    *   </opcode>
1093    *   :
1094    */
1095   if (global_opcodes != 0) {
1096     list_info_ptr_t list_info = {/* plugin instance = */ "global-opcodes",
1097                                  /* type = */ "dns_opcode"};
1098
1099     bind_parse_generic_name_value(/* xpath = */ "server/requests/opcode",
1100                                   /* callback = */ bind_xml_list_callback,
1101                                   /* user_data = */ &list_info, doc, xpathCtx,
1102                                   current_time, DS_TYPE_COUNTER);
1103   }
1104
1105   /* XPath:     server/queries-in/rdtype, server/counters[@type='qtype']
1106    * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
1107    *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
1108    *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
1109    * Layout v1 or v2:
1110    *   <rdtype>
1111    *     <name>A</name>
1112    *     <counter>1</counter>
1113    *   </rdtype>
1114    *   :
1115    */
1116   if (global_qtypes != 0) {
1117     list_info_ptr_t list_info = {/* plugin instance = */ "global-qtypes",
1118                                  /* type = */ "dns_qtype"};
1119
1120     bind_parse_generic_name_value(/* xpath = */ "server/queries-in/rdtype",
1121                                   /* callback = */ bind_xml_list_callback,
1122                                   /* user_data = */ &list_info, doc, xpathCtx,
1123                                   current_time, DS_TYPE_COUNTER);
1124   }
1125
1126   /* XPath:     server/nsstats, server/nsstat, server/counters[@type='nsstat']
1127    * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
1128    *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
1129    *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
1130    *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
1131    *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
1132    *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
1133    *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
1134    *            UpdateBadPrereq
1135    * Layout v1:
1136    *   <nsstats>
1137    *     <Requestv4>1</Requestv4>
1138    *     <Requestv6>0</Requestv6>
1139    *     :
1140    *   </nsstats>
1141    * Layout v2:
1142    *   <nsstat>
1143    *     <name>Requestv4</name>
1144    *     <counter>1</counter>
1145    *   </nsstat>
1146    *   <nsstat>
1147    *     <name>Requestv6</name>
1148    *     <counter>0</counter>
1149    *   </nsstat>
1150    *   :
1151    */
1152   if (global_server_stats) {
1153     translation_table_ptr_t table_ptr = {
1154         nsstats_translation_table, nsstats_translation_table_length,
1155         /* plugin_instance = */ "global-server_stats"};
1156
1157     if (version == 1) {
1158       bind_parse_generic_value_list("server/nsstats",
1159                                     /* callback = */ bind_xml_table_callback,
1160                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1161                                     current_time, DS_TYPE_COUNTER);
1162     } else {
1163       bind_parse_generic_name_value("server/nsstat",
1164                                     /* callback = */ bind_xml_table_callback,
1165                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1166                                     current_time, DS_TYPE_COUNTER);
1167     }
1168   }
1169
1170   /* XPath:     server/zonestats, server/zonestat,
1171    * server/counters[@type='zonestat']
1172    * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
1173    *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
1174    *            XfrSuccess, XfrFail
1175    * Layout v1:
1176    *   <zonestats>
1177    *     <NotifyOutv4>0</NotifyOutv4>
1178    *     <NotifyOutv6>0</NotifyOutv6>
1179    *     :
1180    *   </zonestats>
1181    * Layout v2:
1182    *   <zonestat>
1183    *     <name>NotifyOutv4</name>
1184    *     <counter>0</counter>
1185    *   </zonestat>
1186    *   <zonestat>
1187    *     <name>NotifyOutv6</name>
1188    *     <counter>0</counter>
1189    *   </zonestat>
1190    *   :
1191    */
1192   if (global_zone_maint_stats) {
1193     translation_table_ptr_t table_ptr = {
1194         zonestats_translation_table, zonestats_translation_table_length,
1195         /* plugin_instance = */ "global-zone_maint_stats"};
1196
1197     if (version == 1) {
1198       bind_parse_generic_value_list("server/zonestats",
1199                                     /* callback = */ bind_xml_table_callback,
1200                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1201                                     current_time, DS_TYPE_COUNTER);
1202     } else {
1203       bind_parse_generic_name_value("server/zonestat",
1204                                     /* callback = */ bind_xml_table_callback,
1205                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1206                                     current_time, DS_TYPE_COUNTER);
1207     }
1208   }
1209
1210   /* XPath:     server/resstats, server/counters[@type='resstat']
1211    * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
1212    *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
1213    *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
1214    *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
1215    * Layout v1:
1216    *   <resstats>
1217    *     <Queryv4>0</Queryv4>
1218    *     <Queryv6>0</Queryv6>
1219    *     :
1220    *   </resstats>
1221    * Layout v2:
1222    *   <resstat>
1223    *     <name>Queryv4</name>
1224    *     <counter>0</counter>
1225    *   </resstat>
1226    *   <resstat>
1227    *     <name>Queryv6</name>
1228    *     <counter>0</counter>
1229    *   </resstat>
1230    *   :
1231    */
1232   if (global_resolver_stats != 0) {
1233     translation_table_ptr_t table_ptr = {
1234         resstats_translation_table, resstats_translation_table_length,
1235         /* plugin_instance = */ "global-resolver_stats"};
1236
1237     if (version == 1) {
1238       bind_parse_generic_value_list("server/resstats",
1239                                     /* callback = */ bind_xml_table_callback,
1240                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1241                                     current_time, DS_TYPE_COUNTER);
1242     } else {
1243       bind_parse_generic_name_value("server/resstat",
1244                                     /* callback = */ bind_xml_table_callback,
1245                                     /* user_data = */ &table_ptr, doc, xpathCtx,
1246                                     current_time, DS_TYPE_COUNTER);
1247     }
1248   }
1249 } /* }}} bind_xml_stats_v1_v2 */
1250
1251 static int bind_xml_stats(int version, xmlDoc *doc, /* {{{ */
1252                           xmlXPathContext *xpathCtx, xmlNode *statsnode) {
1253   time_t current_time = 0;
1254   int status;
1255
1256   xpathCtx->node = statsnode;
1257
1258   /* TODO: Check `server/boot-time' to recognize server restarts. */
1259
1260   status = bind_xml_read_timestamp("server/current-time", doc, xpathCtx,
1261                                    &current_time);
1262   if (status != 0) {
1263     ERROR("bind plugin: Reading `server/current-time' failed.");
1264     return -1;
1265   }
1266   DEBUG("bind plugin: Current server time is %i.", (int)current_time);
1267
1268   if (version == 3) {
1269     bind_xml_stats_v3(doc, xpathCtx, statsnode, current_time);
1270   } else {
1271     bind_xml_stats_v1_v2(version, doc, xpathCtx, statsnode, current_time);
1272   }
1273
1274   /* XPath:  memory/summary
1275    * Variables: TotalUse, InUse, BlockSize, ContextSize, Lost
1276    * Layout: v2 and v3:
1277    *   <summary>
1278    *     <TotalUse>6587096</TotalUse>
1279    *     <InUse>1345424</InUse>
1280    *     <BlockSize>5505024</BlockSize>
1281    *     <ContextSize>3732456</ContextSize>
1282    *     <Lost>0</Lost>
1283    *   </summary>
1284    */
1285   if (global_memory_stats != 0) {
1286     translation_table_ptr_t table_ptr = {
1287         memsummary_translation_table, memsummary_translation_table_length,
1288         /* plugin_instance = */ "global-memory_stats"};
1289
1290     bind_parse_generic_value_list("memory/summary",
1291                                   /* callback = */ bind_xml_table_callback,
1292                                   /* user_data = */ &table_ptr, doc, xpathCtx,
1293                                   current_time, DS_TYPE_GAUGE);
1294   }
1295
1296   if (views_num > 0)
1297     bind_xml_stats_search_views(version, doc, xpathCtx, statsnode,
1298                                 current_time);
1299
1300   return 0;
1301 } /* }}} int bind_xml_stats */
1302
1303 static int bind_xml(const char *data) /* {{{ */
1304 {
1305   xmlDoc *doc = NULL;
1306   xmlXPathContext *xpathCtx = NULL;
1307   xmlXPathObject *xpathObj = NULL;
1308   int ret = -1;
1309
1310   doc = xmlParseMemory(data, strlen(data));
1311   if (doc == NULL) {
1312     ERROR("bind plugin: xmlParseMemory failed.");
1313     return -1;
1314   }
1315
1316   xpathCtx = xmlXPathNewContext(doc);
1317   if (xpathCtx == NULL) {
1318     ERROR("bind plugin: xmlXPathNewContext failed.");
1319     xmlFreeDoc(doc);
1320     return -1;
1321   }
1322
1323   //
1324   // version 3.* of statistics XML (since BIND9.9)
1325   //
1326
1327   xpathObj = xmlXPathEvalExpression(BAD_CAST "/statistics", xpathCtx);
1328   if (xpathObj == NULL || xpathObj->nodesetval == NULL ||
1329       xpathObj->nodesetval->nodeNr == 0) {
1330     DEBUG("bind plugin: Statistics appears not to be v3");
1331     // we will fallback to v1 or v2 detection
1332     if (xpathObj != NULL) {
1333       xmlXPathFreeObject(xpathObj);
1334     }
1335   } else {
1336     for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++) {
1337       xmlNode *node;
1338       char *attr_version;
1339
1340       node = xpathObj->nodesetval->nodeTab[i];
1341       assert(node != NULL);
1342
1343       attr_version = (char *)xmlGetProp(node, BAD_CAST "version");
1344       if (attr_version == NULL) {
1345         NOTICE("bind plugin: Found <statistics> tag doesn't have a "
1346                "`version' attribute.");
1347         continue;
1348       }
1349       DEBUG("bind plugin: Found: <statistics version=\"%s\">", attr_version);
1350
1351       if (strncmp("3.", attr_version, strlen("3.")) != 0) {
1352         /* TODO: Use the complaint mechanism here. */
1353         NOTICE("bind plugin: Found <statistics> tag with version `%s'. "
1354                "Unfortunately I have no clue how to parse that. "
1355                "Please open a bug report for this.",
1356                attr_version);
1357         xmlFree(attr_version);
1358         continue;
1359       }
1360       ret = bind_xml_stats(3, doc, xpathCtx, node);
1361
1362       xmlFree(attr_version);
1363       /* One <statistics> node ought to be enough. */
1364       break;
1365     }
1366
1367     // we are finished, early-return
1368     xmlXPathFreeObject(xpathObj);
1369     xmlXPathFreeContext(xpathCtx);
1370     xmlFreeDoc(doc);
1371
1372     return ret;
1373   }
1374
1375   //
1376   // versions 1.* or 2.* of statistics XML
1377   //
1378
1379   xpathObj = xmlXPathEvalExpression(BAD_CAST "/isc/bind/statistics", xpathCtx);
1380   if (xpathObj == NULL) {
1381     ERROR("bind plugin: Cannot find the <statistics> tag.");
1382     xmlXPathFreeContext(xpathCtx);
1383     xmlFreeDoc(doc);
1384     return -1;
1385   } else if (xpathObj->nodesetval == NULL) {
1386     ERROR("bind plugin: xmlXPathEvalExpression failed.");
1387     xmlXPathFreeObject(xpathObj);
1388     xmlXPathFreeContext(xpathCtx);
1389     xmlFreeDoc(doc);
1390     return -1;
1391   }
1392
1393   for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++) {
1394     xmlNode *node;
1395     char *attr_version;
1396     int parsed_version = 0;
1397
1398     node = xpathObj->nodesetval->nodeTab[i];
1399     assert(node != NULL);
1400
1401     attr_version = (char *)xmlGetProp(node, BAD_CAST "version");
1402     if (attr_version == NULL) {
1403       NOTICE("bind plugin: Found <statistics> tag doesn't have a "
1404              "`version' attribute.");
1405       continue;
1406     }
1407     DEBUG("bind plugin: Found: <statistics version=\"%s\">", attr_version);
1408
1409     /* At the time this plugin was written, version "1.0" was used by
1410      * BIND 9.5.0, version "2.0" was used by BIND 9.5.1 and 9.6.0. We assume
1411      * that "1.*" and "2.*" don't introduce structural changes, so we just
1412      * check for the first two characters here. */
1413     if (strncmp("1.", attr_version, strlen("1.")) == 0)
1414       parsed_version = 1;
1415     else if (strncmp("2.", attr_version, strlen("2.")) == 0)
1416       parsed_version = 2;
1417     else {
1418       /* TODO: Use the complaint mechanism here. */
1419       NOTICE("bind plugin: Found <statistics> tag with version `%s'. "
1420              "Unfortunately I have no clue how to parse that. "
1421              "Please open a bug report for this.",
1422              attr_version);
1423       xmlFree(attr_version);
1424       continue;
1425     }
1426
1427     ret = bind_xml_stats(parsed_version, doc, xpathCtx, node);
1428
1429     xmlFree(attr_version);
1430     /* One <statistics> node ought to be enough. */
1431     break;
1432   }
1433
1434   xmlXPathFreeObject(xpathObj);
1435   xmlXPathFreeContext(xpathCtx);
1436   xmlFreeDoc(doc);
1437
1438   return ret;
1439 } /* }}} int bind_xml */
1440
1441 static int bind_config_set_bool(const char *name, int *var, /* {{{ */
1442                                 oconfig_item_t *ci) {
1443   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
1444     WARNING("bind plugin: The `%s' option needs "
1445             "exactly one boolean argument.",
1446             name);
1447     return -1;
1448   }
1449
1450   if (ci->values[0].value.boolean)
1451     *var = 1;
1452   else
1453     *var = 0;
1454   return 0;
1455 } /* }}} int bind_config_set_bool */
1456
1457 static int bind_config_add_view_zone(cb_view_t *view, /* {{{ */
1458                                      oconfig_item_t *ci) {
1459   char **tmp;
1460
1461   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
1462     WARNING("bind plugin: The `Zone' option needs "
1463             "exactly one string argument.");
1464     return -1;
1465   }
1466
1467   tmp = realloc(view->zones, sizeof(char *) * (view->zones_num + 1));
1468   if (tmp == NULL) {
1469     ERROR("bind plugin: realloc failed.");
1470     return -1;
1471   }
1472   view->zones = tmp;
1473
1474   view->zones[view->zones_num] = strdup(ci->values[0].value.string);
1475   if (view->zones[view->zones_num] == NULL) {
1476     ERROR("bind plugin: strdup failed.");
1477     return -1;
1478   }
1479   view->zones_num++;
1480
1481   return 0;
1482 } /* }}} int bind_config_add_view_zone */
1483
1484 static int bind_config_add_view(oconfig_item_t *ci) /* {{{ */
1485 {
1486   cb_view_t *tmp;
1487
1488   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
1489     WARNING("bind plugin: `View' blocks need exactly one string argument.");
1490     return -1;
1491   }
1492
1493   tmp = realloc(views, sizeof(*views) * (views_num + 1));
1494   if (tmp == NULL) {
1495     ERROR("bind plugin: realloc failed.");
1496     return -1;
1497   }
1498   views = tmp;
1499   tmp = views + views_num;
1500
1501   memset(tmp, 0, sizeof(*tmp));
1502   tmp->qtypes = 1;
1503   tmp->resolver_stats = 1;
1504   tmp->cacherrsets = 1;
1505   tmp->zones = NULL;
1506   tmp->zones_num = 0;
1507
1508   tmp->name = strdup(ci->values[0].value.string);
1509   if (tmp->name == NULL) {
1510     ERROR("bind plugin: strdup failed.");
1511     sfree(views);
1512     return -1;
1513   }
1514
1515   for (int i = 0; i < ci->children_num; i++) {
1516     oconfig_item_t *child = ci->children + i;
1517
1518     if (strcasecmp("QTypes", child->key) == 0)
1519       bind_config_set_bool("QTypes", &tmp->qtypes, child);
1520     else if (strcasecmp("ResolverStats", child->key) == 0)
1521       bind_config_set_bool("ResolverStats", &tmp->resolver_stats, child);
1522     else if (strcasecmp("CacheRRSets", child->key) == 0)
1523       bind_config_set_bool("CacheRRSets", &tmp->cacherrsets, child);
1524     else if (strcasecmp("Zone", child->key) == 0)
1525       bind_config_add_view_zone(tmp, child);
1526     else {
1527       WARNING("bind plugin: Unknown configuration option "
1528               "`%s' in view `%s' will be ignored.",
1529               child->key, tmp->name);
1530     }
1531   } /* for (i = 0; i < ci->children_num; i++) */
1532
1533   views_num++;
1534   return 0;
1535 } /* }}} int bind_config_add_view */
1536
1537 static int bind_config(oconfig_item_t *ci) /* {{{ */
1538 {
1539   for (int i = 0; i < ci->children_num; i++) {
1540     oconfig_item_t *child = ci->children + i;
1541
1542     if (strcasecmp("Url", child->key) == 0) {
1543       if ((child->values_num != 1) ||
1544           (child->values[0].type != OCONFIG_TYPE_STRING)) {
1545         WARNING("bind plugin: The `Url' option needs "
1546                 "exactly one string argument.");
1547         return -1;
1548       }
1549
1550       sfree(url);
1551       url = strdup(child->values[0].value.string);
1552     } else if (strcasecmp("OpCodes", child->key) == 0)
1553       bind_config_set_bool("OpCodes", &global_opcodes, child);
1554     else if (strcasecmp("QTypes", child->key) == 0)
1555       bind_config_set_bool("QTypes", &global_qtypes, child);
1556     else if (strcasecmp("ServerStats", child->key) == 0)
1557       bind_config_set_bool("ServerStats", &global_server_stats, child);
1558     else if (strcasecmp("ZoneMaintStats", child->key) == 0)
1559       bind_config_set_bool("ZoneMaintStats", &global_zone_maint_stats, child);
1560     else if (strcasecmp("ResolverStats", child->key) == 0)
1561       bind_config_set_bool("ResolverStats", &global_resolver_stats, child);
1562     else if (strcasecmp("MemoryStats", child->key) == 0)
1563       bind_config_set_bool("MemoryStats", &global_memory_stats, child);
1564     else if (strcasecmp("View", child->key) == 0)
1565       bind_config_add_view(child);
1566     else if (strcasecmp("ParseTime", child->key) == 0)
1567       cf_util_get_boolean(child, &config_parse_time);
1568     else if (strcasecmp("Timeout", child->key) == 0)
1569       cf_util_get_int(child, &timeout);
1570     else {
1571       WARNING("bind plugin: Unknown configuration option "
1572               "`%s' will be ignored.",
1573               child->key);
1574     }
1575   }
1576
1577   return 0;
1578 } /* }}} int bind_config */
1579
1580 static int bind_init(void) /* {{{ */
1581 {
1582   if (curl != NULL)
1583     return 0;
1584
1585   curl = curl_easy_init();
1586   if (curl == NULL) {
1587     ERROR("bind plugin: bind_init: curl_easy_init failed.");
1588     return -1;
1589   }
1590
1591   curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
1592   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, bind_curl_callback);
1593   curl_easy_setopt(curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
1594   curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, bind_curl_error);
1595   curl_easy_setopt(curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
1596   curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1597   curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
1598 #ifdef HAVE_CURLOPT_TIMEOUT_MS
1599   curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS,
1600                    (timeout >= 0) ? (long)timeout : (long)CDTIME_T_TO_MS(
1601                                                         plugin_get_interval()));
1602 #endif
1603
1604   return 0;
1605 } /* }}} int bind_init */
1606
1607 static int bind_read(void) /* {{{ */
1608 {
1609   int status;
1610
1611   if (curl == NULL) {
1612     ERROR("bind plugin: I don't have a CURL object.");
1613     return -1;
1614   }
1615
1616   bind_buffer_fill = 0;
1617   if (curl_easy_perform(curl) != CURLE_OK) {
1618     ERROR("bind plugin: curl_easy_perform failed: %s", bind_curl_error);
1619     return -1;
1620   }
1621
1622   status = bind_xml(bind_buffer);
1623   if (status != 0)
1624     return -1;
1625   else
1626     return 0;
1627 } /* }}} int bind_read */
1628
1629 static int bind_shutdown(void) /* {{{ */
1630 {
1631   if (curl != NULL) {
1632     curl_easy_cleanup(curl);
1633     curl = NULL;
1634   }
1635
1636   return 0;
1637 } /* }}} int bind_shutdown */
1638
1639 void module_register(void) {
1640   plugin_register_complex_config("bind", bind_config);
1641   plugin_register_init("bind", bind_init);
1642   plugin_register_read("bind", bind_read);
1643   plugin_register_shutdown("bind", bind_shutdown);
1644 } /* void module_register */