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