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