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