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