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