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/> */
submit(const char * plugin_instance,const char * pdns_type,const char * value_str)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 
powerdns_get_data_dgram(list_item_t * item,char ** ret_buffer)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 
powerdns_get_data_stream(list_item_t * item,char ** ret_buffer)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 
powerdns_get_data(list_item_t * item,char ** ret_buffer)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 
powerdns_read_server(list_item_t * item)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  */
powerdns_update_recursor_command(list_item_t * li)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 
powerdns_read_recursor(list_item_t * item)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 
powerdns_config_add_collect(list_item_t * li,oconfig_item_t * ci)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 
powerdns_config_add_server(oconfig_item_t * ci)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 
powerdns_config(oconfig_item_t * ci)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 
powerdns_read(void)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 
powerdns_shutdown(void)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 
module_register(void)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 */
945