write_prometheus plugin: Escape label values.
[collectd.git] / src / bind.c
index 6a6ec74..2b7ad75 100644 (file)
@@ -36,9 +36,9 @@
 #endif /* STRPTIME_NEEDS_STANDARDS */
 
 #include "collectd.h"
+
 #include "common.h"
 #include "plugin.h"
-#include "configfile.h"
 
 /* Some versions of libcurl don't include this themselves and then don't have
  * fd_set available. */
@@ -54,7 +54,7 @@
 # define BIND_DEFAULT_URL "http://localhost:8053/"
 #endif
 
-/* 
+/*
  * Some types used for the callback functions. `translation_table_ptr_t' and
  * `list_info_ptr_t' are passed to the callbacks in the `void *user_data'
  * pointer.
@@ -109,6 +109,7 @@ static int global_server_stats     = 1;
 static int global_zone_maint_stats = 1;
 static int global_resolver_stats   = 0;
 static int global_memory_stats     = 1;
+static int timeout                 = -1;
 
 static cb_view_t *views = NULL;
 static size_t     views_num = 0;
@@ -124,47 +125,47 @@ static char   bind_curl_error[CURL_ERROR_SIZE];
 static const translation_info_t nsstats_translation_table[] = /* {{{ */
 {
   /* Requests */
-  { "Requestv4",       "dns_request",  "IPv4"        },
-  { "Requestv6",       "dns_request",  "IPv6"        },
-  { "ReqEdns0",        "dns_request",  "EDNS0"       },
-  { "ReqBadEDNSVer",   "dns_request",  "BadEDNSVer"  },
-  { "ReqTSIG",         "dns_request",  "TSIG"        },
-  { "ReqSIG0",         "dns_request",  "SIG0"        },
-  { "ReqBadSIG",       "dns_request",  "BadSIG"      },
-  { "ReqTCP",          "dns_request",  "TCP"         },
+  { "Requestv4",       "dns_request",  "IPv4"          },
+  { "Requestv6",       "dns_request",  "IPv6"          },
+  { "ReqEdns0",        "dns_request",  "EDNS0"         },
+  { "ReqBadEDNSVer",   "dns_request",  "BadEDNSVer"    },
+  { "ReqTSIG",         "dns_request",  "TSIG"          },
+  { "ReqSIG0",         "dns_request",  "SIG0"          },
+  { "ReqBadSIG",       "dns_request",  "BadSIG"        },
+  { "ReqTCP",          "dns_request",  "TCP"           },
   /* Rejects */
-  { "AuthQryRej",      "dns_reject",   "authorative" },
-  { "RecQryRej",       "dns_reject",   "recursive"   },
-  { "XfrRej",          "dns_reject",   "transfer"    },
-  { "UpdateRej",       "dns_reject",   "update"      },
+  { "AuthQryRej",      "dns_reject",   "authoritative" },
+  { "RecQryRej",       "dns_reject",   "recursive"     },
+  { "XfrRej",          "dns_reject",   "transfer"      },
+  { "UpdateRej",       "dns_reject",   "update"        },
   /* Responses */
-  { "Response",        "dns_response", "normal"      },
-  { "TruncatedResp",   "dns_response", "truncated"   },
-  { "RespEDNS0",       "dns_response", "EDNS0"       },
-  { "RespTSIG",        "dns_response", "TSIG"        },
-  { "RespSIG0",        "dns_response", "SIG0"        },
+  { "Response",        "dns_response", "normal"        },
+  { "TruncatedResp",   "dns_response", "truncated"     },
+  { "RespEDNS0",       "dns_response", "EDNS0"         },
+  { "RespTSIG",        "dns_response", "TSIG"          },
+  { "RespSIG0",        "dns_response", "SIG0"          },
   /* Queries */
-  { "QryAuthAns",      "dns_query",    "authorative" },
-  { "QryNoauthAns",    "dns_query",    "nonauth"     },
-  { "QryReferral",     "dns_query",    "referral"    },
-  { "QryRecursion",    "dns_query",    "recursion"   },
-  { "QryDuplicate",    "dns_query",    "dupliate"    },
-  { "QryDropped",      "dns_query",    "dropped"     },
-  { "QryFailure",      "dns_query",    "failure"     },
+  { "QryAuthAns",      "dns_query",    "authoritative" },
+  { "QryNoauthAns",    "dns_query",    "nonauth"       },
+  { "QryReferral",     "dns_query",    "referral"      },
+  { "QryRecursion",    "dns_query",    "recursion"     },
+  { "QryDuplicate",    "dns_query",    "duplicate"     },
+  { "QryDropped",      "dns_query",    "dropped"       },
+  { "QryFailure",      "dns_query",    "failure"       },
   /* Response codes */
-  { "QrySuccess",      "dns_rcode",    "tx-NOERROR"  },
-  { "QryNxrrset",      "dns_rcode",    "tx-NXRRSET"  },
-  { "QrySERVFAIL",     "dns_rcode",    "tx-SERVFAIL" },
-  { "QryFORMERR",      "dns_rcode",    "tx-FORMERR"  },
-  { "QryNXDOMAIN",     "dns_rcode",    "tx-NXDOMAIN" }
+  { "QrySuccess",      "dns_rcode",    "tx-NOERROR"    },
+  { "QryNxrrset",      "dns_rcode",    "tx-NXRRSET"    },
+  { "QrySERVFAIL",     "dns_rcode",    "tx-SERVFAIL"   },
+  { "QryFORMERR",      "dns_rcode",    "tx-FORMERR"    },
+  { "QryNXDOMAIN",     "dns_rcode",    "tx-NXDOMAIN"   }
 #if 0
-  { "XfrReqDone",      "type", "type_instance"       },
-  { "UpdateReqFwd",    "type", "type_instance"       },
-  { "UpdateRespFwd",   "type", "type_instance"       },
-  { "UpdateFwdFail",   "type", "type_instance"       },
-  { "UpdateDone",      "type", "type_instance"       },
-  { "UpdateFail",      "type", "type_instance"       },
-  { "UpdateBadPrereq", "type", "type_instance"       },
+  { "XfrReqDone",      "type",         "type_instance" },
+  { "UpdateReqFwd",    "type",         "type_instance" },
+  { "UpdateRespFwd",   "type",         "type_instance" },
+  { "UpdateFwdFail",   "type",         "type_instance" },
+  { "UpdateDone",      "type",         "type_instance" },
+  { "UpdateFail",      "type",         "type_instance" },
+  { "UpdateBadPrereq", "type",         "type_instance" },
 #endif
 };
 static int nsstats_translation_table_length =
