Merge branch 'collectd-5.5' into collectd-5.6
[collectd.git] / src / powerdns.c
1 /**
2  * collectd - src/powerdns.c
3  * Copyright (C) 2007-2008  C-Ware, Inc.
4  * Copyright (C) 2008       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  * Author:
20  *   Luke Heberling <lukeh at c-ware.com>
21  *   Florian Forster <octo at collectd.org>
22  *
23  * DESCRIPTION
24  *   Queries a PowerDNS control socket for statistics
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_llist.h"
32
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <sys/un.h>
41
42 #ifndef UNIX_PATH_MAX
43 # define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
44 #endif
45 #define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", func, sstrerror (errno, errbuf, sizeof (errbuf))); } while (0)
46
47 #define SERVER_SOCKET  LOCALSTATEDIR"/run/pdns.controlsocket"
48 #define SERVER_COMMAND "SHOW * \n"
49
50 #define RECURSOR_SOCKET  LOCALSTATEDIR"/run/pdns_recursor.controlsocket"
51 #define RECURSOR_COMMAND "get noerror-answers nxdomain-answers " \
52   "servfail-answers sys-msec user-msec qa-latency cache-entries cache-hits " \
53   "cache-misses questions \n"
54
55 struct list_item_s;
56 typedef struct list_item_s list_item_t;
57
58 struct list_item_s
59 {
60   enum
61   {
62     SRV_AUTHORITATIVE,
63     SRV_RECURSOR
64   } server_type;
65   int (*func) (list_item_t *item);
66   char *instance;
67
68   char **fields;
69   int fields_num;
70   char *command;
71
72   struct sockaddr_un sockaddr;
73   int socktype;
74 };
75
76 struct statname_lookup_s
77 {
78   const char *name;
79   const char *type;
80   const char *type_instance;
81 };
82 typedef struct statname_lookup_s statname_lookup_t;
83
84 /* Description of statistics returned by the recursor: {{{
85 all-outqueries        counts the number of outgoing UDP queries since starting
86 answers-slow          counts the number of queries answered after 1 second
87 answers0-1            counts the number of queries answered within 1 millisecond
88 answers1-10           counts the number of queries answered within 10 milliseconds
89 answers10-100         counts the number of queries answered within 100 milliseconds
90 answers100-1000       counts the number of queries answered within 1 second
91 cache-bytes           size of the cache in bytes (since 3.3.1)
92 cache-entries         shows the number of entries in the cache
93 cache-hits            counts the number of cache hits since starting, this does not include hits that got answered from the packet-cache
94 cache-misses          counts the number of cache misses since starting
95 case-mismatches       counts the number of mismatches in character case since starting
96 chain-resends         number of queries chained to existing outstanding query
97 client-parse-errors   counts number of client packets that could not be parsed
98 concurrent-queries    shows the number of MThreads currently running
99 dlg-only-drops        number of records dropped because of delegation only setting
100 dont-outqueries       number of outgoing queries dropped because of 'dont-query' setting (since 3.3)
101 edns-ping-matches     number of servers that sent a valid EDNS PING respons
102 edns-ping-mismatches  number of servers that sent an invalid EDNS PING response
103 failed-host-entries   number of servers that failed to resolve
104 ipv6-outqueries       number of outgoing queries over IPv6
105 ipv6-questions        counts all End-user initiated queries with the RD bit set, received over IPv6 UDP
106 malloc-bytes          returns the number of bytes allocated by the process (broken, always returns 0)
107 max-mthread-stack     maximum amount of thread stack ever used
108 negcache-entries      shows the number of entries in the Negative answer cache
109 no-packet-error       number of errorneous received packets
110 noedns-outqueries     number of queries sent out without EDNS
111 noerror-answers       counts the number of times it answered NOERROR since starting
112 noping-outqueries     number of queries sent out without ENDS PING
113 nsset-invalidations   number of times an nsset was dropped because it no longer worked
114 nsspeeds-entries      shows the number of entries in the NS speeds map
115 nxdomain-answers      counts the number of times it answered NXDOMAIN since starting
116 outgoing-timeouts     counts the number of timeouts on outgoing UDP queries since starting
117 over-capacity-drops   questions dropped because over maximum concurrent query limit (since 3.2)
118 packetcache-bytes     size of the packet cache in bytes (since 3.3.1)
119 packetcache-entries   size of packet cache (since 3.2)
120 packetcache-hits      packet cache hits (since 3.2)
121 packetcache-misses    packet cache misses (since 3.2)
122 policy-drops          packets dropped because of (Lua) policy decision
123 qa-latency            shows the current latency average
124 questions             counts all end-user initiated queries with the RD bit set
125 resource-limits       counts number of queries that could not be performed because of resource limits
126 security-status       security status based on security polling
127 server-parse-errors   counts number of server replied packets that could not be parsed
128 servfail-answers      counts the number of times it answered SERVFAIL since starting
129 spoof-prevents        number of times PowerDNS considered itself spoofed, and dropped the data
130 sys-msec              number of CPU milliseconds spent in 'system' mode
131 tcp-client-overflow   number of times an IP address was denied TCP access because it already had too many connections
132 tcp-clients           counts the number of currently active TCP/IP clients
133 tcp-outqueries        counts the number of outgoing TCP queries since starting
134 tcp-questions         counts all incoming TCP queries (since starting)
135 throttle-entries      shows the number of entries in the throttle map
136 throttled-out         counts the number of throttled outgoing UDP queries since starting
137 throttled-outqueries  idem to throttled-out
138 unauthorized-tcp      number of TCP questions denied because of allow-from restrictions
139 unauthorized-udp      number of UDP questions denied because of allow-from restrictions
140 unexpected-packets    number of answers from remote servers that were unexpected (might point to spoofing)
141 unreachables          number of times nameservers were unreachable since starting
142 uptime                number of seconds process has been running (since 3.1.5)
143 user-msec             number of CPU milliseconds spent in 'user' mode
144 }}} */
145
146 static const char* const default_server_fields[] = /* {{{ */
147 {
148   "latency",
149   "packetcache-hit",
150   "packetcache-miss",
151   "packetcache-size",
152   "query-cache-hit",
153   "query-cache-miss",
154   "recursing-answers",
155   "recursing-questions",
156   "tcp-answers",
157   "tcp-queries",
158   "udp-answers",
159   "udp-queries",
160 }; /* }}} */
161 static int default_server_fields_num = STATIC_ARRAY_SIZE (default_server_fields);
162
163 static statname_lookup_t lookup_table[] = /* {{{ */
164 {
165   /*********************
166    * Server statistics *
167    *********************/
168   /* Questions */
169   {"recursing-questions",    "dns_question", "recurse"},
170   {"tcp-queries",            "dns_question", "tcp"},
171   {"udp-queries",            "dns_question", "udp"},
172   {"rd-queries",             "dns_question", "rd"},
173
174   /* Answers */
175   {"recursing-answers",      "dns_answer",   "recurse"},
176   {"tcp-answers",            "dns_answer",   "tcp"},
177   {"udp-answers",            "dns_answer",   "udp"},
178   {"recursion-unanswered",   "dns_answer",   "recursion-unanswered"},
179   {"udp-answers-bytes",      "total_bytes",  "udp-answers-bytes"},
180
181   /* Cache stuff */
182   {"cache-bytes",            "cache_size",   "cache-bytes"},
183   {"packetcache-bytes",      "cache_size",   "packet-bytes"},
184   {"packetcache-entries",    "cache_size",   "packet-entries"},
185   {"packetcache-hit",        "cache_result", "packet-hit"},
186   {"packetcache-hits",       "cache_result", "packet-hit"},
187   {"packetcache-miss",       "cache_result", "packet-miss"},
188   {"packetcache-misses",     "cache_result", "packet-miss"},
189   {"packetcache-size",       "cache_size",   "packet"},
190   {"key-cache-size",         "cache_size",   "key"},
191   {"meta-cache-size",        "cache_size",   "meta"},
192   {"signature-cache-size",   "cache_size",   "signature"},
193   {"query-cache-hit",        "cache_result", "query-hit"},
194   {"query-cache-miss",       "cache_result", "query-miss"},
195
196   /* Latency */
197   {"latency",                "latency",      NULL},
198
199   /* DNS updates */
200   {"dnsupdate-answers",      "dns_answer",   "dnsupdate-answer"},
201   {"dnsupdate-changes",      "dns_question", "dnsupdate-changes"},
202   {"dnsupdate-queries",      "dns_question", "dnsupdate-queries"},
203   {"dnsupdate-refused",      "dns_answer",   "dnsupdate-refused"},
204
205   /* Other stuff.. */
206   {"corrupt-packets",        "ipt_packets",  "corrupt"},
207   {"deferred-cache-inserts", "counter",      "cache-deferred_insert"},
208   {"deferred-cache-lookup",  "counter",      "cache-deferred_lookup"},
209   {"dont-outqueries",        "dns_question", "dont-outqueries"},
210   {"qsize-a",                "cache_size",   "answers"},
211   {"qsize-q",                "cache_size",   "questions"},
212   {"servfail-packets",       "ipt_packets",  "servfail"},
213   {"timedout-packets",       "ipt_packets",  "timeout"},
214   {"udp4-answers",           "dns_answer",   "udp4"},
215   {"udp4-queries",           "dns_question", "queries-udp4"},
216   {"udp6-answers",           "dns_answer",   "udp6"},
217   {"udp6-queries",           "dns_question", "queries-udp6"},
218   {"security-status",        "dns_question", "security-status"},
219   {"udp-do-queries",         "dns_question", "udp-do_queries"},
220   {"signatures",             "counter",      "signatures"},
221
222   /***********************
223    * Recursor statistics *
224    ***********************/
225   /* Answers by return code */
226   {"noerror-answers",      "dns_rcode",    "NOERROR"},
227   {"nxdomain-answers",     "dns_rcode",    "NXDOMAIN"},
228   {"servfail-answers",     "dns_rcode",    "SERVFAIL"},
229
230   /* CPU utilization */
231   {"sys-msec",             "cpu",          "system"},
232   {"user-msec",            "cpu",          "user"},
233
234   /* Question-to-answer latency */
235   {"qa-latency",           "latency",      NULL},
236
237   /* Cache */
238   {"cache-entries",        "cache_size",   NULL},
239   {"cache-hits",           "cache_result", "hit"},
240   {"cache-misses",         "cache_result", "miss"},
241
242   /* Total number of questions.. */
243   {"questions",            "dns_qtype",    "total"},
244
245   /* All the other stuff.. */
246   {"all-outqueries",       "dns_question", "outgoing"},
247   {"answers0-1",           "dns_answer",   "0_1"},
248   {"answers1-10",          "dns_answer",   "1_10"},
249   {"answers10-100",        "dns_answer",   "10_100"},
250   {"answers100-1000",      "dns_answer",   "100_1000"},
251   {"answers-slow",         "dns_answer",   "slow"},
252   {"case-mismatches",      "counter",      "case_mismatches"},
253   {"chain-resends",        "dns_question", "chained"},
254   {"client-parse-errors",  "counter",      "drops-client_parse_error"},
255   {"concurrent-queries",   "dns_question", "concurrent"},
256   {"dlg-only-drops",       "counter",      "drops-delegation_only"},
257   {"edns-ping-matches",    "counter",      "edns-ping_matches"},
258   {"edns-ping-mismatches", "counter",      "edns-ping_mismatches"},
259   {"failed-host-entries",  "counter",      "entries-failed_host"},
260   {"ipv6-outqueries",      "dns_question", "outgoing-ipv6"},
261   {"ipv6-questions",       "dns_question", "incoming-ipv6"},
262   {"malloc-bytes",         "gauge",        "malloc_bytes"},
263   {"max-mthread-stack",    "gauge",        "max_mthread_stack"},
264   {"no-packet-error",      "gauge",        "no_packet_error"},
265   {"noedns-outqueries",    "dns_question", "outgoing-noedns"},
266   {"noping-outqueries",    "dns_question", "outgoing-noping"},
267   {"over-capacity-drops",  "dns_question", "incoming-over_capacity"},
268   {"negcache-entries",     "cache_size",   "negative"},
269   {"nsspeeds-entries",     "gauge",        "entries-ns_speeds"},
270   {"nsset-invalidations",  "counter",      "ns_set_invalidation"},
271   {"outgoing-timeouts",    "counter",      "drops-timeout_outgoing"},
272   {"policy-drops",         "counter",      "drops-policy"},
273   {"resource-limits",      "counter",      "drops-resource_limit"},
274   {"server-parse-errors",  "counter",      "drops-server_parse_error"},
275   {"spoof-prevents",       "counter",      "drops-spoofed"},
276   {"tcp-client-overflow",  "counter",      "denied-client_overflow_tcp"},
277   {"tcp-clients",          "gauge",        "clients-tcp"},
278   {"tcp-outqueries",       "dns_question", "outgoing-tcp"},
279   {"tcp-questions",        "dns_question", "incoming-tcp"},
280   {"throttled-out",        "dns_question", "outgoing-throttled"},
281   {"throttle-entries",     "gauge",        "entries-throttle"},
282   {"throttled-outqueries", "dns_question", "outgoing-throttle"},
283   {"unauthorized-tcp",     "counter",      "denied-unauthorized_tcp"},
284   {"unauthorized-udp",     "counter",      "denied-unauthorized_udp"},
285   {"unexpected-packets",   "dns_answer",   "unexpected"},
286   {"uptime",               "uptime",       NULL}
287 }; /* }}} */
288 static int lookup_table_length = STATIC_ARRAY_SIZE (lookup_table);
289
290 static llist_t *list = NULL;
291
292 #define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
293 static char *local_sockpath = NULL;
294
295 /* TODO: Do this before 4.4:
296  * - Update the collectd.conf(5) manpage.
297  *
298  * -octo
299  */
300
301 /* <https://doc.powerdns.com/md/recursor/stats/> */
302 static void submit (const char *plugin_instance, /* {{{ */
303     const char *pdns_type, const char *value)
304 {
305   value_list_t vl = VALUE_LIST_INIT;
306   value_t values[1];
307
308   const char *type = NULL;
309   const char *type_instance = NULL;
310   const data_set_t *ds;
311
312   int i;
313
314   for (i = 0; i < lookup_table_length; i++)
315     if (strcmp (lookup_table[i].name, pdns_type) == 0)
316       break;
317
318   if (i >= lookup_table_length)
319   {
320     INFO ("powerdns plugin: submit: Not found in lookup table: %s = %s;",
321         pdns_type, value);
322     return;
323   }
324
325   if (lookup_table[i].type == NULL)
326     return;
327
328   type = lookup_table[i].type;
329   type_instance = lookup_table[i].type_instance;
330
331   ds = plugin_get_ds (type);
332   if (ds == NULL)
333   {
334     ERROR ("powerdns plugin: The lookup table returned type `%s', "
335         "but I cannot find it via `plugin_get_ds'.",
336         type);
337     return;
338   }
339
340   if (ds->ds_num != 1)
341   {
342     ERROR ("powerdns plugin: type `%s' has %zu data sources, "
343         "but I can only handle one.",
344         type, ds->ds_num);
345     return;
346   }
347
348   if (0 != parse_value (value, &values[0], ds->ds[0].type))
349   {
350     ERROR ("powerdns plugin: Cannot convert `%s' "
351         "to a number.", value);
352     return;
353   }
354
355   vl.values = values;
356   vl.values_len = 1;
357   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
358   sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
359   sstrncpy (vl.type, type, sizeof (vl.type));
360   if (type_instance != NULL)
361     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
362   sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
363
364   plugin_dispatch_values (&vl);
365 } /* }}} static void submit */
366
367 static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
368     char **ret_buffer,
369     size_t *ret_buffer_size)
370 {
371   int sd;
372   int status;
373
374   char temp[4096];
375   char *buffer = NULL;
376   size_t buffer_size = 0;
377
378   struct sockaddr_un sa_unix = { 0 };
379
380   struct timeval stv_timeout;
381   cdtime_t cdt_timeout;
382
383   sd = socket (PF_UNIX, item->socktype, 0);
384   if (sd < 0)
385   {
386     FUNC_ERROR ("socket");
387     return (-1);
388   }
389
390   sa_unix.sun_family = AF_UNIX;
391   sstrncpy (sa_unix.sun_path,
392       (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
393       sizeof (sa_unix.sun_path));
394
395   status = unlink (sa_unix.sun_path);
396   if ((status != 0) && (errno != ENOENT))
397   {
398     FUNC_ERROR ("unlink");
399     close (sd);
400     return (-1);
401   }
402
403   do /* while (0) */
404   {
405     /* We need to bind to a specific path, because this is a datagram socket
406      * and otherwise the daemon cannot answer. */
407     status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
408     if (status != 0)
409     {
410       FUNC_ERROR ("bind");
411       break;
412     }
413
414     /* Make the socket writeable by the daemon.. */
415     status = chmod (sa_unix.sun_path, 0666);
416     if (status != 0)
417     {
418       FUNC_ERROR ("chmod");
419       break;
420     }
421
422     cdt_timeout = plugin_get_interval () * 3 / 4;
423     if (cdt_timeout < TIME_T_TO_CDTIME_T (2))
424       cdt_timeout = TIME_T_TO_CDTIME_T (2);
425
426     CDTIME_T_TO_TIMEVAL (cdt_timeout, &stv_timeout);
427
428     status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &stv_timeout, sizeof (stv_timeout));
429     if (status != 0)
430     {
431       FUNC_ERROR ("setsockopt");
432       break;
433     }
434
435     status = connect (sd, (struct sockaddr *) &item->sockaddr,
436         sizeof (item->sockaddr));
437     if (status != 0)
438     {
439       FUNC_ERROR ("connect");
440       break;
441     }
442
443     status = send (sd, item->command, strlen (item->command), 0);
444     if (status < 0)
445     {
446       FUNC_ERROR ("send");
447       break;
448     }
449
450     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
451     if (status < 0)
452     {
453       FUNC_ERROR ("recv");
454       break;
455     }
456     buffer_size = status + 1;
457     status = 0;
458   } while (0);
459
460   close (sd);
461   unlink (sa_unix.sun_path);
462
463   if (status != 0)
464     return (-1);
465
466   assert (buffer_size > 0);
467   buffer = malloc (buffer_size);
468   if (buffer == NULL)
469   {
470     FUNC_ERROR ("malloc");
471     return (-1);
472   }
473
474   memcpy (buffer, temp, buffer_size - 1);
475   buffer[buffer_size - 1] = 0;
476
477   *ret_buffer = buffer;
478   *ret_buffer_size = buffer_size;
479
480   return (0);
481 } /* }}} int powerdns_get_data_dgram */
482
483 static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
484     char **ret_buffer,
485     size_t *ret_buffer_size)
486 {
487   int sd;
488   int status;
489
490   char temp[4096];
491   char *buffer = NULL;
492   size_t buffer_size = 0;
493
494   sd = socket (PF_UNIX, item->socktype, 0);
495   if (sd < 0)
496   {
497     FUNC_ERROR ("socket");
498     return (-1);
499   }
500
501   struct timeval timeout;
502   timeout.tv_sec=5;
503   timeout.tv_usec=0;
504   status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));
505   if (status != 0)
506   {
507     FUNC_ERROR ("setsockopt");
508     close (sd);
509     return (-1);
510   }
511
512   status = connect (sd, (struct sockaddr *) &item->sockaddr,
513       sizeof (item->sockaddr));
514   if (status != 0)
515   {
516     FUNC_ERROR ("connect");
517     close (sd);
518     return (-1);
519   }
520
521   /* strlen + 1, because we need to send the terminating NULL byte, too. */
522   status = send (sd, item->command, strlen (item->command) + 1,
523       /* flags = */ 0);
524   if (status < 0)
525   {
526     FUNC_ERROR ("send");
527     close (sd);
528     return (-1);
529   }
530
531   while (42)
532   {
533     char *buffer_new;
534
535     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
536     if (status < 0)
537     {
538       FUNC_ERROR ("recv");
539       break;
540     }
541     else if (status == 0)
542       break;
543
544     buffer_new = realloc (buffer, buffer_size + status + 1);
545     if (buffer_new == NULL)
546     {
547       FUNC_ERROR ("realloc");
548       status = -1;
549       break;
550     }
551     buffer = buffer_new;
552
553     memcpy (buffer + buffer_size, temp, status);
554     buffer_size += status;
555     buffer[buffer_size] = 0;
556   } /* while (42) */
557   close (sd);
558
559   if (status < 0)
560   {
561     sfree (buffer);
562   }
563   else
564   {
565     assert (status == 0);
566     *ret_buffer = buffer;
567     *ret_buffer_size = buffer_size;
568   }
569
570   return (status);
571 } /* }}} int powerdns_get_data_stream */
572
573 static int powerdns_get_data (list_item_t *item, char **ret_buffer,
574     size_t *ret_buffer_size)
575 {
576   if (item->socktype == SOCK_DGRAM)
577     return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
578   else if (item->socktype == SOCK_STREAM)
579     return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
580   else
581   {
582     ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
583     return (-1);
584   }
585 } /* int powerdns_get_data */
586
587 static int powerdns_read_server (list_item_t *item) /* {{{ */
588 {
589   char *buffer = NULL;
590   size_t buffer_size = 0;
591   int status;
592
593   char *dummy;
594   char *saveptr;
595
596   char *key;
597   char *value;
598
599   const char* const *fields;
600   int fields_num;
601
602   if (item->command == NULL)
603     item->command = strdup (SERVER_COMMAND);
604   if (item->command == NULL)
605   {
606     ERROR ("powerdns plugin: strdup failed.");
607     return (-1);
608   }
609
610   status = powerdns_get_data (item, &buffer, &buffer_size);
611   if (status != 0)
612     return (-1);
613
614   if (item->fields_num != 0)
615   {
616     fields = (const char* const *) item->fields;
617     fields_num = item->fields_num;
618   }
619   else
620   {
621     fields = default_server_fields;
622     fields_num = default_server_fields_num;
623   }
624
625   assert (fields != NULL);
626   assert (fields_num > 0);
627
628   /* corrupt-packets=0,deferred-cache-inserts=0,deferred-cache-lookup=0,latency=0,packetcache-hit=0,packetcache-miss=0,packetcache-size=0,qsize-q=0,query-cache-hit=0,query-cache-miss=0,recursing-answers=0,recursing-questions=0,servfail-packets=0,tcp-answers=0,tcp-queries=0,timedout-packets=0,udp-answers=0,udp-queries=0,udp4-answers=0,udp4-queries=0,udp6-answers=0,udp6-queries=0, */
629   dummy = buffer;
630   saveptr = NULL;
631   while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
632   {
633     dummy = NULL;
634
635     value = strchr (key, '=');
636     if (value == NULL)
637       break;
638
639     *value = '\0';
640     value++;
641
642     if (value[0] == '\0')
643       continue;
644
645     /* Check if this item was requested. */
646     int i;
647     for (i = 0; i < fields_num; i++)
648       if (strcasecmp (key, fields[i]) == 0)
649         break;
650     if (i >= fields_num)
651       continue;
652
653     submit (item->instance, key, value);
654   } /* while (strtok_r) */
655
656   sfree (buffer);
657
658   return (0);
659 } /* }}} int powerdns_read_server */
660
661 /*
662  * powerdns_update_recursor_command
663  *
664  * Creates a string that holds the command to be sent to the recursor. This
665  * string is stores in the `command' member of the `list_item_t' passed to the
666  * function. This function is called by `powerdns_read_recursor'.
667  */
668 static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
669 {
670   char buffer[4096];
671   int status;
672
673   if (li == NULL)
674     return (0);
675
676   if (li->fields_num < 1)
677   {
678     sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
679   }
680   else
681   {
682     sstrncpy (buffer, "get ", sizeof (buffer));
683     status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
684         li->fields, li->fields_num,
685         /* seperator = */ " ");
686     if (status < 0)
687     {
688       ERROR ("powerdns plugin: strjoin failed.");
689       return (-1);
690     }
691     buffer[sizeof (buffer) - 1] = 0;
692     size_t len = strlen (buffer);
693     if (len < sizeof (buffer) - 2)
694     {
695       buffer[len++] = ' ';
696       buffer[len++] = '\n';
697       buffer[len++] = '\0';
698     }
699   }
700
701   buffer[sizeof (buffer) - 1] = 0;
702   li->command = strdup (buffer);
703   if (li->command == NULL)
704   {
705     ERROR ("powerdns plugin: strdup failed.");
706     return (-1);
707   }
708
709   return (0);
710 } /* }}} int powerdns_update_recursor_command */
711
712 static int powerdns_read_recursor (list_item_t *item) /* {{{ */
713 {
714   char *buffer = NULL;
715   size_t buffer_size = 0;
716   int status;
717
718   char *dummy;
719
720   char *keys_list;
721   char *key;
722   char *key_saveptr;
723   char *value;
724   char *value_saveptr;
725
726   if (item->command == NULL)
727   {
728     status = powerdns_update_recursor_command (item);
729     if (status != 0)
730     {
731       ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
732       return (-1);
733     }
734
735     DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
736         item->command);
737   }
738   assert (item->command != NULL);
739
740   status = powerdns_get_data (item, &buffer, &buffer_size);
741   if (status != 0)
742   {
743     ERROR ("powerdns plugin: powerdns_get_data failed.");
744     return (-1);
745   }
746
747   keys_list = strdup (item->command);
748   if (keys_list == NULL)
749   {
750     FUNC_ERROR ("strdup");
751     sfree (buffer);
752     return (-1);
753   }
754
755   key_saveptr = NULL;
756   value_saveptr = NULL;
757
758   /* Skip the `get' at the beginning */
759   strtok_r (keys_list, " \t", &key_saveptr);
760
761   dummy = buffer;
762   while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
763   {
764     dummy = NULL;
765
766     key = strtok_r (NULL, " \t", &key_saveptr);
767     if (key == NULL)
768       break;
769
770     submit (item->instance, key, value);
771   } /* while (strtok_r) */
772
773   sfree (buffer);
774   sfree (keys_list);
775
776   return (0);
777 } /* }}} int powerdns_read_recursor */
778
779 static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
780     oconfig_item_t *ci)
781 {
782   char **temp;
783
784   if (ci->values_num < 1)
785   {
786     WARNING ("powerdns plugin: The `Collect' option needs "
787         "at least one argument.");
788     return (-1);
789   }
790
791   for (int i = 0; i < ci->values_num; i++)
792     if (ci->values[i].type != OCONFIG_TYPE_STRING)
793     {
794       WARNING ("powerdns plugin: Only string arguments are allowed to "
795           "the `Collect' option.");
796       return (-1);
797     }
798
799   temp = realloc (li->fields,
800       sizeof (char *) * (li->fields_num + ci->values_num));
801   if (temp == NULL)
802   {
803     WARNING ("powerdns plugin: realloc failed.");
804     return (-1);
805   }
806   li->fields = temp;
807
808   for (int i = 0; i < ci->values_num; i++)
809   {
810     li->fields[li->fields_num] = strdup (ci->values[i].value.string);
811     if (li->fields[li->fields_num] == NULL)
812     {
813       WARNING ("powerdns plugin: strdup failed.");
814       continue;
815     }
816     li->fields_num++;
817   }
818
819   /* Invalidate a previously computed command */
820   sfree (li->command);
821
822   return (0);
823 } /* }}} int powerdns_config_add_collect */
824
825 static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
826 {
827   char *socket_temp;
828
829   list_item_t *item;
830   int status;
831
832   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
833   {
834     WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
835         ci->key);
836     return (-1);
837   }
838
839   item = calloc (1, sizeof (*item));
840   if (item == NULL)
841   {
842     ERROR ("powerdns plugin: calloc failed.");
843     return (-1);
844   }
845
846   item->instance = strdup (ci->values[0].value.string);
847   if (item->instance == NULL)
848   {
849     ERROR ("powerdns plugin: strdup failed.");
850     sfree (item);
851     return (-1);
852   }
853
854   /*
855    * Set default values for the members of list_item_t
856    */
857   if (strcasecmp ("Server", ci->key) == 0)
858   {
859     item->server_type = SRV_AUTHORITATIVE;
860     item->func = powerdns_read_server;
861     item->socktype = SOCK_STREAM;
862     socket_temp = strdup (SERVER_SOCKET);
863   }
864   else if (strcasecmp ("Recursor", ci->key) == 0)
865   {
866     item->server_type = SRV_RECURSOR;
867     item->func = powerdns_read_recursor;
868     item->socktype = SOCK_DGRAM;
869     socket_temp = strdup (RECURSOR_SOCKET);
870   }
871   else
872   {
873     /* We must never get here.. */
874     assert (0);
875     return (-1);
876   }
877
878   status = 0;
879   for (int i = 0; i < ci->children_num; i++)
880   {
881     oconfig_item_t *option = ci->children + i;
882
883     if (strcasecmp ("Collect", option->key) == 0)
884       status = powerdns_config_add_collect (item, option);
885     else if (strcasecmp ("Socket", option->key) == 0)
886       status = cf_util_get_string (option, &socket_temp);
887     else
888     {
889       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
890       status = -1;
891     }
892
893     if (status != 0)
894       break;
895   }
896
897   while (status == 0)
898   {
899     llentry_t *e;
900
901     if (socket_temp == NULL)
902     {
903       ERROR ("powerdns plugin: socket_temp == NULL.");
904       status = -1;
905       break;
906     }
907
908     item->sockaddr.sun_family = AF_UNIX;
909     sstrncpy (item->sockaddr.sun_path, socket_temp,
910       sizeof (item->sockaddr.sun_path));
911
912     e = llentry_create (item->instance, item);
913     if (e == NULL)
914     {
915       ERROR ("powerdns plugin: llentry_create failed.");
916       status = -1;
917       break;
918     }
919     llist_append (list, e);
920
921     break;
922   }
923
924   if (status != 0)
925   {
926     sfree (socket_temp);
927     sfree (item);
928     return (-1);
929   }
930
931   DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
932
933   sfree (socket_temp);
934   return (0);
935 } /* }}} int powerdns_config_add_server */
936
937 static int powerdns_config (oconfig_item_t *ci) /* {{{ */
938 {
939   DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
940
941   if (list == NULL)
942   {
943     list = llist_create ();
944
945     if (list == NULL)
946     {
947       ERROR ("powerdns plugin: `llist_create' failed.");
948       return (-1);
949     }
950   }
951
952   for (int i = 0; i < ci->children_num; i++)
953   {
954     oconfig_item_t *option = ci->children + i;
955
956     if ((strcasecmp ("Server", option->key) == 0)
957         || (strcasecmp ("Recursor", option->key) == 0))
958       powerdns_config_add_server (option);
959     else if (strcasecmp ("LocalSocket", option->key) == 0)
960     {
961       if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
962       {
963         WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
964       }
965       else
966       {
967         char *temp = strdup (option->values[0].value.string);
968         if (temp == NULL)
969           return (1);
970         sfree (local_sockpath);
971         local_sockpath = temp;
972       }
973     }
974     else
975     {
976       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
977     }
978   } /* for (i = 0; i < ci->children_num; i++) */
979
980   return (0);
981 } /* }}} int powerdns_config */
982
983 static int powerdns_read (void)
984 {
985   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
986   {
987     list_item_t *item = e->value;
988     item->func (item);
989   }
990
991   return (0);
992 } /* static int powerdns_read */
993
994 static int powerdns_shutdown (void)
995 {
996   if (list == NULL)
997     return (0);
998
999   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
1000   {
1001     list_item_t *item = (list_item_t *) e->value;
1002     e->value = NULL;
1003
1004     sfree (item->instance);
1005     sfree (item->command);
1006     sfree (item);
1007   }
1008
1009   llist_destroy (list);
1010   list = NULL;
1011
1012   return (0);
1013 } /* static int powerdns_shutdown */
1014
1015 void module_register (void)
1016 {
1017   plugin_register_complex_config ("powerdns", powerdns_config);
1018   plugin_register_read ("powerdns", powerdns_read);
1019   plugin_register_shutdown ("powerdns", powerdns_shutdown );
1020 } /* void module_register */
1021
1022 /* vim: set sw=2 sts=2 ts=8 fdm=marker : */