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, /* {{{ */
380                                    char **ret_buffer, size_t *ret_buffer_size) {
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   *ret_buffer_size = buffer_size;
479
480   return 0;
481 } /* }}} int powerdns_get_data_dgram */
482
483 static int powerdns_get_data_stream(list_item_t *item, /* {{{ */
484                                     char **ret_buffer,
485                                     size_t *ret_buffer_size) {
486   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     buffer_new = realloc(buffer, buffer_size + status + 1);
537     if (buffer_new == NULL) {
538       FUNC_ERROR("realloc");
539       status = -1;
540       break;
541     }
542     buffer = buffer_new;
543
544     memcpy(buffer + buffer_size, temp, status);
545     buffer_size += status;
546     buffer[buffer_size] = 0;
547   } /* while (42) */
548   close(sd);
549
550   if (status < 0) {
551     sfree(buffer);
552   } else {
553     assert(status == 0);
554     *ret_buffer = buffer;
555     *ret_buffer_size = buffer_size;
556   }
557
558   return status;
559 } /* }}} int powerdns_get_data_stream */
560
561 static int powerdns_get_data(list_item_t *item, char **ret_buffer,
562                              size_t *ret_buffer_size) {
563   if (item->socktype == SOCK_DGRAM)
564     return powerdns_get_data_dgram(item, ret_buffer, ret_buffer_size);
565   else if (item->socktype == SOCK_STREAM)
566     return powerdns_get_data_stream(item, ret_buffer, ret_buffer_size);
567   else {
568     ERROR("powerdns plugin: Unknown socket type: %i", (int)item->socktype);
569     return -1;
570   }
571 } /* int powerdns_get_data */
572
573 static int powerdns_read_server(list_item_t *item) /* {{{ */
574 {
575   char *buffer = NULL;
576   size_t buffer_size = 0;
577   int status;
578
579   char *dummy;
580   char *saveptr;
581
582   char *key;
583   char *value;
584
585   const char *const *fields;
586   int fields_num;
587
588   if (item->command == NULL)
589     item->command = strdup(SERVER_COMMAND);
590   if (item->command == NULL) {
591     ERROR("powerdns plugin: strdup failed.");
592     return -1;
593   }
594
595   status = powerdns_get_data(item, &buffer, &buffer_size);
596   if (status != 0)
597     return -1;
598
599   if (item->fields_num != 0) {
600     fields = (const char *const *)item->fields;
601     fields_num = item->fields_num;
602   } else {
603     fields = default_server_fields;
604     fields_num = default_server_fields_num;
605   }
606
607   assert(fields != NULL);
608   assert(fields_num > 0);
609
610   /* 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,
611    */
612   dummy = buffer;
613   saveptr = NULL;
614   while ((key = strtok_r(dummy, ",", &saveptr)) != NULL) {
615     dummy = NULL;
616
617     value = strchr(key, '=');
618     if (value == NULL)
619       break;
620
621     *value = '\0';
622     value++;
623
624     if (value[0] == '\0')
625       continue;
626
627     /* Check if this item was requested. */
628     int i;
629     for (i = 0; i < fields_num; i++)
630       if (strcasecmp(key, fields[i]) == 0)
631         break;
632     if (i >= fields_num)
633       continue;
634
635     submit(item->instance, key, value);
636   } /* while (strtok_r) */
637
638   sfree(buffer);
639
640   return 0;
641 } /* }}} int powerdns_read_server */
642
643 /*
644  * powerdns_update_recursor_command
645  *
646  * Creates a string that holds the command to be sent to the recursor. This
647  * string is stores in the `command' member of the `list_item_t' passed to the
648  * function. This function is called by `powerdns_read_recursor'.
649  */
650 static int powerdns_update_recursor_command(list_item_t *li) /* {{{ */
651 {
652   char buffer[4096];
653   int status;
654
655   if (li == NULL)
656     return 0;
657
658   if (li->fields_num < 1) {
659     sstrncpy(buffer, RECURSOR_COMMAND, sizeof(buffer));
660   } else {
661     sstrncpy(buffer, "get ", sizeof(buffer));
662     status = strjoin(&buffer[strlen("get ")], sizeof(buffer) - strlen("get "),
663                      li->fields, li->fields_num,
664                      /* seperator = */ " ");
665     if (status < 0) {
666       ERROR("powerdns plugin: strjoin failed.");
667       return -1;
668     }
669     buffer[sizeof(buffer) - 1] = 0;
670     size_t len = strlen(buffer);
671     if (len < sizeof(buffer) - 2) {
672       buffer[len++] = ' ';
673       buffer[len++] = '\n';
674       buffer[len++] = '\0';
675     }
676   }
677
678   buffer[sizeof(buffer) - 1] = 0;
679   li->command = strdup(buffer);
680   if (li->command == NULL) {
681     ERROR("powerdns plugin: strdup failed.");
682     return -1;
683   }
684
685   return 0;
686 } /* }}} int powerdns_update_recursor_command */
687
688 static int powerdns_read_recursor(list_item_t *item) /* {{{ */
689 {
690   char *buffer = NULL;
691   size_t buffer_size = 0;
692   int status;
693
694   char *dummy;
695
696   char *keys_list;
697   char *key;
698   char *key_saveptr;
699   char *value;
700   char *value_saveptr;
701
702   if (item->command == NULL) {
703     status = powerdns_update_recursor_command(item);
704     if (status != 0) {
705       ERROR("powerdns plugin: powerdns_update_recursor_command failed.");
706       return -1;
707     }
708
709     DEBUG("powerdns plugin: powerdns_read_recursor: item->command = %s;",
710           item->command);
711   }
712   assert(item->command != NULL);
713
714   status = powerdns_get_data(item, &buffer, &buffer_size);
715   if (status != 0) {
716     ERROR("powerdns plugin: powerdns_get_data failed.");
717     return -1;
718   }
719
720   keys_list = strdup(item->command);
721   if (keys_list == NULL) {
722     FUNC_ERROR("strdup");
723     sfree(buffer);
724     return -1;
725   }
726
727   key_saveptr = NULL;
728   value_saveptr = NULL;
729
730   /* Skip the `get' at the beginning */
731   strtok_r(keys_list, " \t", &key_saveptr);
732
733   dummy = buffer;
734   while ((value = strtok_r(dummy, " \t\n\r", &value_saveptr)) != NULL) {
735     dummy = NULL;
736
737     key = strtok_r(NULL, " \t", &key_saveptr);
738     if (key == NULL)
739       break;
740
741     submit(item->instance, key, value);
742   } /* while (strtok_r) */
743
744   sfree(buffer);
745   sfree(keys_list);
746
747   return 0;
748 } /* }}} int powerdns_read_recursor */
749
750 static int powerdns_config_add_collect(list_item_t *li, /* {{{ */
751                                        oconfig_item_t *ci) {
752   char **temp;
753
754   if (ci->values_num < 1) {
755     WARNING("powerdns plugin: The `Collect' option needs "
756             "at least one argument.");
757     return -1;
758   }
759
760   for (int i = 0; i < ci->values_num; i++)
761     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
762       WARNING("powerdns plugin: Only string arguments are allowed to "
763               "the `Collect' option.");
764       return -1;
765     }
766
767   temp =
768       realloc(li->fields, sizeof(char *) * (li->fields_num + ci->values_num));
769   if (temp == NULL) {
770     WARNING("powerdns plugin: realloc failed.");
771     return -1;
772   }
773   li->fields = temp;
774
775   for (int i = 0; i < ci->values_num; i++) {
776     li->fields[li->fields_num] = strdup(ci->values[i].value.string);
777     if (li->fields[li->fields_num] == NULL) {
778       WARNING("powerdns plugin: strdup failed.");
779       continue;
780     }
781     li->fields_num++;
782   }
783
784   /* Invalidate a previously computed command */
785   sfree(li->command);
786
787   return 0;
788 } /* }}} int powerdns_config_add_collect */
789
790 static int powerdns_config_add_server(oconfig_item_t *ci) /* {{{ */
791 {
792   char *socket_temp;
793
794   list_item_t *item;
795   int status;
796
797   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
798     WARNING("powerdns plugin: `%s' needs exactly one string argument.",
799             ci->key);
800     return -1;
801   }
802
803   item = calloc(1, sizeof(*item));
804   if (item == NULL) {
805     ERROR("powerdns plugin: calloc failed.");
806     return -1;
807   }
808
809   item->instance = strdup(ci->values[0].value.string);
810   if (item->instance == NULL) {
811     ERROR("powerdns plugin: strdup failed.");
812     sfree(item);
813     return -1;
814   }
815
816   /*
817    * Set default values for the members of list_item_t
818    */
819   if (strcasecmp("Server", ci->key) == 0) {
820     item->server_type = SRV_AUTHORITATIVE;
821     item->func = powerdns_read_server;
822     item->socktype = SOCK_STREAM;
823     socket_temp = strdup(SERVER_SOCKET);
824   } else if (strcasecmp("Recursor", ci->key) == 0) {
825     item->server_type = SRV_RECURSOR;
826     item->func = powerdns_read_recursor;
827     item->socktype = SOCK_DGRAM;
828     socket_temp = strdup(RECURSOR_SOCKET);
829   } else {
830     /* We must never get here.. */
831     assert(0);
832     return -1;
833   }
834
835   status = 0;
836   for (int i = 0; i < ci->children_num; i++) {
837     oconfig_item_t *option = ci->children + i;
838
839     if (strcasecmp("Collect", option->key) == 0)
840       status = powerdns_config_add_collect(item, option);
841     else if (strcasecmp("Socket", option->key) == 0)
842       status = cf_util_get_string(option, &socket_temp);
843     else {
844       ERROR("powerdns plugin: Option `%s' not allowed here.", option->key);
845       status = -1;
846     }
847
848     if (status != 0)
849       break;
850   }
851
852   while (status == 0) {
853     llentry_t *e;
854
855     if (socket_temp == NULL) {
856       ERROR("powerdns plugin: socket_temp == NULL.");
857       status = -1;
858       break;
859     }
860
861     item->sockaddr.sun_family = AF_UNIX;
862     sstrncpy(item->sockaddr.sun_path, socket_temp,
863              sizeof(item->sockaddr.sun_path));
864
865     e = llentry_create(item->instance, item);
866     if (e == NULL) {
867       ERROR("powerdns plugin: llentry_create failed.");
868       status = -1;
869       break;
870     }
871     llist_append(list, e);
872
873     break;
874   }
875
876   if (status != 0) {
877     sfree(socket_temp);
878     sfree(item);
879     return -1;
880   }
881
882   DEBUG("powerdns plugin: Add server: instance = %s;", item->instance);
883
884   sfree(socket_temp);
885   return 0;
886 } /* }}} int powerdns_config_add_server */
887
888 static int powerdns_config(oconfig_item_t *ci) /* {{{ */
889 {
890   DEBUG("powerdns plugin: powerdns_config (ci = %p);", (void *)ci);
891
892   if (list == NULL) {
893     list = llist_create();
894
895     if (list == NULL) {
896       ERROR("powerdns plugin: `llist_create' failed.");
897       return -1;
898     }
899   }
900
901   for (int i = 0; i < ci->children_num; i++) {
902     oconfig_item_t *option = ci->children + i;
903
904     if ((strcasecmp("Server", option->key) == 0) ||
905         (strcasecmp("Recursor", option->key) == 0))
906       powerdns_config_add_server(option);
907     else if (strcasecmp("LocalSocket", option->key) == 0) {
908       if ((option->values_num != 1) ||
909           (option->values[0].type != OCONFIG_TYPE_STRING)) {
910         WARNING("powerdns plugin: `%s' needs exactly one string argument.",
911                 option->key);
912       } else {
913         char *temp = strdup(option->values[0].value.string);
914         if (temp == NULL)
915           return 1;
916         sfree(local_sockpath);
917         local_sockpath = temp;
918       }
919     } else {
920       ERROR("powerdns plugin: Option `%s' not allowed here.", option->key);
921     }
922   } /* for (i = 0; i < ci->children_num; i++) */
923
924   return 0;
925 } /* }}} int powerdns_config */
926
927 static int powerdns_read(void) {
928   for (llentry_t *e = llist_head(list); e != NULL; e = e->next) {
929     list_item_t *item = e->value;
930     item->func(item);
931   }
932
933   return 0;
934 } /* static int powerdns_read */
935
936 static int powerdns_shutdown(void) {
937   if (list == NULL)
938     return 0;
939
940   for (llentry_t *e = llist_head(list); e != NULL; e = e->next) {
941     list_item_t *item = (list_item_t *)e->value;
942     e->value = NULL;
943
944     sfree(item->instance);
945     sfree(item->command);
946     sfree(item);
947   }
948
949   llist_destroy(list);
950   list = NULL;
951
952   return 0;
953 } /* static int powerdns_shutdown */
954
955 void module_register(void) {
956   plugin_register_complex_config("powerdns", powerdns_config);
957   plugin_register_read("powerdns", powerdns_read);
958   plugin_register_shutdown("powerdns", powerdns_shutdown);
959 } /* void module_register */