bind plugin: Reworked Bruno's patch to remove repeating code.
[collectd.git] / src / bind.c
1 /**
2  * collectd - src/bind.c
3  * Copyright (C) 2009  Bruno PrĂ©mont
4  * Copyright (C) 2009  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 verplant.org>
22  **/
23
24 /* Set to C99 and POSIX code */
25 #ifndef _ISOC99_SOURCE
26 # define _ISOC99_SOURCE
27 #endif
28 #ifndef _POSIX_SOURCE
29 # define _POSIX_SOURCE
30 #endif
31 #ifndef _POSIX_C_SOURCE
32 # define _POSIX_C_SOURCE 200112L
33 #endif
34 #ifndef _REENTRANT
35 # define _REENTRANT
36 #endif
37 #ifndef _XOPEN_SOURCE
38 # define _XOPEN_SOURCE 600
39 #endif
40 #ifndef _BSD_SOURCE
41 # define _BSD_SOURCE
42 #endif
43
44 #include "collectd.h"
45 #include "common.h"
46 #include "plugin.h"
47 #include "configfile.h"
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, counter_t value,
63     time_t current_time, void *user_data);
64
65 struct translation_info_s
66 {
67   const char *xml_name;
68   const char *type;
69   const char *type_instance;
70   const int  *config_variable;
71 };
72 typedef struct translation_info_s translation_info_t;
73
74 struct translation_table_ptr_s
75 {
76   const translation_info_t *table;
77   size_t table_length;
78   const char *plugin_instance;
79 };
80 typedef struct translation_table_ptr_s translation_table_ptr_t;
81
82 struct list_info_ptr_s
83 {
84   const char *plugin_instance;
85   const char *type;
86 };
87 typedef struct list_info_ptr_s list_info_ptr_t;
88
89 static char *url               = NULL;
90 static int   use_requests      = 1;
91 static int   use_rejects       = 1;
92 static int   use_responses     = 1;
93 static int   use_queries       = 1;
94 static int   use_rcode         = 1;
95 static int   use_zonestats     = 1;
96 static int   use_opcode        = 1;
97 static int   use_resolver      = 1;
98 static int   use_dnssec        = 1;
99
100 static int   use_rrqueries_in  = 1;
101 static int   use_query_results = 1;
102 static int   use_updates       = 1;
103 static int   use_zone_maint    = 1;
104
105 static CURL *curl = NULL;
106
107 static char  *bind_buffer = NULL;
108 static size_t bind_buffer_size = 0;
109 static size_t bind_buffer_fill = 0;
110 static char   bind_curl_error[CURL_ERROR_SIZE];
111
112 static const char *config_keys[] =
113 {
114   "URL",
115   "Requests",
116   "Rejects",
117   "Responses",
118   "Queries",
119   "RCode",
120   "ZoneStats",
121   "OpCodes",
122   "Resolver",
123   "DNSSEC",
124
125   "RRQueriesIn",
126   "QueryResults",
127   "Updates",
128   "ZoneMaintenance"
129 };
130 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
131
132 /* Translation table for the `nsstats' values. */
133 static const translation_info_t nsstats_translation_table[] =
134 {
135   /* Requests */
136   { "Requestv4",       "dns_request",  "IPv4",        &use_requests },
137   { "Requestv6",       "dns_request",  "IPv6",        &use_requests },
138   { "ReqEdns0",        "dns_request",  "EDNS0",       &use_requests },
139   { "ReqBadEDNSVer",   "dns_request",  "BadEDNSVer",  &use_requests },
140   { "ReqTSIG",         "dns_request",  "TSIG",        &use_requests },
141   { "ReqSIG0",         "dns_request",  "SIG0",        &use_requests },
142   { "ReqBadSIG",       "dns_request",  "BadSIG",      &use_requests },
143   { "ReqTCP",          "dns_request",  "TCP",         &use_requests },
144   /* Rejects */
145   { "AuthQryRej",      "dns_reject",   "authorative", &use_rejects },
146   { "RecQryRej",       "dns_reject",   "recursive",   &use_rejects },
147   { "XfrRej",          "dns_reject",   "transer",     &use_rejects },
148   { "UpdateRej",       "dns_reject",   "update",      &use_rejects },
149   /* Responses */
150   { "Response",        "dns_response", "normal",      &use_responses },
151   { "TruncatedResp",   "dns_response", "truncated",   &use_responses },
152   { "RespEDNS0",       "dns_response", "EDNS0",       &use_responses },
153   { "RespTSIG",        "dns_response", "TSIG",        &use_responses },
154   { "RespSIG0",        "dns_response", "SIG0",        &use_responses },
155   /* Queries */
156   { "QryAuthAns",      "dns_query",    "authorative", &use_queries },
157   { "QryNoauthAns",    "dns_query",    "nonauth",     &use_queries },
158   { "QryReferral",     "dns_query",    "referral",    &use_queries },
159   { "QryRecursion",    "dns_query",    "recursion",   &use_queries },
160   { "QryDuplicate",    "dns_query",    "dupliate",    &use_queries },
161   { "QryDropped",      "dns_query",    "dropped",     &use_queries },
162   { "QryFailure",      "dns_query",    "failure",     &use_queries },
163   /* Response codes */
164   { "QrySuccess",      "dns_rcode",    "tx-NOERROR",  &use_rcode },
165   { "QryNxrrset",      "dns_rcode",    "tx-NXRRSET",  &use_rcode },
166   { "QrySERVFAIL",     "dns_rcode",    "tx-SERVFAIL", &use_rcode },
167   { "QryFORMERR",      "dns_rcode",    "tx-FORMERR",  &use_rcode },
168   { "QryNXDOMAIN",     "dns_rcode",    "tx-NXDOMAIN", &use_rcode }
169 #if 0
170   { "XfrReqDone",      "type", "type_instance", &use_something },
171   { "UpdateReqFwd",    "type", "type_instance", &use_something },
172   { "UpdateRespFwd",   "type", "type_instance", &use_something },
173   { "UpdateFwdFail",   "type", "type_instance", &use_something },
174   { "UpdateDone",      "type", "type_instance", &use_something },
175   { "UpdateFail",      "type", "type_instance", &use_something },
176   { "UpdateBadPrereq", "type", "type_instance", &use_something },
177 #endif
178 };
179 static int nsstats_translation_table_length =
180   STATIC_ARRAY_SIZE (nsstats_translation_table);
181 #define PARSE_NSSTATS (use_requests || use_rejects || use_responses \
182     || use_queries || use_rcode)
183
184 /* Translation table for the `zonestats' values. */
185 static const translation_info_t zonestats_translation_table[] =
186 {
187   /* Notify's */
188   { "NotifyOutv4",     "dns_notify",   "tx-IPv4",     &use_zonestats },
189   { "NotifyOutv6",     "dns_notify",   "tx-IPv6",     &use_zonestats },
190   { "NotifyInv4",      "dns_notify",   "rx-IPv4",     &use_zonestats },
191   { "NotifyInv6",      "dns_notify",   "rx-IPv6",     &use_zonestats },
192   { "NotifyRej",       "dns_notify",   "rejected",    &use_zonestats },
193   /* SOA/AXFS/IXFS requests */
194   { "SOAOutv4",        "dns_opcode",   "SOA-IPv4",    &use_opcode },
195   { "SOAOutv6",        "dns_opcode",   "SOA-IPv4",    &use_opcode },
196   { "AXFRReqv4",       "dns_opcode",   "AXFR-IPv4",   &use_opcode },
197   { "AXFRReqv6",       "dns_opcode",   "AXFR-IPv6",   &use_opcode },
198   { "IXFRReqv4",       "dns_opcode",   "IXFR-IPv4",   &use_opcode },
199   { "IXFRReqv6",       "dns_opcode",   "IXFR-IPv6",   &use_opcode },
200   /* Domain transfers */
201   { "XfrSuccess",      "dns_transfer", "success",     &use_zonestats },
202   { "XfrFail",         "dns_transfer", "failure",     &use_zonestats }
203 };
204 static int zonestats_translation_table_length =
205   STATIC_ARRAY_SIZE (zonestats_translation_table);
206 #define PARSE_ZONESTATS (use_zonestats || use_opcode)
207
208 /* Translation table for the `resstats' values. */
209 static const translation_info_t resstats_translation_table[] =
210 {
211   /* Generic resolver information */
212   { "Queryv4",         "dns_query",    "IPv4",           &use_resolver },
213   { "Queryv6",         "dns_query",    "IPv6",           &use_resolver },
214   { "Responsev4",      "dns_response", "IPv4",           &use_resolver },
215   { "Responsev6",      "dns_response", "IPv6",           &use_resolver },
216   /* Received response codes */
217   { "NXDOMAIN",        "dns_rcode",    "rx-NXDOMAIN",    &use_rcode },
218   { "SERVFAIL",        "dns_rcode",    "rx-SERVFAIL",    &use_rcode },
219   { "FORMERR",         "dns_rcode",    "rx-FORMERR",     &use_rcode },
220   { "OtherError",      "dns_rcode",    "rx-OTHER",       &use_rcode },
221   { "EDNS0Fail",       "dns_rcode",    "rx-EDNS0Fail",   &use_rcode },
222   /* Received responses */
223   { "Mismatch",        "dns_response", "mismatch",       &use_responses },
224   { "Truncated",       "dns_response", "truncated",      &use_responses },
225   { "Lame",            "dns_response", "lame",           &use_responses },
226   { "Retry",           "dns_query",    "retry",          &use_responses },
227 #if 0
228   { "GlueFetchv4",     "type", "type_instance", &use_something },
229   { "GlueFetchv6",     "type", "type_instance", &use_something },
230   { "GlueFetchv4Fail", "type", "type_instance", &use_something },
231   { "GlueFetchv6Fail", "type", "type_instance", &use_something },
232 #endif
233   /* DNSSEC information */
234   { "ValAttempt",      "dns_resolver", "DNSSEC-attempt", &use_dnssec },
235   { "ValOk",           "dns_resolver", "DNSSEC-okay",    &use_dnssec },
236   { "ValNegOk",        "dns_resolver", "DNSSEC-negokay", &use_dnssec },
237   { "ValFail",         "dns_resolver", "DNSSEC-fail",    &use_dnssec }
238 };
239 static int resstats_translation_table_length =
240   STATIC_ARRAY_SIZE (resstats_translation_table);
241 #define PARSE_RESSTATS (use_resolver || use_rcode || use_responses || use_dnssec)
242
243 static void remove_special (char *buffer, size_t buffer_size)
244 {
245   size_t i;
246
247   for (i = 0; i < buffer_size; i++)
248   {
249     if (buffer[i] == 0)
250       return;
251     if (!isalnum ((int) buffer[i]))
252       buffer[i] = '_';
253   }
254 } /* void remove_special */
255
256 static void submit_counter(time_t ts, const char *plugin_instance, const char *type,
257     const char *type_instance, counter_t value)
258 {
259   value_t values[1];
260   value_list_t vl = VALUE_LIST_INIT;
261
262   values[0].counter = value;
263
264   vl.values = values;
265   vl.values_len = 1;
266   vl.time = (ts == 0) ? time (NULL) : ts;
267   sstrncpy(vl.host, hostname_g, sizeof(vl.host));
268   sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
269   if (plugin_instance) {
270     sstrncpy(vl.plugin_instance, plugin_instance,
271         sizeof(vl.plugin_instance));
272     remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
273   }
274   sstrncpy(vl.type, type, sizeof(vl.type));
275   if (type_instance) {
276     sstrncpy(vl.type_instance, type_instance,
277         sizeof(vl.type_instance));
278     remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
279   }
280   plugin_dispatch_values(&vl);
281 } /* void submit_counter */
282
283 static size_t bind_curl_callback (void *buf, size_t size, size_t nmemb,
284     void *stream)
285 {
286   size_t len = size * nmemb;
287
288   if (len <= 0)
289     return (len);
290
291   if ((bind_buffer_fill + len) >= bind_buffer_size)
292   {
293     char *temp;
294
295     temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
296     if (temp == NULL)
297     {
298       ERROR ("bind plugin: realloc failed.");
299       return (0);
300     }
301     bind_buffer = temp;
302     bind_buffer_size = bind_buffer_fill + len + 1;
303   }
304
305   memcpy (bind_buffer + bind_buffer_fill, (char *) buf, len);
306   bind_buffer_fill += len;
307   bind_buffer[bind_buffer_fill] = 0;
308
309   return (len);
310 } /* size_t bind_curl_callback */
311
312 /*
313  * Callback, that's called with a translation table.
314  * (Plugin instance is fixed, type and type instance come from lookup table.)
315  */
316 static int bind_xml_table_callback (const char *name, counter_t value,
317     time_t current_time, void *user_data)
318 {
319   translation_table_ptr_t *table = (translation_table_ptr_t *) user_data;
320   size_t i;
321
322   if (table == NULL)
323     return (-1);
324
325   for (i = 0; i < table->table_length; i++)
326   {
327     if (strcmp (table->table[i].xml_name, name) != 0)
328       continue;
329
330     if (*table->table[i].config_variable == 0)
331       break;
332
333     submit_counter (current_time,
334         table->plugin_instance,
335         table->table[i].type,
336         table->table[i].type_instance,
337         value);
338     break;
339   }
340
341   return (0);
342 } /* int bind_xml_table_callback */
343
344 /*
345  * Callback, that's used for lists.
346  * (Plugin instance and type are fixed, xml name is used as type instance.)
347  */
348 static int bind_xml_list_callback (const char *name, counter_t value,
349     time_t current_time, void *user_data)
350 {
351   list_info_ptr_t *list_info = (list_info_ptr_t *) user_data;
352
353   if (list_info == NULL)
354     return (-1);
355
356   submit_counter (current_time,
357       list_info->plugin_instance,
358       list_info->type,
359       /* type instance = */ name,
360       value);
361
362   return (0);
363 } /* int bind_xml_list_callback */
364
365 static int bind_xml_read_counter (xmlDoc *doc, xmlNode *node, 
366     counter_t *ret_value)
367 {
368   char *str_ptr, *end_ptr;
369   long long int value;
370
371   str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
372   if (str_ptr == NULL)
373   {
374     ERROR ("bind plugin: bind_xml_read_counter: xmlNodeListGetString failed.");
375     return (-1);
376   }
377
378   errno = 0;
379   value = strtoll (str_ptr, &end_ptr, 10);
380   xmlFree(str_ptr);
381   if (str_ptr == end_ptr || errno)
382   {
383     if (errno && value == LLONG_MIN)
384       ERROR ("bind plugin: bind_xml_read_counter: strtoll failed with underflow.");
385     else if (errno && value == LLONG_MAX)
386       ERROR ("bind plugin: bind_xml_read_counter: strtoll failed with overflow.");
387     else
388       ERROR ("bind plugin: bind_xml_read_counter: strtoll failed.");
389     return (-1);
390   }
391
392   *ret_value = value;
393   return (0);
394 } /* int bind_xml_read_counter */
395
396 static int bind_xml_read_timestamp (const char *xpath_expression, xmlDoc *doc,
397     xmlXPathContext *xpathCtx, time_t *ret_value)
398 {
399   xmlXPathObject *xpathObj = NULL;
400   xmlNode *node;
401   char *str_ptr;
402   char *tmp;
403   struct tm tm;
404
405   xpathObj = xmlXPathEvalExpression (BAD_CAST xpath_expression, xpathCtx);
406   if (xpathObj == NULL)
407   {
408     ERROR ("bind plugin: Unable to evaluate XPath expression `%s'.",
409         xpath_expression);
410     return (-1);
411   }
412
413   if ((xpathObj->nodesetval == NULL) || (xpathObj->nodesetval->nodeNr < 1))
414   {
415     xmlXPathFreeObject (xpathObj);
416     return (-1);
417   }
418
419   if (xpathObj->nodesetval->nodeNr != 1)
420   {
421     NOTICE ("bind plugin: Evaluating the XPath expression `%s' returned "
422         "%i nodes. Only handling the first one.",
423         xpath_expression, xpathObj->nodesetval->nodeNr);
424   }
425
426   node = xpathObj->nodesetval->nodeTab[0];
427
428   if (node->xmlChildrenNode == NULL)
429   {
430     ERROR ("bind plugin: bind_xml_read_timestamp: "
431         "node->xmlChildrenNode == NULL");
432     xmlXPathFreeObject (xpathObj);
433     return (-1);
434   }
435
436   str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
437   if (str_ptr == NULL)
438   {
439     ERROR ("bind plugin: bind_xml_read_timestamp: xmlNodeListGetString failed.");
440     xmlXPathFreeObject (xpathObj);
441     return (-1);
442   }
443
444   memset (&tm, 0, sizeof(tm));
445   tmp = strptime (str_ptr, "%Y-%m-%dT%T", &tm);
446   xmlFree(str_ptr);
447   if (tmp == NULL)
448   {
449     ERROR ("bind plugin: bind_xml_read_timestamp: strptime failed.");
450     xmlXPathFreeObject (xpathObj);
451     return (-1);
452   }
453
454   *ret_value = timegm(&tm);
455
456   xmlXPathFreeObject (xpathObj);
457   return (0);
458 } /* int bind_xml_read_timestamp */
459
460 /* 
461  * bind_parse_generic_name_value
462  *
463  * Reads statistics in the form:
464  * <foo>
465  *   <name>QUERY</name>
466  *   <value>123</name>
467  * </foo>
468  */
469 static int bind_parse_generic_name_value (const char *xpath_expression,
470     list_callback_t list_callback,
471     void *user_data,
472     xmlDoc *doc, xmlXPathContext *xpathCtx,
473     time_t current_time)
474 {
475   xmlXPathObject *xpathObj = NULL;
476   int num_entries;
477   int i;
478
479   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
480   if (xpathObj == NULL)
481   {
482     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
483         xpath_expression);
484     return (-1);
485   }
486
487   num_entries = 0;
488   /* Iterate over all matching nodes. */
489   for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
490   {
491     xmlNode *name_node = NULL;
492     xmlNode *counter = NULL;
493     xmlNode *child;
494
495     /* Iterate over all child nodes. */
496     for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
497         child != NULL;
498         child = child->next)
499     {
500       if (child->type != XML_ELEMENT_NODE)
501         continue;
502
503       if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
504         name_node = child;
505       else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
506         counter = child;
507     }
508
509     if ((name_node != NULL) && (counter != NULL))
510     {
511       char *name = (char *) xmlNodeListGetString (doc,
512           name_node->xmlChildrenNode, 1);
513       counter_t value;
514       int status;
515
516       status = bind_xml_read_counter (doc, counter, &value);
517       if (status != 0)
518         continue;
519
520       status = (*list_callback) (name, value, current_time, user_data);
521       if (status == 0)
522         num_entries++;
523
524       xmlFree (name);
525     }
526   }
527
528   DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
529       num_entries, (num_entries == 1) ? "entry" : "entries",
530       xpath_expression);
531
532   xmlXPathFreeObject(xpathObj);
533
534   return (0);
535 } /* int bind_parse_generic_name_value */
536
537 /* 
538  * bind_parse_generic_value_list
539  *
540  * Reads statistics in the form:
541  * <foo>
542  *   <name0>123</name0>
543  *   <name1>234</name1>
544  *   <name2>345</name2>
545  *   :
546  * </foo>
547  */
548 static int bind_parse_generic_value_list (const char *xpath_expression,
549     list_callback_t list_callback,
550     void *user_data,
551     xmlDoc *doc, xmlXPathContext *xpathCtx,
552     time_t current_time)
553 {
554   xmlXPathObject *xpathObj = NULL;
555   int num_entries;
556   int i;
557
558   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
559   if (xpathObj == NULL)
560   {
561     ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
562         xpath_expression);
563     return (-1);
564   }
565
566   num_entries = 0;
567   /* Iterate over all matching nodes. */
568   for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
569   {
570     xmlNode *child;
571
572     /* Iterate over all child nodes. */
573     for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
574         child != NULL;
575         child = child->next)
576     {
577       char *node_name;
578       counter_t value;
579       int status;
580
581       if (child->type != XML_ELEMENT_NODE)
582         continue;
583
584       node_name = (char *) child->name;
585       status = bind_xml_read_counter (doc, child, &value);
586       if (status != 0)
587         continue;
588
589       status = (*list_callback) (node_name, value, current_time, user_data);
590       if (status == 0)
591         num_entries++;
592     }
593   }
594
595   DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
596       num_entries, (num_entries == 1) ? "entry" : "entries",
597       xpath_expression);
598
599   xmlXPathFreeObject(xpathObj);
600
601   return (0);
602 } /* int bind_parse_generic_value_list */
603
604 static int bind_xml_stats (int version, xmlDoc *doc,
605     xmlXPathContext *xpathCtx, xmlNode *statsnode)
606 {
607   time_t current_time = 0;
608   int status;
609
610   xpathCtx->node = statsnode;
611
612   /* TODO: Check `server/boot-time' to recognize server restarts. */
613
614   status = bind_xml_read_timestamp ("server/current-time",
615       doc, xpathCtx, &current_time);
616   if (status != 0)
617   {
618     ERROR ("bind plugin: Reading `server/current-time' failed.");
619     return (-1);
620   }
621   DEBUG ("bind plugin: Current server time is %i.", (int) current_time);
622
623   /* XPath:     server/requests/opcode
624    * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
625    * Layout:
626    *   <opcode>
627    *     <name>A</name>
628    *     <counter>1</counter>
629    *   </opcode>
630    *   :
631    */
632   if (use_opcode)
633   {
634     list_info_ptr_t list_info =
635     {
636       /* plugin instance = */ "requests",
637       /* type = */ "dns_opcode"
638     };
639
640     bind_parse_generic_name_value (/* xpath = */ "server/requests/opcode",
641         /* callback = */ bind_xml_list_callback,
642         /* user_data = */ &list_info,
643         doc, xpathCtx, current_time);
644   }
645
646   /* XPath:     server/queries-in/rdtype
647    * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
648    *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
649    *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
650    * Layout:
651    *   <rdtype>
652    *     <name>A</name>
653    *     <counter>1</counter>
654    *   </rdtype>
655    *   :
656    */
657   if (use_rrqueries_in)
658   {
659     list_info_ptr_t list_info =
660     {
661       /* plugin instance = */ "queries-in",
662       /* type = */ "dns_qtype"
663     };
664
665     bind_parse_generic_name_value (/* xpath = */ "server/queries-in/rdtype",
666         /* callback = */ bind_xml_list_callback,
667         /* user_data = */ &list_info,
668         doc, xpathCtx, current_time);
669   }
670   
671   /* XPath:     server/nsstats, server/nsstat
672    * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
673    *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
674    *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
675    *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
676    *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
677    *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
678    *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
679    *            UpdateBadPrereq
680    * Layout v1:
681    *   <nsstats>
682    *     <Requestv4>1</Requestv4>
683    *     <Requestv6>0</Requestv6>
684    *     :
685    *   </nsstats>
686    * Layout v2:
687    *   <nsstat>
688    *     <name>Requestv4</name>
689    *     <counter>1</counter>
690    *   </nsstat>
691    *   <nsstat>
692    *     <name>Requestv6</name>
693    *     <counter>0</counter>
694    *   </nsstat>
695    *   :
696    */
697   if (PARSE_NSSTATS)
698   {
699     translation_table_ptr_t table_ptr =
700     { 
701       nsstats_translation_table,
702       nsstats_translation_table_length,
703       /* plugin_instance = */ "nsstats"
704     };
705
706     if (version == 1)
707     {
708       bind_parse_generic_value_list ("server/nsstats",
709           /* callback = */ bind_xml_table_callback,
710           /* user_data = */ &table_ptr,
711           doc, xpathCtx, current_time);
712     }
713     else
714     {
715       bind_parse_generic_name_value ("server/nsstat",
716           /* callback = */ bind_xml_table_callback,
717           /* user_data = */ &table_ptr,
718           doc, xpathCtx, current_time);
719     }
720   }
721
722   /* XPath:     server/zonestats, server/zonestat
723    * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
724    *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
725    *            XfrSuccess, XfrFail
726    * Layout v1:
727    *   <zonestats>
728    *     <NotifyOutv4>0</NotifyOutv4>
729    *     <NotifyOutv6>0</NotifyOutv6>
730    *     :
731    *   </zonestats>
732    * Layout v2:
733    *   <zonestat>
734    *     <name>NotifyOutv4</name>
735    *     <counter>0</counter>
736    *   </zonestat>
737    *   <zonestat>
738    *     <name>NotifyOutv6</name>
739    *     <counter>0</counter>
740    *   </zonestat>
741    *   :
742    */
743   if (PARSE_ZONESTATS)
744   {
745     translation_table_ptr_t table_ptr =
746     { 
747       zonestats_translation_table,
748       zonestats_translation_table_length,
749       /* plugin_instance = */ "zonestats"
750     };
751
752     if (version == 1)
753     {
754       bind_parse_generic_value_list ("server/zonestats",
755           /* callback = */ bind_xml_table_callback,
756           /* user_data = */ &table_ptr,
757           doc, xpathCtx, current_time);
758     }
759     else
760     {
761       bind_parse_generic_name_value ("server/zonestat",
762           /* callback = */ bind_xml_table_callback,
763           /* user_data = */ &table_ptr,
764           doc, xpathCtx, current_time);
765     }
766   }
767
768   /* XPath:     server/resstats
769    * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
770    *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
771    *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
772    *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
773    * Layout v1:
774    *   <resstats>
775    *     <Queryv4>0</Queryv4>
776    *     <Queryv6>0</Queryv6>
777    *     :
778    *   </resstats>
779    * Layout v2:
780    *   <resstat>
781    *     <name>Queryv4</name>
782    *     <counter>0</counter>
783    *   </resstat>
784    *   <resstat>
785    *     <name>Queryv6</name>
786    *     <counter>0</counter>
787    *   </resstat>
788    *   :
789    */
790   if (PARSE_RESSTATS)
791   {
792     translation_table_ptr_t table_ptr =
793     { 
794       resstats_translation_table,
795       resstats_translation_table_length,
796       /* plugin_instance = */ "resstats"
797     };
798
799     if (version == 1)
800     {
801       bind_parse_generic_value_list ("server/resstats",
802           /* callback = */ bind_xml_table_callback,
803           /* user_data = */ &table_ptr,
804           doc, xpathCtx, current_time);
805     }
806     else
807     {
808       bind_parse_generic_name_value ("server/resstat",
809           /* callback = */ bind_xml_table_callback,
810           /* user_data = */ &table_ptr,
811           doc, xpathCtx, current_time);
812     }
813   }
814
815   return 0;
816 } /* int bind_xml_stats */
817
818 static int bind_xml (const char *data)
819 {
820   xmlDoc *doc = NULL;
821   xmlXPathContext *xpathCtx = NULL;
822   xmlXPathObject *xpathObj = NULL;
823   int ret = -1;
824   int i;
825
826   doc = xmlParseMemory (data, strlen (data));
827   if (doc == NULL)
828   {
829     ERROR ("bind plugin: xmlParseMemory failed.");
830     return (-1);
831   }
832
833   xpathCtx = xmlXPathNewContext (doc);
834   if (xpathCtx == NULL)
835   {
836     ERROR ("bind plugin: xmlXPathNewContext failed.");
837     xmlFreeDoc (doc);
838     return (-1);
839   }
840
841   xpathObj = xmlXPathEvalExpression (BAD_CAST "/isc/bind/statistics", xpathCtx);
842   if (xpathObj == NULL)
843   {
844     ERROR ("bind plugin: Cannot find the <statistics> tag.");
845     xmlXPathFreeContext (xpathCtx);
846     xmlFreeDoc (doc);
847     return (-1);
848   }
849   else if (xpathObj->nodesetval == NULL)
850   {
851     ERROR ("bind plugin: xmlXPathEvalExpression failed.");
852     xmlXPathFreeObject (xpathObj);
853     xmlXPathFreeContext (xpathCtx);
854     xmlFreeDoc (doc);
855     return (-1);
856   }
857
858   for (i = 0; i < xpathObj->nodesetval->nodeNr; i++)
859   {
860     xmlNode *node;
861     char *attr_version;
862     int parsed_version = 0;
863
864     node = xpathObj->nodesetval->nodeTab[i];
865     assert (node != NULL);
866
867     attr_version = (char *) xmlGetProp (node, BAD_CAST "version");
868     if (attr_version == NULL)
869     {
870       NOTICE ("bind plugin: Found <statistics> tag doesn't have a "
871           "`version' attribute.");
872       continue;
873     }
874     DEBUG ("bind plugin: Found: <statistics version=\"%s\">", attr_version);
875
876     /* At the time this plugin was written, version "1.0" was used by
877      * BIND 9.5.0, version "2.0" was used by BIND 9.5.1 and 9.6.0. We assume
878      * that "1.*" and "2.*" don't introduce structural changes, so we just
879      * check for the first two characters here. */
880     if (strncmp ("1.", attr_version, strlen ("1.")) == 0)
881       parsed_version = 1;
882     else if (strncmp ("2.", attr_version, strlen ("2.")) == 0)
883       parsed_version = 2;
884     else
885     {
886       /* TODO: Use the complaint mechanism here. */
887       NOTICE ("bind plugin: Found <statistics> tag with version `%s'. "
888           "Unfortunately I have no clue how to parse that. "
889           "Please open a bug report for this.", attr_version);
890       xmlFree (attr_version);
891       continue;
892     }
893
894     ret = bind_xml_stats (parsed_version,
895         doc, xpathCtx, node);
896
897     xmlFree (attr_version);
898     /* One <statistics> node ought to be enough. */
899     break;
900   }
901
902   xmlXPathFreeObject (xpathObj);
903   xmlXPathFreeContext (xpathCtx);
904   xmlFreeDoc (doc);
905
906   return (ret);
907 } /* int bind_xml */
908
909 static int config_set_str (char **var, const char *value)
910 {
911   if (*var != NULL)
912   {
913     free (*var);
914     *var = NULL;
915   }
916
917   if ((*var = strdup (value)) == NULL)
918     return (-1);
919   else
920     return (0);
921 } /* int config_set_str */
922
923 static int config_set_bool (int *var, const char *value)
924 {
925   if (IS_TRUE (value))
926     *var = 1;
927   else if (IS_FALSE (value))
928     *var = 0;
929   else
930     return -1;
931   return 0;
932 } /* int config_set_bool */
933
934 static int bind_config (const char *key, const char *value)
935 {
936   if (strcasecmp (key, "URL") == 0)
937     return (config_set_str (&url, value));
938   else if (strcasecmp (key, "Requests") == 0)
939     return (config_set_bool (&use_requests, value));
940   else if (strcasecmp (key, "Rejects") == 0)
941     return (config_set_bool (&use_rejects, value));
942   else if (strcasecmp (key, "Responses") == 0)
943     return (config_set_bool (&use_responses, value));
944   else if (strcasecmp (key, "Queries") == 0)
945     return (config_set_bool (&use_queries, value));
946   else if (strcasecmp (key, "RCode") == 0)
947     return (config_set_bool (&use_rcode, value));
948   else if (strcasecmp (key, "ZoneStats") == 0)
949     return (config_set_bool (&use_zonestats, value));
950   else if (strcasecmp (key, "OpCodes") == 0)
951     return (config_set_bool (&use_opcode, value));
952   else if (strcasecmp (key, "Resolver") == 0)
953     return (config_set_bool (&use_resolver, value));
954   else if (strcasecmp (key, "DNSSEC") == 0)
955     return (config_set_bool (&use_dnssec, value));
956
957   else if (strcasecmp (key, "RRQueriesIn") == 0)
958     return (config_set_bool (&use_rrqueries_in, value));
959   else if (strcasecmp (key, "QueryResults") == 0)
960     return (config_set_bool (&use_query_results, value));
961   else if (strcasecmp (key, "Updates") == 0)
962     return (config_set_bool (&use_updates, value));
963   else if (strcasecmp (key, "ZoneMaintenance") == 0)
964     return (config_set_bool (&use_zone_maint, value));
965
966   else
967     return (-1);
968 } /* int bind_config */
969
970 static int bind_init (void)
971 {
972   if (curl != NULL)
973     return (0);
974
975   curl = curl_easy_init ();
976   if (curl == NULL)
977   {
978     ERROR ("bind plugin: bind_init: curl_easy_init failed.");
979     return (-1);
980   }
981
982   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, bind_curl_callback);
983   curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
984   curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, bind_curl_error);
985   curl_easy_setopt (curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
986
987   return (0);
988 } /* int bind_init */
989
990 static int bind_read (void)
991 {
992   int status;
993
994   if (curl == NULL)
995   {
996     ERROR ("bind plugin: I don't have a CURL object.");
997     return (-1);
998   }
999
1000   bind_buffer_fill = 0;
1001   if (curl_easy_perform (curl) != 0)
1002   {
1003     ERROR ("bind plugin: curl_easy_perform failed: %s",
1004         bind_curl_error);
1005     return (-1);
1006   }
1007
1008   status = bind_xml (bind_buffer);
1009   if (status != 0)
1010     return (-1);
1011   else
1012     return (0);
1013 } /* int bind_read */
1014
1015 static int bind_shutdown (void)
1016 {
1017   if (curl != NULL)
1018   {
1019     curl_easy_cleanup (curl);
1020     curl = NULL;
1021   }
1022
1023   return (0);
1024 } /* int bind_shutdown */
1025
1026 void module_register (void)
1027 {
1028   plugin_register_config ("bind", bind_config, config_keys, config_keys_num);
1029   plugin_register_init ("bind", bind_init);
1030   plugin_register_read ("bind", bind_read);
1031   plugin_register_shutdown ("bind", bind_shutdown);
1032 } /* void module_register */
1033
1034 /* vim: set sw=2 sts=2 ts=8 et fdm=marker : */