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