@@ -246,16 +247,12 @@ static int memsummary_translation_table_length =
 static void submit (time_t ts, const char *plugin_instance, /* {{{ */
     const char *type, const char *type_instance, value_t value)
 {
-  value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  values[0] = value;
-
-  vl.values = values;
+  vl.values = &value;
   vl.values_len = 1;
   if (config_parse_time)
     vl.time = TIME_T_TO_CDTIME_T (ts);
-  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
   sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
   if (plugin_instance) {
     sstrncpy(vl.plugin_instance, plugin_instance,
@@ -266,7 +263,7 @@ static void submit (time_t ts, const char *plugin_instance, /* {{{ */
   if (type_instance) {
     sstrncpy(vl.type_instance, type_instance,
         sizeof(vl.type_instance));
-    replace_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+    replace_special (vl.type_instance, sizeof (vl.type_instance));
   }
   plugin_dispatch_values(&vl);
 } /* }}} void submit */
@@ -276,14 +273,14 @@ static size_t bind_curl_callback (void *buf, size_t size, /* {{{ */
 {
   size_t len = size * nmemb;
 
-  if (len <= 0)
+  if (len == 0)
     return (len);
 
   if ((bind_buffer_fill + len) >= bind_buffer_size)
   {
     char *temp;
 
-    temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
+    temp = realloc (bind_buffer, bind_buffer_fill + len + 1);
     if (temp == NULL)
     {
       ERROR ("bind plugin: realloc failed.");
@@ -308,12 +305,11 @@ static int bind_xml_table_callback (const char *name, value_t value, /* {{{ */
     time_t current_time, void *user_data)
 {
   translation_table_ptr_t *table = (translation_table_ptr_t *) user_data;
-  size_t i;
 
   if (table == NULL)
     return (-1);
 
-  for (i = 0; i < table->table_length; i++)
+  for (size_t i = 0; i < table->table_length; i++)
   {
     if (strcmp (table->table[i].xml_name, name) != 0)
       continue;
@@ -369,9 +365,11 @@ static int bind_xml_read_derive (xmlDoc *doc, xmlNode *node, /* {{{ */
   {
     ERROR ("bind plugin: Parsing string \"%s\" to derive value failed.",
         str_ptr);
+    xmlFree(str_ptr);
     return (-1);
   }
 
+  xmlFree(str_ptr);
   *ret_value = value.derive;
   return (0);
 } /* }}} int bind_xml_read_derive */
@@ -414,7 +412,7 @@ static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
   xmlNode *node;
   char *str_ptr;
   char *tmp;
-  struct tm tm;
+  struct tm tm = { 0 };
 
   xpathObj = xmlXPathEvalExpression (BAD_CAST xpath_expression, xpathCtx);
   if (xpathObj == NULL)
@@ -455,7 +453,6 @@ static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
     return (-1);
   }
 
-  memset (&tm, 0, sizeof(tm));
   tmp = strptime (str_ptr, "%Y-%m-%dT%T", &tm);
   xmlFree(str_ptr);
   if (tmp == NULL)
@@ -471,7 +468,7 @@ static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
   return (0);
 } /* }}} int bind_xml_read_timestamp */
 
-/* 
+/*
  * bind_parse_generic_name_value
  *
  * Reads statistics in the form:
@@ -488,7 +485,6 @@ static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ *
 {
   xmlXPathObject *xpathObj = NULL;
   int num_entries;
-  int i;
 
   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
   if (xpathObj == NULL)
@@ -500,19 +496,18 @@ static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ *
 
   num_entries = 0;
   /* Iterate over all matching nodes. */
-  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
   {
     xmlNode *name_node = NULL;
     xmlNode *counter = NULL;
     xmlNode *parent;
-    xmlNode *child;
 
     parent = xpathObj->nodesetval->nodeTab[i];
     DEBUG ("bind plugin: bind_parse_generic_name_value: parent->name = %s;",
         (char *) parent->name);
 
     /* Iterate over all child nodes. */
-    for (child = parent->xmlChildrenNode;
+    for (xmlNode *child = parent->xmlChildrenNode;
         child != NULL;
         child = child->next)
     {
@@ -556,7 +551,7 @@ static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ *
   return (0);
 } /* }}} int bind_parse_generic_name_value */
 
-/* 
+/*
  * bind_parse_generic_value_list
  *
  * Reads statistics in the form:
@@ -575,7 +570,6 @@ static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ *
 {
   xmlXPathObject *xpathObj = NULL;
   int num_entries;
-  int i;
 
   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
   if (xpathObj == NULL)
@@ -587,12 +581,10 @@ static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ *
 
   num_entries = 0;
   /* Iterate over all matching nodes. */
-  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
   {
-    xmlNode *child;
-
     /* Iterate over all child nodes. */
-    for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
+    for (xmlNode *child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
         child != NULL;
         child = child->next)
     {
@@ -646,7 +638,6 @@ static int bind_parse_generic_name_attr_value_list (const char *xpath_expression
 {
   xmlXPathObject *xpathObj = NULL;
   int num_entries;
-  int i;
 
   xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
   if (xpathObj == NULL)
@@ -658,41 +649,39 @@ static int bind_parse_generic_name_attr_value_list (const char *xpath_expression
 
   num_entries = 0;
   /* Iterate over all matching nodes. */
-  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  for (int i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
   {
-    xmlNode *child;
-
     /* Iterate over all child nodes. */
-    for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
+    for (xmlNode *child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
         child != NULL;
         child = child->next)
     {
       if (child->type != XML_ELEMENT_NODE)
         continue;
 
-      if (strncmp ("counter", (char *) child->name, strlen ("counter")) == 0)
+      if (strncmp ("counter", (char *) child->name, strlen ("counter")) != 0)
+        continue;
+
+      char *attr_name;
+      value_t value;
+      int status;
+
+      attr_name = (char *) xmlGetProp (child, BAD_CAST "name");
+      if (attr_name == NULL)
       {
-        char *attr_name;
-        value_t value;
-        int status;
-
-        attr_name = (char *) xmlGetProp (child, BAD_CAST "name");
-        if (attr_name == NULL)
-        {
-          DEBUG ("bind plugin: found <counter> without name.");
-          continue;
-        }
-        if (ds_type == DS_TYPE_GAUGE)
-          status = bind_xml_read_gauge (doc, child, &value.gauge);
-        else
-          status = bind_xml_read_derive (doc, child, &value.derive);
-        if (status != 0)
-          continue;
-
-        status = (*list_callback) (attr_name, value, current_time, user_data);
-        if (status == 0)
-          num_entries++;
+        DEBUG ("bind plugin: found <counter> without name.");
+        continue;
       }
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, child, &value.gauge);
+      else
+        status = bind_xml_read_derive (doc, child, &value.derive);
+      if (status != 0)
+        continue;
+
+      status = (*list_callback) (attr_name, value, current_time, user_data);
+      if (status == 0)
+        num_entries++;
     }
   }
 
@@ -711,28 +700,42 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
 {
   xmlXPathObject *path_obj;
   char *zone_name = NULL;
-  int i;
   size_t j;
 
-  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
-  if (path_obj == NULL)
+  if (version >= 3)
   {
-    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
-    return (-1);
+    char *n = (char *) xmlGetProp (node, BAD_CAST "name");
+    char *c = (char *) xmlGetProp (node, BAD_CAST "rdataclass");
+    if (n && c)
+    {
+      zone_name = (char *) xmlMalloc(strlen(n) + strlen(c) + 2);
+      snprintf(zone_name, strlen(n) + strlen(c) + 2, "%s/%s", n, c);
+    }
+    xmlFree(n);
+    xmlFree(c);
   }
-
-  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  else
   {
-    zone_name = (char *) xmlNodeListGetString (doc,
-        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
-    if (zone_name != NULL)
-      break;
+    path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+    if (path_obj == NULL)
+    {
+      ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+      return (-1);
+    }
+
+    for (int i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+    {
+      zone_name = (char *) xmlNodeListGetString (doc,
+          path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+      if (zone_name != NULL)
+        break;
+    }
+    xmlXPathFreeObject (path_obj);
   }
 
   if (zone_name == NULL)
   {
     ERROR ("bind plugin: Could not determine zone name.");
-    xmlXPathFreeObject (path_obj);
     return (-1);
   }
 
@@ -745,11 +748,8 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
   xmlFree (zone_name);
   zone_name = NULL;
 
-  if (j >= views_num)
-  {
-    xmlXPathFreeObject (path_obj);
+  if (j >= view->zones_num)
     return (0);
-  }
 
   zone_name = view->zones[j];
 
@@ -759,7 +759,7 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
   { /* Parse the <counters> tag {{{ */
     char plugin_instance[DATA_MAX_NAME_LEN];
     translation_table_ptr_t table_ptr =
-    { 
+    {
       nsstats_translation_table,
       nsstats_translation_table_length,
       plugin_instance
@@ -768,14 +768,31 @@ static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
     ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-zone-%s",
         view->name, zone_name);
 
-    bind_parse_generic_value_list (/* xpath = */ "counters",
+    if (version == 3)
+    {
+      list_info_ptr_t list_info =
+      {
+        plugin_instance,
+        /* type = */ "dns_qtype"
+      };
+      bind_parse_generic_name_attr_value_list (/* xpath = */ "counters[@type='rcode']",
         /* callback = */ bind_xml_table_callback,
         /* user_data = */ &table_ptr,
         doc, path_ctx, current_time, DS_TYPE_COUNTER);
+      bind_parse_generic_name_attr_value_list (/* xpath = */ "counters[@type='qtype']",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_value_list (/* xpath = */ "counters",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, path_ctx, current_time, DS_TYPE_COUNTER);
+    }
   } /* }}} */
 
-  xmlXPathFreeObject (path_obj);
-
   return (0);
 } /* }}} int bind_xml_stats_handle_zone */
 
@@ -785,7 +802,6 @@ static int bind_xml_stats_search_zones (int version, xmlDoc *doc, /* {{{ */
 {
   xmlXPathObject *zone_nodes = NULL;
   xmlXPathContext *zone_path_context;
-  int i;
 
   zone_path_context = xmlXPathNewContext (doc);
   if (zone_path_context == NULL)
@@ -802,10 +818,8 @@ static int bind_xml_stats_search_zones (int version, xmlDoc *doc, /* {{{ */
     return (-1);
   }
 
-  for (i = 0; i < zone_nodes->nodesetval->nodeNr; i++)
+  for (int i = 0; i < zone_nodes->nodesetval->nodeNr; i++)
   {
-    xmlNode *node;
-
     node = zone_nodes->nodesetval->nodeTab[i];
     assert (node != NULL);
 
@@ -825,7 +839,6 @@ static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
 {
   char *view_name = NULL;
   cb_view_t *view;
-  int i;
   size_t j;
 
   if (version == 3)
@@ -857,7 +870,7 @@ static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
       return (-1);
     }
 
-    for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+    for (int i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
     {
       view_name = (char *) xmlNodeListGetString (doc,
           path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
@@ -925,7 +938,7 @@ static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
   {
     char plugin_instance[DATA_MAX_NAME_LEN];
     translation_table_ptr_t table_ptr =
-    { 
+    {
       resstats_translation_table,
       resstats_translation_table_length,
       plugin_instance
@@ -968,8 +981,7 @@ static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
         doc, path_ctx, current_time, DS_TYPE_GAUGE);
   } /* }}} */
 
-  // v3 does not provide per-zone stats any more
-  if (version < 3 && view->zones_num > 0)
+  if (view->zones_num > 0)
     bind_xml_stats_search_zones (version, doc, path_ctx, node, view,
         current_time);
 
@@ -981,7 +993,6 @@ static int bind_xml_stats_search_views (int version, xmlDoc *doc, /* {{{ */
 {
   xmlXPathObject *view_nodes = NULL;
   xmlXPathContext *view_path_context;
-  int i;
 
   view_path_context = xmlXPathNewContext (doc);
   if (view_path_context == NULL)
@@ -998,7 +1009,7 @@ static int bind_xml_stats_search_views (int version, xmlDoc *doc, /* {{{ */
     return (-1);
   }
 
-  for (i = 0; i < view_nodes->nodesetval->nodeNr; i++)
+  for (int i = 0; i < view_nodes->nodesetval->nodeNr; i++)
   {
     xmlNode *node;
 
@@ -1016,24 +1027,142 @@ static int bind_xml_stats_search_views (int version, xmlDoc *doc, /* {{{ */
   return (0);
 } /* }}} int bind_xml_stats_search_views */
 
-static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
-    xmlXPathContext *xpathCtx, xmlNode *statsnode)
+static void bind_xml_stats_v3 (xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode, time_t current_time)
 {
-  time_t current_time = 0;
-  int status;
+  /* XPath:     server/counters[@type='opcode']
+   * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
+   * Layout v3:
+   *   <counters type="opcode">
+   *     <counter name="A">1</counter>
+   *     :
+   *   </counters>
+   */
+  if (global_opcodes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-opcodes",
+      /* type = */ "dns_opcode"
+    };
+    bind_parse_generic_name_attr_value_list (/* xpath = */ "server/counters[@type='opcode']",
+      /* callback = */ bind_xml_list_callback,
+      /* user_data = */ &list_info,
+      doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
 
-  xpathCtx->node = statsnode;
+  /* XPath:     server/counters[@type='qtype']
+   * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
+   *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
+   *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
+   * Layout v3:
+   *   <counters type="opcode">
+   *     <counter name="A">1</counter>
+   *     :
+   *   </counters>
+   */
+  if (global_qtypes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-qtypes",
+      /* type = */ "dns_qtype"
+    };
 
-  /* TODO: Check `server/boot-time' to recognize server restarts. */
+    bind_parse_generic_name_attr_value_list (/* xpath = */ "server/counters[@type='qtype']",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
 
-  status = bind_xml_read_timestamp ("server/current-time",
-      doc, xpathCtx, &current_time);
-  if (status != 0)
+  /* XPath:     server/counters[@type='nsstat']
+   * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
+   *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
+   *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
+   *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
+   *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
+   *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
+   *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
+   *            UpdateBadPrereq
+   * Layout v3:
+   *   <counters type="nsstat"
+   *     <counter name="Requestv4">1</counter>
+   *     <counter name="Requestv6">0</counter>
+   *     :
+   *   </counter>
+   */
+  if (global_server_stats)
   {
-    ERROR ("bind plugin: Reading `server/current-time' failed.");
-    return (-1);
+    translation_table_ptr_t table_ptr =
+    {
+      nsstats_translation_table,
+      nsstats_translation_table_length,
+      /* plugin_instance = */ "global-server_stats"
+    };
+
+    bind_parse_generic_name_attr_value_list ("server/counters[@type='nsstat']",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
   }
-  DEBUG ("bind plugin: Current server time is %i.", (int) current_time);
+
+  /* XPath:     server/zonestats, server/zonestat, server/counters[@type='zonestat']
+   * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
+   *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
+   *            XfrSuccess, XfrFail
+   * Layout v3:
+   *   <counters type="zonestat"
+   *     <counter name="NotifyOutv4">0</counter>
+   *     <counter name="NotifyOutv6">0</counter>
+   *     :
+   *   </counter>
+   */
+  if (global_zone_maint_stats)
+  {
+    translation_table_ptr_t table_ptr =
+    {
+      zonestats_translation_table,
+      zonestats_translation_table_length,
+      /* plugin_instance = */ "global-zone_maint_stats"
+    };
+
+    bind_parse_generic_name_attr_value_list ("server/counters[@type='zonestat']",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+
+  /* XPath:     server/resstats, server/counters[@type='resstat']
+   * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
+   *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
+   *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
+   *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
+   * Layout v3:
+   *   <counters type="resstat"
+   *     <counter name="Queryv4">0</counter>
+   *     <counter name="Queryv6">0</counter>
+   *     :
+   *   </counter>
+   */
+  if (global_resolver_stats != 0)
+  {
+    translation_table_ptr_t table_ptr =
+    {
+      resstats_translation_table,
+      resstats_translation_table_length,
+      /* plugin_instance = */ "global-resolver_stats"
+    };
+
+    bind_parse_generic_name_attr_value_list ("server/counters[@type='resstat']",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+} /* }}} bind_xml_stats_v3 */
+
+static void bind_xml_stats_v1_v2 (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode, time_t current_time)
+{
   /* XPath:     server/requests/opcode, server/counters[@type='opcode']
    * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
    * Layout V1 and V2:
@@ -1042,12 +1171,6 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
    *     <counter>1</counter>
    *   </opcode>
    *   :
-   *
-   * Layout v3:
-   *   <counters type="opcode">
-   *     <counter name="A">1</counter>
-   *     :
-   *   </counters>
    */
   if (global_opcodes != 0)
   {
@@ -1056,20 +1179,11 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
       /* plugin instance = */ "global-opcodes",
       /* type = */ "dns_opcode"
     };
-    if (version == 3)
-    {
-      bind_parse_generic_name_attr_value_list (/* xpath = */ "server/counters[@type='opcode']",
+
+    bind_parse_generic_name_value (/* xpath = */ "server/requests/opcode",
         /* callback = */ bind_xml_list_callback,
         /* user_data = */ &list_info,
         doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
-    else
-    {
-      bind_parse_generic_name_value (/* xpath = */ "server/requests/opcode",
-          /* callback = */ bind_xml_list_callback,
-          /* user_data = */ &list_info,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
   }
 
   /* XPath:     server/queries-in/rdtype, server/counters[@type='qtype']
@@ -1082,12 +1196,6 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
    *     <counter>1</counter>
    *   </rdtype>
    *   :
-   *
-   * Layout v3:
-   *   <counters type="opcode">
-   *     <counter name="A">1</counter>
-   *     :
-   *   </counters>
    */
   if (global_qtypes != 0)
   {
@@ -1096,20 +1204,11 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
       /* plugin instance = */ "global-qtypes",
       /* type = */ "dns_qtype"
     };
-    if (version == 3)
-    {
-      bind_parse_generic_name_attr_value_list (/* xpath = */ "server/counters[@type='qtype']",
-          /* callback = */ bind_xml_list_callback,
-          /* user_data = */ &list_info,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
-    else
-    {
-      bind_parse_generic_name_value (/* xpath = */ "server/queries-in/rdtype",
-          /* callback = */ bind_xml_list_callback,
-          /* user_data = */ &list_info,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
+
+    bind_parse_generic_name_value (/* xpath = */ "server/queries-in/rdtype",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
   }
 
   /* XPath:     server/nsstats, server/nsstat, server/counters[@type='nsstat']
@@ -1137,17 +1236,11 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
    *     <counter>0</counter>
    *   </nsstat>
    *   :
-   * Layout v3:
-   *   <counters type="nsstat"
-   *     <counter name="Requestv4">1</counter>
-   *     <counter name="Requestv6">0</counter>
-   *     :
-   *   </counter>
    */
   if (global_server_stats)
   {
     translation_table_ptr_t table_ptr =
-    { 
+    {
       nsstats_translation_table,
       nsstats_translation_table_length,
       /* plugin_instance = */ "global-server_stats"
@@ -1160,20 +1253,13 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
-    else if (version == 2)
+    else
     {
       bind_parse_generic_name_value ("server/nsstat",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
-    else // version == 3
-    {
-      bind_parse_generic_name_attr_value_list ("server/counters[@type='nsstat']",
-          /* callback = */ bind_xml_table_callback,
-          /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
   }
 
   /* XPath:     server/zonestats, server/zonestat, server/counters[@type='zonestat']
@@ -1196,17 +1282,11 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
    *     <counter>0</counter>
    *   </zonestat>
    *   :
-   * Layout v3:
-   *   <counters type="zonestat"
-   *     <counter name="NotifyOutv4">0</counter>
-   *     <counter name="NotifyOutv6">0</counter>
-   *     :
-   *   </counter>
    */
   if (global_zone_maint_stats)
   {
     translation_table_ptr_t table_ptr =
-    { 
+    {
       zonestats_translation_table,
       zonestats_translation_table_length,
       /* plugin_instance = */ "global-zone_maint_stats"
@@ -1219,20 +1299,13 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
-    else if (version == 2)
+    else
     {
       bind_parse_generic_name_value ("server/zonestat",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
-    else // version == 3
-    {
-      bind_parse_generic_name_attr_value_list ("server/counters[@type='zonestat']",
-          /* callback = */ bind_xml_table_callback,
-          /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
   }
 
   /* XPath:     server/resstats, server/counters[@type='resstat']
@@ -1256,17 +1329,11 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
    *     <counter>0</counter>
    *   </resstat>
    *   :
-   * Layout v3:
-   *   <counters type="resstat"
-   *     <counter name="Queryv4">0</counter>
-   *     <counter name="Queryv6">0</counter>
-   *     :
-   *   </counter>
    */
   if (global_resolver_stats != 0)
   {
     translation_table_ptr_t table_ptr =
-    { 
+    {
       resstats_translation_table,
       resstats_translation_table_length,
       /* plugin_instance = */ "global-resolver_stats"
@@ -1279,21 +1346,43 @@ static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
-    else if (version == 2)
-    {
-      bind_parse_generic_name_value ("server/resstat",
-          /* callback = */ bind_xml_table_callback,
-          /* user_data = */ &table_ptr,
-          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
-    }
     else
     {
-      bind_parse_generic_name_attr_value_list ("server/counters[@type='resstat']",
+      bind_parse_generic_name_value ("server/resstat",
           /* callback = */ bind_xml_table_callback,
           /* user_data = */ &table_ptr,
           doc, xpathCtx, current_time, DS_TYPE_COUNTER);
     }
   }
+} /* }}} bind_xml_stats_v1_v2 */
+
+static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode)
+{
+  time_t current_time = 0;
+  int status;
+
+  xpathCtx->node = statsnode;
+
+  /* TODO: Check `server/boot-time' to recognize server restarts. */
+
+  status = bind_xml_read_timestamp ("server/current-time",
+      doc, xpathCtx, &current_time);
+  if (status != 0)
+  {
+    ERROR ("bind plugin: Reading `server/current-time' failed.");
+    return (-1);
+  }
+  DEBUG ("bind plugin: Current server time is %i.", (int) current_time);
+
+  if (version == 3)
+  {
+    bind_xml_stats_v3(doc, xpathCtx, statsnode, current_time);
+  }
+  else
+  {
+    bind_xml_stats_v1_v2(version, doc, xpathCtx, statsnode, current_time);
+  }
 
   /* XPath:  memory/summary
    * Variables: TotalUse, InUse, BlockSize, ContextSize, Lost
@@ -1334,7 +1423,6 @@ static int bind_xml (const char *data) /* {{{ */
   xmlXPathContext *xpathCtx = NULL;
   xmlXPathObject *xpathObj = NULL;
   int ret = -1;
-  int i;
 
   doc = xmlParseMemory (data, strlen (data));
   if (doc == NULL)
@@ -1364,7 +1452,7 @@ static int bind_xml (const char *data) /* {{{ */
   }
   else
   {
-    for (i = 0; i < xpathObj->nodesetval->nodeNr; i++)
+    for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++)
     {
       xmlNode *node;
       char *attr_version;
@@ -1426,7 +1514,7 @@ static int bind_xml (const char *data) /* {{{ */
     return (-1);
   }
 
-  for (i = 0; i < xpathObj->nodesetval->nodeNr; i++)
+  for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++)
   {
     xmlNode *node;
     char *attr_version;
@@ -1506,7 +1594,7 @@ static int bind_config_add_view_zone (cb_view_t *view, /* {{{ */
     return (-1);
   }
 
-  tmp = (char **) realloc (view->zones,
+  tmp = realloc (view->zones,
       sizeof (char *) * (view->zones_num + 1));
   if (tmp == NULL)
   {
@@ -1529,7 +1617,6 @@ static int bind_config_add_view_zone (cb_view_t *view, /* {{{ */
 static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
 {
   cb_view_t *tmp;
-  int i;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
   {
@@ -1537,7 +1624,7 @@ static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
     return (-1);
   }
 
-  tmp = (cb_view_t *) realloc (views, sizeof (*views) * (views_num + 1));
+  tmp = realloc (views, sizeof (*views) * (views_num + 1));
   if (tmp == NULL)
   {
     ERROR ("bind plugin: realloc failed.");
@@ -1557,11 +1644,11 @@ static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
   if (tmp->name == NULL)
   {
     ERROR ("bind plugin: strdup failed.");
-    free (tmp);
+    sfree (views);
     return (-1);
   }
 
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;
 
@@ -1586,9 +1673,7 @@ static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
 
 static int bind_config (oconfig_item_t *ci) /* {{{ */
 {
-  int i;
-
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;
 
@@ -1600,6 +1685,7 @@ static int bind_config (oconfig_item_t *ci) /* {{{ */
         return (-1);
       }
 
+      sfree (url);
       url = strdup (child->values[0].value.string);
     } else if (strcasecmp ("OpCodes", child->key) == 0)
       bind_config_set_bool ("OpCodes", &global_opcodes, child);
@@ -1617,6 +1703,8 @@ static int bind_config (oconfig_item_t *ci) /* {{{ */
       bind_config_add_view (child);
     else if (strcasecmp ("ParseTime", child->key) == 0)
       cf_util_get_boolean (child, &config_parse_time);
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      cf_util_get_int (child, &timeout);
     else
     {
       WARNING ("bind plugin: Unknown configuration option "
@@ -1646,6 +1734,11 @@ static int bind_init (void) /* {{{ */
   curl_easy_setopt (curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
   curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 50L);
+#ifdef HAVE_CURLOPT_TIMEOUT_MS
+  curl_easy_setopt (curl, CURLOPT_TIMEOUT_MS, (timeout >= 0) ?
+      (long) timeout : (long) CDTIME_T_TO_MS(plugin_get_interval()));
+#endif
+
 
   return (0);
 } /* }}} int bind_init */