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