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