Merge branch 'collectd-5.5'
[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 "configfile.h"
32 #include "utils_llist.h"
33
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/un.h>
42
43 #ifndef UNIX_PATH_MAX
44 # define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
45 #endif
46 #define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", 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)
305 {
306   value_list_t vl = VALUE_LIST_INIT;
307   value_t values[1];
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);
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, &values[0], ds->ds[0].type))
350   {
351     ERROR ("powerdns plugin: Cannot convert `%s' "
352         "to a number.", value);
353     return;
354   }
355
356   vl.values = values;
357   vl.values_len = 1;
358   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
359   sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
360   sstrncpy (vl.type, type, sizeof (vl.type));
361   if (type_instance != NULL)
362     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
363   sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
364
365   plugin_dispatch_values (&vl);
366 } /* }}} static void submit */
367
368 static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
369     char **ret_buffer,
370     size_t *ret_buffer_size)
371 {
372   int sd;
373   int status;
374
375   char temp[4096];
376   char *buffer = NULL;
377   size_t buffer_size = 0;
378
379   struct sockaddr_un sa_unix = { 0 };
380
381   struct timeval stv_timeout;
382   cdtime_t cdt_timeout;
383
384   sd = socket (PF_UNIX, item->socktype, 0);
385   if (sd < 0)
386   {
387     FUNC_ERROR ("socket");
388     return (-1);
389   }
390
391   sa_unix.sun_family = AF_UNIX;
392   sstrncpy (sa_unix.sun_path,
393       (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
394       sizeof (sa_unix.sun_path));
395
396   status = unlink (sa_unix.sun_path);
397   if ((status != 0) && (errno != ENOENT))
398   {
399     FUNC_ERROR ("unlink");
400     close (sd);
401     return (-1);
402   }
403
404   do /* while (0) */
405   {
406     /* We need to bind to a specific path, because this is a datagram socket
407      * and otherwise the daemon cannot answer. */
408     status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
409     if (status != 0)
410     {
411       FUNC_ERROR ("bind");
412       break;
413     }
414
415     /* Make the socket writeable by the daemon.. */
416     status = chmod (sa_unix.sun_path, 0666);
417     if (status != 0)
418     {
419       FUNC_ERROR ("chmod");
420       break;
421     }
422
423     cdt_timeout = plugin_get_interval () * 3 / 4;
424     if (cdt_timeout < TIME_T_TO_CDTIME_T (2))
425       cdt_timeout = TIME_T_TO_CDTIME_T (2);
426
427     CDTIME_T_TO_TIMEVAL (cdt_timeout, &stv_timeout);
428
429     status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &stv_timeout, sizeof (stv_timeout));
430     if (status != 0)
431     {
432       FUNC_ERROR ("setsockopt");
433       break;
434     }
435
436     status = connect (sd, (struct sockaddr *) &item->sockaddr,
437         sizeof (item->sockaddr));
438     if (status != 0)
439     {
440       FUNC_ERROR ("connect");
441       break;
442     }
443
444     status = send (sd, item->command, strlen (item->command), 0);
445     if (status < 0)
446     {
447       FUNC_ERROR ("send");
448       break;
449     }
450
451     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
452     if (status < 0)
453     {
454       FUNC_ERROR ("recv");
455       break;
456     }
457     buffer_size = status + 1;
458     status = 0;
459   } while (0);
460
461   close (sd);
462   unlink (sa_unix.sun_path);
463
464   if (status != 0)
465     return (-1);
466
467   assert (buffer_size > 0);
468   buffer = malloc (buffer_size);
469   if (buffer == NULL)
470   {
471     FUNC_ERROR ("malloc");
472     return (-1);
473   }
474
475   memcpy (buffer, temp, buffer_size - 1);
476   buffer[buffer_size - 1] = 0;
477
478   *ret_buffer = buffer;
479   *ret_buffer_size = buffer_size;
480
481   return (0);
482 } /* }}} int powerdns_get_data_dgram */
483
484 static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
485     char **ret_buffer,
486     size_t *ret_buffer_size)
487 {
488   int sd;
489   int status;
490
491   char temp[4096];
492   char *buffer = NULL;
493   size_t buffer_size = 0;
494
495   sd = socket (PF_UNIX, item->socktype, 0);
496   if (sd < 0)
497   {
498     FUNC_ERROR ("socket");
499     return (-1);
500   }
501
502   struct timeval timeout;
503   timeout.tv_sec=5;
504   timeout.tv_usec=0;
505   status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));
506   if (status != 0)
507   {
508     FUNC_ERROR ("setsockopt");
509     close (sd);
510     return (-1);
511   }
512
513   status = connect (sd, (struct sockaddr *) &item->sockaddr,
514       sizeof (item->sockaddr));
515   if (status != 0)
516   {
517     FUNC_ERROR ("connect");
518     close (sd);
519     return (-1);
520   }
521
522   /* strlen + 1, because we need to send the terminating NULL byte, too. */
523   status = send (sd, item->command, strlen (item->command) + 1,
524       /* flags = */ 0);
525   if (status < 0)
526   {
527     FUNC_ERROR ("send");
528     close (sd);
529     return (-1);
530   }
531
532   while (42)
533   {
534     char *buffer_new;
535
536     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
537     if (status < 0)
538     {
539       FUNC_ERROR ("recv");
540       break;
541     }
542     else if (status == 0)
543       break;
544
545     buffer_new = realloc (buffer, buffer_size + status + 1);
546     if (buffer_new == NULL)
547     {
548       FUNC_ERROR ("realloc");
549       status = -1;
550       break;
551     }
552     buffer = buffer_new;
553
554     memcpy (buffer + buffer_size, temp, status);
555     buffer_size += status;
556     buffer[buffer_size] = 0;
557   } /* while (42) */
558   close (sd);
559
560   if (status < 0)
561   {
562     sfree (buffer);
563   }
564   else
565   {
566     assert (status == 0);
567     *ret_buffer = buffer;
568     *ret_buffer_size = buffer_size;
569   }
570
571   return (status);
572 } /* }}} int powerdns_get_data_stream */
573
574 static int powerdns_get_data (list_item_t *item, char **ret_buffer,
575     size_t *ret_buffer_size)
576 {
577   if (item->socktype == SOCK_DGRAM)
578     return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
579   else if (item->socktype == SOCK_STREAM)
580     return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
581   else
582   {
583     ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
584     return (-1);
585   }
586 } /* int powerdns_get_data */
587
588 static int powerdns_read_server (list_item_t *item) /* {{{ */
589 {
590   char *buffer = NULL;
591   size_t buffer_size = 0;
592   int status;
593
594   char *dummy;
595   char *saveptr;
596
597   char *key;
598   char *value;
599
600   const char* const *fields;
601   int fields_num;
602
603   if (item->command == NULL)
604     item->command = strdup (SERVER_COMMAND);
605   if (item->command == NULL)
606   {
607     ERROR ("powerdns plugin: strdup failed.");
608     return (-1);
609   }
610
611   status = powerdns_get_data (item, &buffer, &buffer_size);
612   if (status != 0)
613     return (-1);
614
615   if (item->fields_num != 0)
616   {
617     fields = (const char* const *) item->fields;
618     fields_num = item->fields_num;
619   }
620   else
621   {
622     fields = default_server_fields;
623     fields_num = default_server_fields_num;
624   }
625
626   assert (fields != NULL);
627   assert (fields_num > 0);
628
629   /* 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, */
630   dummy = buffer;
631   saveptr = NULL;
632   while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
633   {
634     dummy = NULL;
635
636     value = strchr (key, '=');
637     if (value == NULL)
638       break;
639
640     *value = '\0';
641     value++;
642
643     if (value[0] == '\0')
644       continue;
645
646     /* Check if this item was requested. */
647     int i;
648     for (i = 0; i < fields_num; i++)
649       if (strcasecmp (key, fields[i]) == 0)
650         break;
651     if (i >= fields_num)
652       continue;
653
654     submit (item->instance, key, value);
655   } /* while (strtok_r) */
656
657   sfree (buffer);
658
659   return (0);
660 } /* }}} int powerdns_read_server */
661
662 /*
663  * powerdns_update_recursor_command
664  *
665  * Creates a string that holds the command to be sent to the recursor. This
666  * string is stores in the `command' member of the `list_item_t' passed to the
667  * function. This function is called by `powerdns_read_recursor'.
668  */
669 static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
670 {
671   char buffer[4096];
672   int status;
673
674   if (li == NULL)
675     return (0);
676
677   if (li->fields_num < 1)
678   {
679     sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
680   }
681   else
682   {
683     sstrncpy (buffer, "get ", sizeof (buffer));
684     status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
685         li->fields, li->fields_num,
686         /* seperator = */ " ");
687     if (status < 0)
688     {
689       ERROR ("powerdns plugin: strjoin failed.");
690       return (-1);
691     }
692     buffer[sizeof (buffer) - 1] = 0;
693     size_t len = strlen (buffer);
694     if (len < sizeof (buffer) - 2)
695     {
696       buffer[len++] = ' ';
697       buffer[len++] = '\n';
698       buffer[len++] = '\0';
699     }
700   }
701
702   buffer[sizeof (buffer) - 1] = 0;
703   li->command = strdup (buffer);
704   if (li->command == NULL)
705   {
706     ERROR ("powerdns plugin: strdup failed.");
707     return (-1);
708   }
709
710   return (0);
711 } /* }}} int powerdns_update_recursor_command */
712
713 static int powerdns_read_recursor (list_item_t *item) /* {{{ */
714 {
715   char *buffer = NULL;
716   size_t buffer_size = 0;
717   int status;
718
719   char *dummy;
720
721   char *keys_list;
722   char *key;
723   char *key_saveptr;
724   char *value;
725   char *value_saveptr;
726
727   if (item->command == NULL)
728   {
729     status = powerdns_update_recursor_command (item);
730     if (status != 0)
731     {
732       ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
733       return (-1);
734     }
735
736     DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
737         item->command);
738   }
739   assert (item->command != NULL);
740
741   status = powerdns_get_data (item, &buffer, &buffer_size);
742   if (status != 0)
743   {
744     ERROR ("powerdns plugin: powerdns_get_data failed.");
745     return (-1);
746   }
747
748   keys_list = strdup (item->command);
749   if (keys_list == NULL)
750   {
751     FUNC_ERROR ("strdup");
752     sfree (buffer);
753     return (-1);
754   }
755
756   key_saveptr = NULL;
757   value_saveptr = NULL;
758
759   /* Skip the `get' at the beginning */
760   strtok_r (keys_list, " \t", &key_saveptr);
761
762   dummy = buffer;
763   while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
764   {
765     dummy = NULL;
766
767     key = strtok_r (NULL, " \t", &key_saveptr);
768     if (key == NULL)
769       break;
770
771     submit (item->instance, key, value);
772   } /* while (strtok_r) */
773
774   sfree (buffer);
775   sfree (keys_list);
776
777   return (0);
778 } /* }}} int powerdns_read_recursor */
779
780 static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
781     oconfig_item_t *ci)
782 {
783   char **temp;
784
785   if (ci->values_num < 1)
786   {
787     WARNING ("powerdns plugin: The `Collect' option needs "
788         "at least one argument.");
789     return (-1);
790   }
791
792   for (int i = 0; i < ci->values_num; i++)
793     if (ci->values[i].type != OCONFIG_TYPE_STRING)
794     {
795       WARNING ("powerdns plugin: Only string arguments are allowed to "
796           "the `Collect' option.");
797       return (-1);
798     }
799
800   temp = realloc (li->fields,
801       sizeof (char *) * (li->fields_num + ci->values_num));
802   if (temp == NULL)
803   {
804     WARNING ("powerdns plugin: realloc failed.");
805     return (-1);
806   }
807   li->fields = temp;
808
809   for (int i = 0; i < ci->values_num; i++)
810   {
811     li->fields[li->fields_num] = strdup (ci->values[i].value.string);
812     if (li->fields[li->fields_num] == NULL)
813     {
814       WARNING ("powerdns plugin: strdup failed.");
815       continue;
816     }
817     li->fields_num++;
818   }
819
820   /* Invalidate a previously computed command */
821   sfree (li->command);
822
823   return (0);
824 } /* }}} int powerdns_config_add_collect */
825
826 static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
827 {
828   char *socket_temp;
829
830   list_item_t *item;
831   int status;
832
833   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
834   {
835     WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
836         ci->key);
837     return (-1);
838   }
839
840   item = calloc (1, sizeof (*item));
841   if (item == NULL)
842   {
843     ERROR ("powerdns plugin: calloc failed.");
844     return (-1);
845   }
846
847   item->instance = strdup (ci->values[0].value.string);
848   if (item->instance == NULL)
849   {
850     ERROR ("powerdns plugin: strdup failed.");
851     sfree (item);
852     return (-1);
853   }
854
855   /*
856    * Set default values for the members of list_item_t
857    */
858   if (strcasecmp ("Server", ci->key) == 0)
859   {
860     item->server_type = SRV_AUTHORITATIVE;
861     item->func = powerdns_read_server;
862     item->socktype = SOCK_STREAM;
863     socket_temp = strdup (SERVER_SOCKET);
864   }
865   else if (strcasecmp ("Recursor", ci->key) == 0)
866   {
867     item->server_type = SRV_RECURSOR;
868     item->func = powerdns_read_recursor;
869     item->socktype = SOCK_DGRAM;
870     socket_temp = strdup (RECURSOR_SOCKET);
871   }
872   else
873   {
874     /* We must never get here.. */
875     assert (0);
876     return (-1);
877   }
878
879   status = 0;
880   for (int i = 0; i < ci->children_num; i++)
881   {
882     oconfig_item_t *option = ci->children + i;
883
884     if (strcasecmp ("Collect", option->key) == 0)
885       status = powerdns_config_add_collect (item, option);
886     else if (strcasecmp ("Socket", option->key) == 0)
887       status = cf_util_get_string (option, &socket_temp);
888     else
889     {
890       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
891       status = -1;
892     }
893
894     if (status != 0)
895       break;
896   }
897
898   while (status == 0)
899   {
900     llentry_t *e;
901
902     if (socket_temp == NULL)
903     {
904       ERROR ("powerdns plugin: socket_temp == NULL.");
905       status = -1;
906       break;
907     }
908
909     item->sockaddr.sun_family = AF_UNIX;
910     sstrncpy (item->sockaddr.sun_path, socket_temp,
911       sizeof (item->sockaddr.sun_path));
912
913     e = llentry_create (item->instance, item);
914     if (e == NULL)
915     {
916       ERROR ("powerdns plugin: llentry_create failed.");
917       status = -1;
918       break;
919     }
920     llist_append (list, e);
921
922     break;
923   }
924
925   if (status != 0)
926   {
927     sfree (socket_temp);
928     sfree (item);
929     return (-1);
930   }
931
932   DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
933
934   sfree (socket_temp);
935   return (0);
936 } /* }}} int powerdns_config_add_server */
937
938 static int powerdns_config (oconfig_item_t *ci) /* {{{ */
939 {
940   DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
941
942   if (list == NULL)
943   {
944     list = llist_create ();
945
946     if (list == NULL)
947     {
948       ERROR ("powerdns plugin: `llist_create' failed.");
949       return (-1);
950     }
951   }
952
953   for (int i = 0; i < ci->children_num; i++)
954   {
955     oconfig_item_t *option = ci->children + i;
956
957     if ((strcasecmp ("Server", option->key) == 0)
958         || (strcasecmp ("Recursor", option->key) == 0))
959       powerdns_config_add_server (option);
960     else if (strcasecmp ("LocalSocket", option->key) == 0)
961     {
962       if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
963       {
964         WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
965       }
966       else
967       {
968         char *temp = strdup (option->values[0].value.string);
969         if (temp == NULL)
970           return (1);
971         sfree (local_sockpath);
972         local_sockpath = temp;
973       }
974     }
975     else
976     {
977       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
978     }
979   } /* for (i = 0; i < ci->children_num; i++) */
980
981   return (0);
982 } /* }}} int powerdns_config */
983
984 static int powerdns_read (void)
985 {
986   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
987   {
988     list_item_t *item = e->value;
989     item->func (item);
990   }
991
992   return (0);
993 } /* static int powerdns_read */
994
995 static int powerdns_shutdown (void)
996 {
997   if (list == NULL)
998     return (0);
999
1000   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
1001   {
1002     list_item_t *item = (list_item_t *) e->value;
1003     e->value = NULL;
1004
1005     sfree (item->instance);
1006     sfree (item->command);
1007     sfree (item);
1008   }
1009
1010   llist_destroy (list);
1011   list = NULL;
1012
1013   return (0);
1014 } /* static int powerdns_shutdown */
1015
1016 void module_register (void)
1017 {
1018   plugin_register_complex_config ("powerdns", powerdns_config);
1019   plugin_register_read ("powerdns", powerdns_read);
1020   plugin_register_shutdown ("powerdns", powerdns_shutdown );
1021 } /* void module_register */
1022
1023 /* vim: set sw=2 sts=2 ts=8 fdm=marker : */