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