d7aa4e9dbe8ea8d94ca739baa2750b1d2026a707
[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 #define SOCK_ERROR(func, sockpath) do { char errbuf[1024]; ERROR ("powerdns plugin: Socket `%s` %s failed: %s", sockpath, func, sstrerror (errno, errbuf, sizeof (errbuf))); } while (0)
47
48 #define SERVER_SOCKET  LOCALSTATEDIR"/run/pdns.controlsocket"
49 #define SERVER_COMMAND "SHOW * \n"
50
51 #define RECURSOR_SOCKET  LOCALSTATEDIR"/run/pdns_recursor.controlsocket"
52 #define RECURSOR_COMMAND "get noerror-answers nxdomain-answers " \
53   "servfail-answers sys-msec user-msec qa-latency cache-entries cache-hits " \
54   "cache-misses questions\n"
55
56 struct list_item_s;
57 typedef struct list_item_s list_item_t;
58
59 struct list_item_s
60 {
61   enum
62   {
63     SRV_AUTHORITATIVE,
64     SRV_RECURSOR
65   } server_type;
66   int (*func) (list_item_t *item);
67   char *instance;
68
69   char **fields;
70   int fields_num;
71   char *command;
72
73   struct sockaddr_un sockaddr;
74   int socktype;
75 };
76
77 struct statname_lookup_s
78 {
79   const char *name;
80   const char *type;
81   const char *type_instance;
82 };
83 typedef struct statname_lookup_s statname_lookup_t;
84
85 /* Description of statistics returned by the recursor: {{{
86 all-outqueries        counts the number of outgoing UDP queries since starting
87 answers-slow          counts the number of queries answered after 1 second
88 answers0-1            counts the number of queries answered within 1 millisecond
89 answers1-10           counts the number of queries answered within 10 milliseconds
90 answers10-100         counts the number of queries answered within 100 milliseconds
91 answers100-1000       counts the number of queries answered within 1 second
92 cache-bytes           size of the cache in bytes (since 3.3.1)
93 cache-entries         shows the number of entries in the cache
94 cache-hits            counts the number of cache hits since starting, this does not include hits that got answered from the packet-cache
95 cache-misses          counts the number of cache misses since starting
96 case-mismatches       counts the number of mismatches in character case since starting
97 chain-resends         number of queries chained to existing outstanding query
98 client-parse-errors   counts number of client packets that could not be parsed
99 concurrent-queries    shows the number of MThreads currently running
100 dlg-only-drops        number of records dropped because of delegation only setting
101 dont-outqueries       number of outgoing queries dropped because of 'dont-query' setting (since 3.3)
102 edns-ping-matches     number of servers that sent a valid EDNS PING respons
103 edns-ping-mismatches  number of servers that sent an invalid EDNS PING response
104 failed-host-entries   number of servers that failed to resolve
105 ipv6-outqueries       number of outgoing queries over IPv6
106 ipv6-questions        counts all End-user initiated queries with the RD bit set, received over IPv6 UDP
107 malloc-bytes          returns the number of bytes allocated by the process (broken, always returns 0)
108 max-mthread-stack     maximum amount of thread stack ever used
109 negcache-entries      shows the number of entries in the Negative answer cache
110 no-packet-error       number of errorneous received packets
111 noedns-outqueries     number of queries sent out without EDNS
112 noerror-answers       counts the number of times it answered NOERROR since starting
113 noping-outqueries     number of queries sent out without ENDS PING
114 nsset-invalidations   number of times an nsset was dropped because it no longer worked
115 nsspeeds-entries      shows the number of entries in the NS speeds map
116 nxdomain-answers      counts the number of times it answered NXDOMAIN since starting
117 outgoing-timeouts     counts the number of timeouts on outgoing UDP queries since starting
118 over-capacity-drops   questions dropped because over maximum concurrent query limit (since 3.2)
119 packetcache-bytes     size of the packet cache in bytes (since 3.3.1)
120 packetcache-entries   size of packet cache (since 3.2)
121 packetcache-hits      packet cache hits (since 3.2)
122 packetcache-misses    packet cache misses (since 3.2)
123 policy-drops          packets dropped because of (Lua) policy decision
124 qa-latency            shows the current latency average
125 questions             counts all end-user initiated queries with the RD bit set
126 resource-limits       counts number of queries that could not be performed because of resource limits
127 security-status       security status based on security polling
128 server-parse-errors   counts number of server replied packets that could not be parsed
129 servfail-answers      counts the number of times it answered SERVFAIL since starting
130 spoof-prevents        number of times PowerDNS considered itself spoofed, and dropped the data
131 sys-msec              number of CPU milliseconds spent in 'system' mode
132 tcp-client-overflow   number of times an IP address was denied TCP access because it already had too many connections
133 tcp-clients           counts the number of currently active TCP/IP clients
134 tcp-outqueries        counts the number of outgoing TCP queries since starting
135 tcp-questions         counts all incoming TCP queries (since starting)
136 throttle-entries      shows the number of entries in the throttle map
137 throttled-out         counts the number of throttled outgoing UDP queries since starting
138 throttled-outqueries  idem to throttled-out
139 unauthorized-tcp      number of TCP questions denied because of allow-from restrictions
140 unauthorized-udp      number of UDP questions denied because of allow-from restrictions
141 unexpected-packets    number of answers from remote servers that were unexpected (might point to spoofing)
142 unreachables          number of times nameservers were unreachable since starting
143 uptime                number of seconds process has been running (since 3.1.5)
144 user-msec             number of CPU milliseconds spent in 'user' mode
145 }}} */
146
147 static const char* const default_server_fields[] = /* {{{ */
148 {
149   "latency",
150   "packetcache-hit",
151   "packetcache-miss",
152   "packetcache-size",
153   "query-cache-hit",
154   "query-cache-miss",
155   "recursing-answers",
156   "recursing-questions",
157   "tcp-answers",
158   "tcp-queries",
159   "udp-answers",
160   "udp-queries",
161 }; /* }}} */
162 static int default_server_fields_num = STATIC_ARRAY_SIZE (default_server_fields);
163
164 static statname_lookup_t lookup_table[] = /* {{{ */
165 {
166   /*********************
167    * Server statistics *
168    *********************/
169   /* Questions */
170   {"recursing-questions",    "dns_question", "recurse"},
171   {"tcp-queries",            "dns_question", "tcp"},
172   {"udp-queries",            "dns_question", "udp"},
173   {"rd-queries",             "dns_question", "rd"},
174
175   /* Answers */
176   {"recursing-answers",      "dns_answer",   "recurse"},
177   {"tcp-answers",            "dns_answer",   "tcp"},
178   {"udp-answers",            "dns_answer",   "udp"},
179   {"recursion-unanswered",   "dns_answer",   "recursion-unanswered"},
180   {"udp-answers-bytes",      "total_bytes",  "udp-answers-bytes"},
181
182   /* Cache stuff */
183   {"cache-bytes",            "cache_size",   "cache-bytes"},
184   {"packetcache-bytes",      "cache_size",   "packet-bytes"},
185   {"packetcache-entries",    "cache_size",   "packet-entries"},
186   {"packetcache-hit",        "cache_result", "packet-hit"},
187   {"packetcache-hits",       "cache_result", "packet-hit"},
188   {"packetcache-miss",       "cache_result", "packet-miss"},
189   {"packetcache-misses",     "cache_result", "packet-miss"},
190   {"packetcache-size",       "cache_size",   "packet"},
191   {"key-cache-size",         "cache_size",   "key"},
192   {"meta-cache-size",        "cache_size",   "meta"},
193   {"signature-cache-size",   "cache_size",   "signature"},
194   {"query-cache-hit",        "cache_result", "query-hit"},
195   {"query-cache-miss",       "cache_result", "query-miss"},
196
197   /* Latency */
198   {"latency",                "latency",      NULL},
199
200   /* DNS updates */
201   {"dnsupdate-answers",      "dns_answer",   "dnsupdate-answer"},
202   {"dnsupdate-changes",      "dns_question", "dnsupdate-changes"},
203   {"dnsupdate-queries",      "dns_question", "dnsupdate-queries"},
204   {"dnsupdate-refused",      "dns_answer",   "dnsupdate-refused"},
205
206   /* Other stuff.. */
207   {"corrupt-packets",        "ipt_packets",  "corrupt"},
208   {"deferred-cache-inserts", "counter",      "cache-deferred_insert"},
209   {"deferred-cache-lookup",  "counter",      "cache-deferred_lookup"},
210   {"dont-outqueries",        "dns_question", "dont-outqueries"},
211   {"qsize-a",                "cache_size",   "answers"},
212   {"qsize-q",                "cache_size",   "questions"},
213   {"servfail-packets",       "ipt_packets",  "servfail"},
214   {"timedout-packets",       "ipt_packets",  "timeout"},
215   {"udp4-answers",           "dns_answer",   "udp4"},
216   {"udp4-queries",           "dns_question", "queries-udp4"},
217   {"udp6-answers",           "dns_answer",   "udp6"},
218   {"udp6-queries",           "dns_question", "queries-udp6"},
219   {"security-status",        "dns_question", "security-status"},
220   {"udp-do-queries",         "dns_question", "udp-do_queries"},
221   {"signatures",             "counter",      "signatures"},
222
223   /***********************
224    * Recursor statistics *
225    ***********************/
226   /* Answers by return code */
227   {"noerror-answers",      "dns_rcode",    "NOERROR"},
228   {"nxdomain-answers",     "dns_rcode",    "NXDOMAIN"},
229   {"servfail-answers",     "dns_rcode",    "SERVFAIL"},
230
231   /* CPU utilization */
232   {"sys-msec",             "cpu",          "system"},
233   {"user-msec",            "cpu",          "user"},
234
235   /* Question-to-answer latency */
236   {"qa-latency",           "latency",      NULL},
237
238   /* Cache */
239   {"cache-entries",        "cache_size",   NULL},
240   {"cache-hits",           "cache_result", "hit"},
241   {"cache-misses",         "cache_result", "miss"},
242
243   /* Total number of questions.. */
244   {"questions",            "dns_qtype",    "total"},
245
246   /* All the other stuff.. */
247   {"all-outqueries",       "dns_question", "outgoing"},
248   {"answers0-1",           "dns_answer",   "0_1"},
249   {"answers1-10",          "dns_answer",   "1_10"},
250   {"answers10-100",        "dns_answer",   "10_100"},
251   {"answers100-1000",      "dns_answer",   "100_1000"},
252   {"answers-slow",         "dns_answer",   "slow"},
253   {"case-mismatches",      "counter",      "case_mismatches"},
254   {"chain-resends",        "dns_question", "chained"},
255   {"client-parse-errors",  "counter",      "drops-client_parse_error"},
256   {"concurrent-queries",   "dns_question", "concurrent"},
257   {"dlg-only-drops",       "counter",      "drops-delegation_only"},
258   {"edns-ping-matches",    "counter",      "edns-ping_matches"},
259   {"edns-ping-mismatches", "counter",      "edns-ping_mismatches"},
260   {"failed-host-entries",  "counter",      "entries-failed_host"},
261   {"ipv6-outqueries",      "dns_question", "outgoing-ipv6"},
262   {"ipv6-questions",       "dns_question", "incoming-ipv6"},
263   {"malloc-bytes",         "gauge",        "malloc_bytes"},
264   {"max-mthread-stack",    "gauge",        "max_mthread_stack"},
265   {"no-packet-error",      "gauge",        "no_packet_error"},
266   {"noedns-outqueries",    "dns_question", "outgoing-noedns"},
267   {"noping-outqueries",    "dns_question", "outgoing-noping"},
268   {"over-capacity-drops",  "dns_question", "incoming-over_capacity"},
269   {"negcache-entries",     "cache_size",   "negative"},
270   {"nsspeeds-entries",     "gauge",        "entries-ns_speeds"},
271   {"nsset-invalidations",  "counter",      "ns_set_invalidation"},
272   {"outgoing-timeouts",    "counter",      "drops-timeout_outgoing"},
273   {"policy-drops",         "counter",      "drops-policy"},
274   {"resource-limits",      "counter",      "drops-resource_limit"},
275   {"server-parse-errors",  "counter",      "drops-server_parse_error"},
276   {"spoof-prevents",       "counter",      "drops-spoofed"},
277   {"tcp-client-overflow",  "counter",      "denied-client_overflow_tcp"},
278   {"tcp-clients",          "gauge",        "clients-tcp"},
279   {"tcp-outqueries",       "dns_question", "outgoing-tcp"},
280   {"tcp-questions",        "dns_question", "incoming-tcp"},
281   {"throttled-out",        "dns_question", "outgoing-throttled"},
282   {"throttle-entries",     "gauge",        "entries-throttle"},
283   {"throttled-outqueries", "dns_question", "outgoing-throttle"},
284   {"unauthorized-tcp",     "counter",      "denied-unauthorized_tcp"},
285   {"unauthorized-udp",     "counter",      "denied-unauthorized_udp"},
286   {"unexpected-packets",   "dns_answer",   "unexpected"},
287   {"uptime",               "uptime",       NULL}
288 }; /* }}} */
289 static int lookup_table_length = STATIC_ARRAY_SIZE (lookup_table);
290
291 static llist_t *list = NULL;
292
293 #define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
294 static char *local_sockpath = NULL;
295
296 /* TODO: Do this before 4.4:
297  * - Update the collectd.conf(5) manpage.
298  *
299  * -octo
300  */
301
302 /* <https://doc.powerdns.com/md/recursor/stats/> */
303 static void submit (const char *plugin_instance, /* {{{ */
304     const char *pdns_type, const char *value_str)
305 {
306   value_list_t vl = VALUE_LIST_INIT;
307   value_t value;
308
309   const char *type = NULL;
310   const char *type_instance = NULL;
311   const data_set_t *ds;
312
313   int i;
314
315   for (i = 0; i < lookup_table_length; i++)
316     if (strcmp (lookup_table[i].name, pdns_type) == 0)
317       break;
318
319   if (i >= lookup_table_length)
320   {
321     INFO ("powerdns plugin: submit: Not found in lookup table: %s = %s;",
322         pdns_type, value_str);
323     return;
324   }
325
326   if (lookup_table[i].type == NULL)
327     return;
328
329   type = lookup_table[i].type;
330   type_instance = lookup_table[i].type_instance;
331
332   ds = plugin_get_ds (type);
333   if (ds == NULL)
334   {
335     ERROR ("powerdns plugin: The lookup table returned type `%s', "
336         "but I cannot find it via `plugin_get_ds'.",
337         type);
338     return;
339   }
340
341   if (ds->ds_num != 1)
342   {
343     ERROR ("powerdns plugin: type `%s' has %zu data sources, "
344         "but I can only handle one.",
345         type, ds->ds_num);
346     return;
347   }
348
349   if (0 != parse_value (value_str, &value, ds->ds[0].type))
350   {
351     ERROR ("powerdns plugin: Cannot convert `%s' "
352         "to a number.", value_str);
353     return;
354   }
355
356   vl.values = &value;
357   vl.values_len = 1;
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     SOCK_ERROR ("unlink", sa_unix.sun_path);
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       SOCK_ERROR ("bind", sa_unix.sun_path);
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       SOCK_ERROR ("chmod", sa_unix.sun_path);
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       SOCK_ERROR ("setsockopt", sa_unix.sun_path);
432       break;
433     }
434
435     status = connect (sd, (struct sockaddr *) &item->sockaddr,
436         sizeof (item->sockaddr));
437     if (status != 0)
438     {
439       SOCK_ERROR ("connect", sa_unix.sun_path);
440       break;
441     }
442
443     status = send (sd, item->command, strlen (item->command), 0);
444     if (status < 0)
445     {
446       SOCK_ERROR ("send", sa_unix.sun_path);
447       break;
448     }
449
450     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
451     if (status < 0)
452     {
453       SOCK_ERROR ("recv", sa_unix.sun_path);
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     SOCK_ERROR ("connect", item->sockaddr.sun_path);
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     SOCK_ERROR ("send", item->sockaddr.sun_path);
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       SOCK_ERROR ("recv", item->sockaddr.sun_path);
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 : */