bind plugin: Add a new plugin for BIND 9.5 and 9.6 statistics.
[collectd.git] / src / bind.c
1 /**
2  * collectd - src/bind.c
3  * Copyright (C) 2009  Bruno PrĂ©mont
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Bruno PrĂ©mont <bonbons at linux-vserver.org>
20  **/
21
22 #define _ISOC99_SOURCE
23 #define _XOPEN_SOURCE
24 #define _BSD_SOURCE
25 #include "collectd.h"
26 #include "common.h"
27 #include "plugin.h"
28 #include "configfile.h"
29
30 #include <curl/curl.h>
31 #include <libxml/parser.h>
32 #include <libxml/xpath.h>
33
34 static char *url              = NULL;
35 static bool use_rrqueries_in  = 1;
36 static bool use_requests      = 1;
37 static bool use_query_results = 1;
38 static bool use_updates       = 1;
39 static bool use_zone_maint    = 1;
40 static bool use_resolver      = 1;
41 static char *srv_boot_ts      = NULL;
42
43 static CURL *curl = NULL;
44
45 static char  *bind_buffer = NULL;
46 static size_t bind_buffer_size = 0;
47 static size_t bind_buffer_fill = 0;
48 static char   bind_curl_error[CURL_ERROR_SIZE];
49
50 static const char *config_keys[] =
51 {
52   "URL",
53   "RRQueriesIn",
54   "Requests"
55   "QueryResults",
56   "Updates",
57   "ZoneMaintenance",
58   "Resolver",
59 };
60 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
61
62 static void submit_counter(time_t ts, const char *plugin_instance, const char *type,
63     const char *type_instance, counter_t value)
64 {
65   value_t values[1];
66   value_list_t vl = VALUE_LIST_INIT;
67   char *p;
68
69   values[0].counter = value;
70
71   vl.values = values;
72   vl.values_len = 1;
73   vl.time = ts == 0 ? time(NULL) : ts;
74   sstrncpy(vl.host, hostname_g, sizeof(vl.host));
75   sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
76   if (plugin_instance) {
77     sstrncpy(vl.plugin_instance, plugin_instance,
78         sizeof(vl.plugin_instance));
79     for (p = vl.type_instance; *p; p++)
80       if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')  && (*p < '0' || *p > '9') && *p != '_')
81         *p = '_';
82   }
83   sstrncpy(vl.type, type, sizeof(vl.type));
84   if (type_instance) {
85     sstrncpy(vl.type_instance, type_instance,
86         sizeof(vl.type_instance));
87     for (p = vl.type_instance; *p; p++)
88       if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')  && (*p < '0' || *p > '9') && *p != '_')
89         *p = '_';
90   }
91   plugin_dispatch_values(&vl);
92 } /* void submit_counter */
93
94 static size_t bind_curl_callback (void *buf, size_t size, size_t nmemb,
95     void *stream)
96 {
97   size_t len = size * nmemb;
98
99   if (len <= 0)
100     return (len);
101
102   if ((bind_buffer_fill + len) >= bind_buffer_size)
103   {
104     char *temp;
105
106     temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
107     if (temp == NULL)
108     {
109       ERROR ("bind plugin: realloc failed.");
110       return (0);
111     }
112     bind_buffer = temp;
113     bind_buffer_size = bind_buffer_fill + len + 1;
114   }
115
116   memcpy (bind_buffer + bind_buffer_fill, (char *) buf, len);
117   bind_buffer_fill += len;
118   bind_buffer[bind_buffer_fill] = 0;
119
120   return (len);
121 } /* size_t bind_curl_callback */
122
123 static int bind_xml_read_counter (xmlDoc *doc, xmlNode *node, 
124     counter_t *ret_value)
125 {
126   char *str_ptr, *end_ptr;
127   long long int value;
128
129   str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
130   if (str_ptr == NULL)
131   {
132     ERROR ("bind plugin: bind_xml_read_int64: xmlNodeListGetString failed.");
133     return (-1);
134   }
135
136   errno = 0;
137   value = strtoll (str_ptr, &end_ptr, 10);
138   xmlFree(str_ptr);
139   if (str_ptr == end_ptr || errno)
140   {
141     if (errno && value == LLONG_MIN)
142       ERROR ("bind plugin: bind_xml_read_int64: strtoll failed with underflow.");
143     else if (errno && value == LLONG_MAX)
144       ERROR ("bind plugin: bind_xml_read_int64: strtoll failed with overflow.");
145     else
146       ERROR ("bind plugin: bind_xml_read_int64: strtoll failed.");
147     return (-1);
148   }
149
150   *ret_value = value;
151   return (0);
152 } /* int bind_xml_read_counter */
153
154 static int bind_xml_read_timestamp (xmlDoc *doc, xmlNode *node,
155     time_t *ret_value)
156 {
157   char *str_ptr, *p;
158   struct tm tm;
159
160   str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
161   if (str_ptr == NULL)
162   {
163     ERROR ("bind plugin: bind_xml_read_int: xmlNodeListGetString failed.");
164     return (-1);
165   }
166
167   memset(&tm, 0, sizeof(tm));
168   p = strptime(str_ptr, "%Y-%m-%dT%T", &tm);
169   xmlFree(str_ptr);
170   if (p == NULL)
171   {
172     ERROR ("bind plugin: bind_xml_read_timestamp: strptime failed.");
173     return (-1);
174   }
175
176   *ret_value = timegm(&tm);
177   return (0);
178 } /* int bind_xml_read_timestamp */
179
180 /* Bind 9.5.x */
181 static int bind_xml_stats_v1(xmlDoc *doc, xmlXPathContext *xpathCtx, xmlNode *statsnode)
182 {
183   xmlXPathObjectPtr xpathObj = NULL;
184   time_t current_time;
185   int i;
186
187   xpathCtx->node = statsnode;
188
189   /* server/boot-time -- detect possible counter-resets
190    * Type: XML DateTime */
191   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/boot-time", xpathCtx)) == NULL) {
192     ERROR("bind plugin: unable to evaluate XPath expression StatsV1-boottime");
193     return -1;
194   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
195     char *boot_tm = (char *) xmlNodeListGetString (doc, xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode, 1);
196     if (srv_boot_ts == NULL || strcmp(srv_boot_ts, boot_tm) != 0) {
197       xmlFree(srv_boot_ts);
198       srv_boot_ts = boot_tm;
199       /* TODO: tell collectd that our counters got reset ... */
200       DEBUG ("bind plugin: Statv1: Server boot time: %s (%d nodes)", srv_boot_ts, xpathObj->nodesetval->nodeNr);
201     } else
202       xmlFree(boot_tm);
203   }
204   xmlXPathFreeObject(xpathObj);
205
206   /* server/current-time -- parse our time-stamp
207    * Type: XML DateTime */
208   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/current-time", xpathCtx)) == NULL) {
209     ERROR("bind plugin: unable to evaluate XPath expression StatsV1-currenttime");
210     return -1;
211   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
212     if (bind_xml_read_timestamp(doc, xpathObj->nodesetval->nodeTab[0], &current_time))
213       current_time = time(NULL);
214     else
215       DEBUG ("bind plugin: Statv1: Server current time: %ld (%d nodes)", current_time, xpathObj->nodesetval->nodeNr);
216   }
217   xmlXPathFreeObject(xpathObj);
218
219   /* requests/opcode -- [name] = [counter]
220    * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ... */
221   if (use_requests) {
222     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/requests/opcode", xpathCtx)) == NULL) {
223       ERROR("bind plugin: unable to evaluate XPath expression StatsV1-currenttime");
224       return -1;
225     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
226       xmlNode *name = NULL, *counter = NULL, *child;
227       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
228         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
229           name = child;
230         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
231           counter = child;
232       if (name && counter) {
233         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
234         counter_t val;
235         if (!bind_xml_read_counter(doc, counter, &val))
236           submit_counter(current_time, NULL, "dns_opcode", tinst, val);
237         xmlFree(tinst);
238       }
239     }
240     DEBUG ("bind plugin: Statv1: Found %d entries for requests/opcode", xpathObj->nodesetval->nodeNr);
241     xmlXPathFreeObject(xpathObj);
242   }
243
244   /* queries-in/rdtype -- [name] = [counter]
245    * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP, X25, PX, AAAA, LOC,
246    *            SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY, SPF, TKEY, IXFR, AXFR, ANY, ..., Others */
247   if (use_rrqueries_in) {
248     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/queries-in/rdtype", xpathCtx)) == NULL) {
249       ERROR("bind plugin: unable to evaluate XPath expression StatsV1-currenttime");
250       return -1;
251     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
252       xmlNode *name = NULL, *counter = NULL, *child;
253       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
254         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
255           name = child;
256         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
257           counter = child;
258       if (name && counter) {
259         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
260         counter_t val;
261         if (!bind_xml_read_counter(doc, counter, &val))
262           submit_counter(current_time, NULL, "dns_qtype", tinst, val);
263         xmlFree(tinst);
264       }
265     }
266     DEBUG ("bind plugin: Statv1: Found %d entries for queries-in/rdtype", xpathObj->nodesetval->nodeNr);
267     xmlXPathFreeObject(xpathObj);
268   }
269
270   /* nsstats -- [$name] = [$value] */
271   /* Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG, ReqSIG0, ReqBadSIG, ReqTCP,
272    *            AuthQryRej, RecQryRej, XfrRej, UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
273    *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral, QryNxrrset, QrySERVFAIL,
274    *            QryFORMERR, QryNXDOMAIN, QryRecursion, QryDuplicate, QryDropped, QryFailure, XfrReqDone,
275    *            UpdateReqFwd, UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail, UpdateBadPrereq */
276   if (use_updates || use_query_results || use_requests) {
277     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/nsstats", xpathCtx)) == NULL) {
278       ERROR("bind plugin: unable to evaluate XPath expression StatsV1-nsstats");
279       return -1;
280     } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
281       xmlNode *child;
282       counter_t val;
283       int n = 0;
284       for (child = xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode; child != NULL; child = child->next, n++)
285         if (child->type == XML_ELEMENT_NODE && !bind_xml_read_counter(doc, child, &val)) {
286           if (use_updates && (strncmp("Update", (char *)child->name, 6) == 0 || strcmp("XfrReqDone", (char *)child->name) == 0))
287             submit_counter(current_time, NULL, "dns_update", (char *)child->name, val);
288           else if (use_query_results && strncmp("Qry", (char *)child->name, 3) == 0)
289             submit_counter(current_time, NULL, "dns_rcode", (char *)child->name, val);
290           else if (use_query_results && (strncmp("Resp", (char *)child->name, 4) == 0 ||
291                 strcmp("AuthQryRej", (char *)child->name) == 0 || strcmp("RecQryRej", (char *)child->name) == 0 ||
292                 strcmp("XfrRej", (char *)child->name) == 0 || strcmp("TruncatedResp", (char *)child->name) == 0))
293             submit_counter(current_time, NULL, "dns_rcode", (char *)child->name, val);
294           else if (use_requests && strncmp("Req", (char *)child->name, 3) == 0)
295             submit_counter(current_time, NULL, "dns_request", (char *)child->name, val);
296         }
297       DEBUG ("bind plugin: Statv1: Found %d entries for %d nsstats", n, xpathObj->nodesetval->nodeNr);
298     }
299     xmlXPathFreeObject(xpathObj);
300   }
301
302   /* zonestats -- [$name] = [$value] */
303   /* Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej, SOAOutv4, SOAOutv6,
304    *            AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6, XfrSuccess, XfrFail */
305   if (use_zone_maint) {
306     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/zonestats", xpathCtx)) == NULL) {
307       ERROR("bind plugin: unable to evaluate XPath expression StatsV1-zonestats");
308       return -1;
309     } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
310       xmlNode *child;
311       counter_t val;
312       int n = 0;
313       for (child = xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode; child != NULL; child = child->next, n++)
314         if (child->type == XML_ELEMENT_NODE) {
315           if (!bind_xml_read_counter(doc, child, &val))
316             submit_counter(current_time, NULL, "dns_zops", (char *)child->name, val);
317         }
318       DEBUG ("bind plugin: Statv1: Found %d entries for %d zonestats", n, xpathObj->nodesetval->nodeNr);
319     }
320     xmlXPathFreeObject(xpathObj);
321   }
322
323   /* resstats -- [$name] = [$value] */
324   /* Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL, FORMERR, OtherError,
325    *            EDNS0Fail, Mismatch, Truncated, Lame, Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
326    *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail */
327   if (use_resolver) {
328     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/resstats", xpathCtx)) == NULL) {
329       ERROR("bind plugin: unable to evaluate XPath expression StatsV1-resstats");
330       return -1;
331     } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
332       xmlNode *child;
333       counter_t val;
334       int n = 0;
335       for (child = xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode; child != NULL; child = child->next, n++)
336         if (child->type == XML_ELEMENT_NODE) {
337           if (!bind_xml_read_counter(doc, child, &val))
338             submit_counter(current_time, NULL, "dns_resolver", (char *)child->name, val);
339         }
340       DEBUG ("bind plugin: Statv1: Found %d entries for %d resstats", n, xpathObj->nodesetval->nodeNr);
341     }
342     xmlXPathFreeObject(xpathObj);
343   }
344   return 0;
345 }
346
347 /* Bind 9.6.x */
348 static int bind_xml_stats_v2(xmlDoc *doc, xmlXPathContext *xpathCtx, xmlNode *statsnode)
349 {
350   xmlXPathObjectPtr xpathObj = NULL;
351   time_t current_time;
352   int i;
353
354   xpathCtx->node = statsnode;
355
356   /* server/boot-time -- detect possible counter-resets
357    * Type: XML DateTime */
358   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/boot-time", xpathCtx)) == NULL) {
359     ERROR("bind plugin: unable to evaluate XPath expression StatsV2-boottime");
360     return -1;
361   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
362     char *boot_tm = (char *) xmlNodeListGetString (doc, xpathObj->nodesetval->nodeTab[0]->xmlChildrenNode, 1);
363     if (srv_boot_ts == NULL || strcmp(srv_boot_ts, boot_tm) != 0) {
364       xmlFree(srv_boot_ts);
365       srv_boot_ts = boot_tm;
366       /* TODO: tell collectd that our counters got reset ... */
367       DEBUG ("bind plugin: Statv2: Server boot time: %s (%d nodes)", srv_boot_ts, xpathObj->nodesetval->nodeNr);
368     } else
369       xmlFree(boot_tm);
370   }
371   xmlXPathFreeObject(xpathObj);
372
373   /* server/current-time -- parse our time-stamp
374    * Type: XML DateTime */
375   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/current-time", xpathCtx)) == NULL) {
376     ERROR("bind plugin: unable to evaluate XPath expression StatsV2-currenttime");
377     return -1;
378   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
379     if (bind_xml_read_timestamp(doc, xpathObj->nodesetval->nodeTab[0], &current_time))
380       current_time = time(NULL);
381     else
382       DEBUG ("bind plugin: Statv2: Server current time: %ld (%d nodes)", current_time, xpathObj->nodesetval->nodeNr);
383   }
384   xmlXPathFreeObject(xpathObj);
385
386   /* requests/opcode -- [name] = [counter]
387    * Variables: QUERY, ... */
388   if (use_requests) {
389     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/requests/opcode", xpathCtx)) == NULL) {
390       ERROR("bind plugin: unable to evaluate XPath expression StatsV2-opcode");
391       return -1;
392     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
393       xmlNode *name = NULL, *counter = NULL, *child;
394       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
395         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
396           name = child;
397         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
398           counter = child;
399       if (name && counter) {
400         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
401         counter_t val;
402         if (!bind_xml_read_counter(doc, counter, &val))
403           submit_counter(current_time, NULL, "dns_opcode", tinst, val);
404         xmlFree(tinst);
405       }
406     }
407     DEBUG ("bind plugin: Statv2: Found %d entries for requests/opcode", xpathObj->nodesetval->nodeNr);
408     xmlXPathFreeObject(xpathObj);
409   }
410
411   /* queries-in/rdtype -- [name] = [counter]
412    * Variables: A, NS, SOA, PTR, MX, TXT, SRV, ... */
413   if (use_rrqueries_in) {
414     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/queries-in/rdtype", xpathCtx)) == NULL) {
415       ERROR("bind plugin: unable to evaluate XPath expression StatsV2-rdtype");
416       return -1;
417     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
418       xmlNode *name = NULL, *counter = NULL, *child;
419       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
420         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
421           name = child;
422         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
423           counter = child;
424       if (name && counter) {
425         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
426         counter_t val;
427         if (!bind_xml_read_counter(doc, counter, &val))
428           submit_counter(current_time, NULL, "dns_qtype", tinst, val);
429         xmlFree(tinst);
430       }
431     }
432     DEBUG ("bind plugin: Statv2: Found %d entries for queries-in/rdtype", xpathObj->nodesetval->nodeNr);
433     xmlXPathFreeObject(xpathObj);
434   }
435
436   /* nsstat -- [name] = [counter]
437    * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG, ReqSIG0, ReqBadSIG, ReqTCP,
438    *            AuthQryRej, RecQryRej, XfrRej, UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
439    *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral, QryNxrrset, QrySERVFAIL,
440    *            QryFORMERR, QryNXDOMAIN, QryRecursion, QryDuplicate, QryDropped, QryFailure, XfrReqDone,
441    *            UpdateReqFwd, UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail, UpdateBadPrereq, */
442   if (use_updates || use_query_results || use_requests) {
443     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/nsstat", xpathCtx)) == NULL) {
444       ERROR("bind plugin: unable to evaluate XPath expression StatsV2-nsstat");
445       return -1;
446     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
447       xmlNode *name = NULL, *counter = NULL, *child;
448       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
449         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
450           name = child;
451         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
452           counter = child;
453       if (name && counter) {
454         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
455         counter_t val;
456         if (!bind_xml_read_counter(doc, counter, &val)) {
457           if (use_updates && (strncmp("Update", tinst, 6) == 0 || strcmp("XfrReqDone", tinst) == 0))
458             submit_counter(current_time, NULL, "dns_update", tinst, val);
459           else if (use_query_results && strncmp("Qry", tinst, 3) == 0)
460             submit_counter(current_time, NULL, "dns_rcode", tinst, val);
461           else if (use_query_results && (strncmp("Resp", tinst, 4) == 0 || strcmp("AuthQryRej", tinst) == 0 ||
462                 strcmp("RecQryRej", tinst) == 0 || strcmp("XfrRej", tinst) == 0 || strcmp("TruncatedResp", tinst) == 0))
463             submit_counter(current_time, NULL, "dns_rcode", tinst, val);
464           else if (use_requests && strncmp("Req", tinst, 3) == 0)
465             submit_counter(current_time, NULL, "dns_request", tinst, val);
466         }
467         xmlFree(tinst);
468       }
469     }
470     DEBUG ("bind plugin: Statv2: Found %d entries for nsstat", xpathObj->nodesetval->nodeNr);
471     xmlXPathFreeObject(xpathObj);
472   }
473
474   /* zonestat -- [name] = [counter]
475    * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej, SOAOutv4, SOAOutv6,
476    *            AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6, XfrSuccess, XfrFail */
477   if (use_zone_maint) {
478     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "server/zonestat", xpathCtx)) == NULL) {
479       ERROR("bind plugin: unable to evaluate XPath expression StatsV2-zonestat");
480       return -1;
481     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
482       xmlNode *name = NULL, *counter = NULL, *child;
483       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL; child = child->next)
484         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
485           name = child;
486         else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
487           counter = child;
488       if (name && counter) {
489         char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
490         counter_t val;
491         if (!bind_xml_read_counter(doc, counter, &val))
492           submit_counter(current_time, NULL, "dns_zops", tinst, val);
493         xmlFree(tinst);
494       }
495     }
496     DEBUG ("bind plugin: Statv2: Found %d entries for zonestat", xpathObj->nodesetval->nodeNr);
497     xmlXPathFreeObject(xpathObj);
498   }
499
500   /* WARNING: per-view only: views/view/resstat, view-name as plugin-instance */
501   /* resstat -- [name] = [counter]
502    * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL, FORMERR, OtherError,
503    *            EDNS0Fail, Mismatch, Truncated, Lame, Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
504    *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail */
505   if (use_resolver) {
506     if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "views/view", xpathCtx)) == NULL) {
507       ERROR("bind plugin: unable to evaluate XPath expression StatsV2-view");
508       return -1;
509     } else for (i = 0; xpathObj->nodesetval && i < xpathObj->nodesetval->nodeNr; i++) {
510       char *zname = NULL;
511       xmlNode *name = NULL, *counter = NULL, *rchild, *child;
512       int n = 0;
513       for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; child != NULL && zname == NULL; child = child->next)
514         if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
515           zname = (char *) xmlNodeListGetString (doc, child->xmlChildrenNode, 1);
516       if (!zname || strcmp("_bind", zname) == 0)
517         continue; /* Unnamed zone?? */
518       /* else TODO: allow zone filtering */
519       for (rchild = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode; rchild != NULL; rchild = rchild->next, n++)
520         if (xmlStrcmp (BAD_CAST "resstat", rchild->name) == 0) {
521           for (child = rchild->xmlChildrenNode; child != NULL; child = child->next)
522             if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
523               name = child;
524             else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
525               counter = child;
526           if (name && counter) {
527             char *tinst   = (char *) xmlNodeListGetString (doc, name->xmlChildrenNode, 1);
528             counter_t val;
529             if (!bind_xml_read_counter(doc, counter, &val))
530               submit_counter(current_time, zname, "dns_resolver", tinst, val);
531             xmlFree(tinst);
532           }
533         }
534       DEBUG ("bind plugin: Statv2: Found %d entries for view %s", n, zname);
535       xmlFree(zname);
536     }
537     xmlXPathFreeObject(xpathObj);
538   }
539   return 0;
540 }
541
542 static int bind_xml (const char *data)
543 {
544   xmlDoc *doc = NULL;
545   xmlXPathContextPtr xpathCtx = NULL;
546   xmlXPathObjectPtr xpathObj = NULL;
547   int ret = -1;
548
549   doc = xmlParseMemory (data, strlen (data));
550   if (doc == NULL) {
551     ERROR ("bind plugin: xmlParseMemory failed.");
552     goto out;
553   }
554
555   if ((xpathCtx = xmlXPathNewContext(doc)) == NULL) {
556     ERROR ("bind plugin: xmlXPathNewContext failed.");
557     goto out;
558   }
559   /* Look for /isc/bind/statistics[@version='2.0'] */
560   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "/isc[@version='1.0']/bind/statistics[@version='1.0']", xpathCtx)) == NULL) {
561     ERROR("bind plugin: unable to evaluate XPath expression StatsV1");
562     goto out;
563   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
564     /* We have Bind-9.5.x */
565     ret = bind_xml_stats_v1(doc, xpathCtx, xpathObj->nodesetval->nodeTab[0]);
566     goto out;
567   } else {
568     xmlXPathFreeObject(xpathObj);
569     xpathObj = NULL;
570   }
571   if ((xpathObj = xmlXPathEvalExpression(BAD_CAST "/isc[@version='1.0']/bind/statistics[@version='2.0']", xpathCtx)) == NULL) {
572     ERROR("bind plugin: unable to evaluate XPath expression StatsV2");
573     goto out;
574   } else if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
575     /* We have Bind-9.6.x */
576     ret = bind_xml_stats_v2(doc, xpathCtx, xpathObj->nodesetval->nodeTab[0]);
577     goto out;
578   } else {
579     xmlXPathFreeObject(xpathObj);
580     xpathObj = NULL;
581   }
582   ERROR("bind plugin: unable to find statistics in supported version.");
583
584 out:
585   xmlXPathFreeObject(xpathObj);
586   xmlXPathFreeContext(xpathCtx);
587   xmlFreeDoc (doc);
588   return (ret);
589 } /* int bind_xml */
590
591 static int config_set_str (char **var, const char *value)
592 {
593   if (*var != NULL)
594   {
595     free (*var);
596     *var = NULL;
597   }
598
599   if ((*var = strdup (value)) == NULL)
600     return (-1);
601   else
602     return (0);
603 } /* int config_set_str */
604
605 static int config_set_bool (bool *var, const char *value)
606 {
607   if ((strcasecmp ("true", value) == 0)
608       || (strcasecmp ("yes", value) == 0)
609       || (strcasecmp ("on", value) == 0))
610     *var = 1;
611   else if ((strcasecmp ("false", value) == 0)
612       || (strcasecmp ("no", value) == 0)
613       || (strcasecmp ("off", value) == 0))
614     *var = 0;
615   else
616     return -1;
617   return 0;
618 } /* int config_set_bool */
619
620 static int bind_config (const char *key, const char *value)
621 {
622   if (strcasecmp (key, "URL") == 0)
623     return (config_set_str (&url, value));
624   else if (strcasecmp (key, "RRQueriesIn") == 0)
625     return (config_set_bool (&use_rrqueries_in, value));
626   else if (strcasecmp (key, "Requests") == 0)
627     return (config_set_bool (&use_requests, value));
628   else if (strcasecmp (key, "QueryResults") == 0)
629     return (config_set_bool (&use_query_results, value));
630   else if (strcasecmp (key, "Updates") == 0)
631     return (config_set_bool (&use_updates, value));
632   else if (strcasecmp (key, "ZoneMaintenance") == 0)
633     return (config_set_bool (&use_zone_maint, value));
634   else if (strcasecmp (key, "Resolver") == 0)
635     return (config_set_bool (&use_resolver, value));
636   else
637     return (-1);
638 } /* int bind_config */
639
640 static int bind_init (void)
641 {
642   if (url == NULL)
643   {
644     WARNING ("bind plugin: bind_init: No URL configured, "
645         "returning an error.");
646     return (-1);
647   }
648
649   if (curl != NULL)
650     curl_easy_cleanup (curl);
651
652   if ((curl = curl_easy_init ()) == NULL)
653   {
654     ERROR ("bind plugin: bind_init: curl_easy_init failed.");
655     return (-1);
656   }
657
658   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, bind_curl_callback);
659   curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
660   curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, bind_curl_error);
661   curl_easy_setopt (curl, CURLOPT_URL, url);
662   return (0);
663 } /* int bind_init */
664
665 static int bind_read (void)
666 {
667   int status;
668
669   if (curl == NULL)
670   {
671     ERROR ("bind plugin: I don't have a CURL object.");
672     return (-1);
673   }
674
675   if (url == NULL)
676   {
677     ERROR ("bind plugin: No URL has been configured.");
678     return (-1);
679   }
680
681   bind_buffer_fill = 0;
682   if (curl_easy_perform (curl) != 0)
683   {
684     ERROR ("bind plugin: curl_easy_perform failed: %s",
685         bind_curl_error);
686     return (-1);
687   }
688
689   status = bind_xml (bind_buffer);
690   if (status != 0)
691     return (-1);
692   else
693     return (0);
694 } /* int bind_read */
695
696 void module_register (void)
697 {
698   plugin_register_config ("bind", bind_config, config_keys, config_keys_num);
699   plugin_register_init ("bind", bind_init);
700   plugin_register_read ("bind", bind_read);
701 } /* void module_register */
702
703 /* vim: set sw=2 sts=2 ts=8 et fdm=marker : */