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