Merge branch 'collectd-5.5'
[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;
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   memset (&sa_unix, 0, sizeof (sa_unix));
391   sa_unix.sun_family = AF_UNIX;
392   sstrncpy (sa_unix.sun_path,
393       (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
394       sizeof (sa_unix.sun_path));
395
396   status = unlink (sa_unix.sun_path);
397   if ((status != 0) && (errno != ENOENT))
398   {
399     FUNC_ERROR ("unlink");
400     close (sd);
401     return (-1);
402   }
403
404   do /* while (0) */
405   {
406     /* We need to bind to a specific path, because this is a datagram socket
407      * and otherwise the daemon cannot answer. */
408     status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
409     if (status != 0)
410     {
411       FUNC_ERROR ("bind");
412       break;
413     }
414
415     /* Make the socket writeable by the daemon.. */
416     status = chmod (sa_unix.sun_path, 0666);
417     if (status != 0)
418     {
419       FUNC_ERROR ("chmod");
420       break;
421     }
422
423     cdt_timeout = plugin_get_interval () * 3 / 4;
424     if (cdt_timeout < TIME_T_TO_CDTIME_T (2))
425       cdt_timeout = TIME_T_TO_CDTIME_T (2);
426
427     CDTIME_T_TO_TIMEVAL (cdt_timeout, &stv_timeout);
428
429     status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &stv_timeout, sizeof (stv_timeout));
430     if (status != 0)
431     {
432       FUNC_ERROR ("setsockopt");
433       break;
434     }
435
436     status = connect (sd, (struct sockaddr *) &item->sockaddr,
437         sizeof (item->sockaddr));
438     if (status != 0)
439     {
440       FUNC_ERROR ("connect");
441       break;
442     }
443
444     status = send (sd, item->command, strlen (item->command), 0);
445     if (status < 0)
446     {
447       FUNC_ERROR ("send");
448       break;
449     }
450
451     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
452     if (status < 0)
453     {
454       FUNC_ERROR ("recv");
455       break;
456     }
457     buffer_size = status + 1;
458     status = 0;
459   } while (0);
460
461   close (sd);
462   unlink (sa_unix.sun_path);
463
464   if (status != 0)
465     return (-1);
466
467   assert (buffer_size > 0);
468   buffer = malloc (buffer_size);
469   if (buffer == NULL)
470   {
471     FUNC_ERROR ("malloc");
472     return (-1);
473   }
474
475   memcpy (buffer, temp, buffer_size - 1);
476   buffer[buffer_size - 1] = 0;
477
478   *ret_buffer = buffer;
479   *ret_buffer_size = buffer_size;
480
481   return (0);
482 } /* }}} int powerdns_get_data_dgram */
483
484 static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
485     char **ret_buffer,
486     size_t *ret_buffer_size)
487 {
488   int sd;
489   int status;
490
491   char temp[4096];
492   char *buffer = NULL;
493   size_t buffer_size = 0;
494
495   sd = socket (PF_UNIX, item->socktype, 0);
496   if (sd < 0)
497   {
498     FUNC_ERROR ("socket");
499     return (-1);
500   }
501
502   struct timeval timeout;
503   timeout.tv_sec=5;
504   timeout.tv_usec=0;
505   status = setsockopt (sd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof (timeout));
506   if (status != 0)
507   {
508     FUNC_ERROR ("setsockopt");
509     close (sd);
510     return (-1);
511   }
512
513   status = connect (sd, (struct sockaddr *) &item->sockaddr,
514       sizeof (item->sockaddr));
515   if (status != 0)
516   {
517     FUNC_ERROR ("connect");
518     close (sd);
519     return (-1);
520   }
521
522   /* strlen + 1, because we need to send the terminating NULL byte, too. */
523   status = send (sd, item->command, strlen (item->command) + 1,
524       /* flags = */ 0);
525   if (status < 0)
526   {
527     FUNC_ERROR ("send");
528     close (sd);
529     return (-1);
530   }
531
532   while (42)
533   {
534     char *buffer_new;
535
536     status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
537     if (status < 0)
538     {
539       FUNC_ERROR ("recv");
540       break;
541     }
542     else if (status == 0)
543       break;
544
545     buffer_new = realloc (buffer, buffer_size + status + 1);
546     if (buffer_new == NULL)
547     {
548       FUNC_ERROR ("realloc");
549       status = -1;
550       break;
551     }
552     buffer = buffer_new;
553
554     memcpy (buffer + buffer_size, temp, status);
555     buffer_size += status;
556     buffer[buffer_size] = 0;
557   } /* while (42) */
558   close (sd);
559
560   if (status < 0)
561   {
562     sfree (buffer);
563   }
564   else
565   {
566     assert (status == 0);
567     *ret_buffer = buffer;
568     *ret_buffer_size = buffer_size;
569   }
570
571   return (status);
572 } /* }}} int powerdns_get_data_stream */
573
574 static int powerdns_get_data (list_item_t *item, char **ret_buffer,
575     size_t *ret_buffer_size)
576 {
577   if (item->socktype == SOCK_DGRAM)
578     return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
579   else if (item->socktype == SOCK_STREAM)
580     return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
581   else
582   {
583     ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
584     return (-1);
585   }
586 } /* int powerdns_get_data */
587
588 static int powerdns_read_server (list_item_t *item) /* {{{ */
589 {
590   char *buffer = NULL;
591   size_t buffer_size = 0;
592   int status;
593
594   char *dummy;
595   char *saveptr;
596
597   char *key;
598   char *value;
599
600   const char* const *fields;
601   int fields_num;
602
603   if (item->command == NULL)
604     item->command = strdup (SERVER_COMMAND);
605   if (item->command == NULL)
606   {
607     ERROR ("powerdns plugin: strdup failed.");
608     return (-1);
609   }
610
611   status = powerdns_get_data (item, &buffer, &buffer_size);
612   if (status != 0)
613     return (-1);
614
615   if (item->fields_num != 0)
616   {
617     fields = (const char* const *) item->fields;
618     fields_num = item->fields_num;
619   }
620   else
621   {
622     fields = default_server_fields;
623     fields_num = default_server_fields_num;
624   }
625
626   assert (fields != NULL);
627   assert (fields_num > 0);
628
629   /* 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, */
630   dummy = buffer;
631   saveptr = NULL;
632   while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
633   {
634     int i;
635
636     dummy = NULL;
637
638     value = strchr (key, '=');
639     if (value == NULL)
640       break;
641
642     *value = '\0';
643     value++;
644
645     if (value[0] == '\0')
646       continue;
647
648     /* Check if this item was requested. */
649     for (i = 0; i < fields_num; i++)
650       if (strcasecmp (key, fields[i]) == 0)
651         break;
652     if (i >= fields_num)
653       continue;
654
655     submit (item->instance, key, value);
656   } /* while (strtok_r) */
657
658   sfree (buffer);
659
660   return (0);
661 } /* }}} int powerdns_read_server */
662
663 /*
664  * powerdns_update_recursor_command
665  *
666  * Creates a string that holds the command to be sent to the recursor. This
667  * string is stores in the `command' member of the `list_item_t' passed to the
668  * function. This function is called by `powerdns_read_recursor'.
669  */
670 static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
671 {
672   char buffer[4096];
673   int status;
674
675   if (li == NULL)
676     return (0);
677
678   if (li->fields_num < 1)
679   {
680     sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
681   }
682   else
683   {
684     sstrncpy (buffer, "get ", sizeof (buffer));
685     status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
686         li->fields, li->fields_num,
687         /* seperator = */ " ");
688     if (status < 0)
689     {
690       ERROR ("powerdns plugin: strjoin failed.");
691       return (-1);
692     }
693     buffer[sizeof (buffer) - 1] = 0;
694     size_t len = strlen (buffer);
695     if (len < sizeof (buffer) - 2)
696     {
697       buffer[len++] = ' ';
698       buffer[len++] = '\n';
699       buffer[len++] = '\0';
700     }
701   }
702
703   buffer[sizeof (buffer) - 1] = 0;
704   li->command = strdup (buffer);
705   if (li->command == NULL)
706   {
707     ERROR ("powerdns plugin: strdup failed.");
708     return (-1);
709   }
710
711   return (0);
712 } /* }}} int powerdns_update_recursor_command */
713
714 static int powerdns_read_recursor (list_item_t *item) /* {{{ */
715 {
716   char *buffer = NULL;
717   size_t buffer_size = 0;
718   int status;
719
720   char *dummy;
721
722   char *keys_list;
723   char *key;
724   char *key_saveptr;
725   char *value;
726   char *value_saveptr;
727
728   if (item->command == NULL)
729   {
730     status = powerdns_update_recursor_command (item);
731     if (status != 0)
732     {
733       ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
734       return (-1);
735     }
736
737     DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
738         item->command);
739   }
740   assert (item->command != NULL);
741
742   status = powerdns_get_data (item, &buffer, &buffer_size);
743   if (status != 0)
744   {
745     ERROR ("powerdns plugin: powerdns_get_data failed.");
746     return (-1);
747   }
748
749   keys_list = strdup (item->command);
750   if (keys_list == NULL)
751   {
752     FUNC_ERROR ("strdup");
753     sfree (buffer);
754     return (-1);
755   }
756
757   key_saveptr = NULL;
758   value_saveptr = NULL;
759
760   /* Skip the `get' at the beginning */
761   strtok_r (keys_list, " \t", &key_saveptr);
762
763   dummy = buffer;
764   while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
765   {
766     dummy = NULL;
767
768     key = strtok_r (NULL, " \t", &key_saveptr);
769     if (key == NULL)
770       break;
771
772     submit (item->instance, key, value);
773   } /* while (strtok_r) */
774
775   sfree (buffer);
776   sfree (keys_list);
777
778   return (0);
779 } /* }}} int powerdns_read_recursor */
780
781 static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
782     oconfig_item_t *ci)
783 {
784   int i;
785   char **temp;
786
787   if (ci->values_num < 1)
788   {
789     WARNING ("powerdns plugin: The `Collect' option needs "
790         "at least one argument.");
791     return (-1);
792   }
793
794   for (i = 0; i < ci->values_num; i++)
795     if (ci->values[i].type != OCONFIG_TYPE_STRING)
796     {
797       WARNING ("powerdns plugin: Only string arguments are allowed to "
798           "the `Collect' option.");
799       return (-1);
800     }
801
802   temp = realloc (li->fields,
803       sizeof (char *) * (li->fields_num + ci->values_num));
804   if (temp == NULL)
805   {
806     WARNING ("powerdns plugin: realloc failed.");
807     return (-1);
808   }
809   li->fields = temp;
810
811   for (i = 0; i < ci->values_num; i++)
812   {
813     li->fields[li->fields_num] = strdup (ci->values[i].value.string);
814     if (li->fields[li->fields_num] == NULL)
815     {
816       WARNING ("powerdns plugin: strdup failed.");
817       continue;
818     }
819     li->fields_num++;
820   }
821
822   /* Invalidate a previously computed command */
823   sfree (li->command);
824
825   return (0);
826 } /* }}} int powerdns_config_add_collect */
827
828 static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
829 {
830   char *socket_temp;
831
832   list_item_t *item;
833   int status;
834   int i;
835
836   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
837   {
838     WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
839         ci->key);
840     return (-1);
841   }
842
843   item = calloc (1, sizeof (*item));
844   if (item == NULL)
845   {
846     ERROR ("powerdns plugin: calloc failed.");
847     return (-1);
848   }
849
850   item->instance = strdup (ci->values[0].value.string);
851   if (item->instance == NULL)
852   {
853     ERROR ("powerdns plugin: strdup failed.");
854     sfree (item);
855     return (-1);
856   }
857
858   /*
859    * Set default values for the members of list_item_t
860    */
861   if (strcasecmp ("Server", ci->key) == 0)
862   {
863     item->server_type = SRV_AUTHORITATIVE;
864     item->func = powerdns_read_server;
865     item->socktype = SOCK_STREAM;
866     socket_temp = strdup (SERVER_SOCKET);
867   }
868   else if (strcasecmp ("Recursor", ci->key) == 0)
869   {
870     item->server_type = SRV_RECURSOR;
871     item->func = powerdns_read_recursor;
872     item->socktype = SOCK_DGRAM;
873     socket_temp = strdup (RECURSOR_SOCKET);
874   }
875   else
876   {
877     /* We must never get here.. */
878     assert (0);
879     return (-1);
880   }
881
882   status = 0;
883   for (i = 0; i < ci->children_num; i++)
884   {
885     oconfig_item_t *option = ci->children + i;
886
887     if (strcasecmp ("Collect", option->key) == 0)
888       status = powerdns_config_add_collect (item, option);
889     else if (strcasecmp ("Socket", option->key) == 0)
890       status = cf_util_get_string (option, &socket_temp);
891     else
892     {
893       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
894       status = -1;
895     }
896
897     if (status != 0)
898       break;
899   }
900
901   while (status == 0)
902   {
903     llentry_t *e;
904
905     if (socket_temp == NULL)
906     {
907       ERROR ("powerdns plugin: socket_temp == NULL.");
908       status = -1;
909       break;
910     }
911
912     item->sockaddr.sun_family = AF_UNIX;
913     sstrncpy (item->sockaddr.sun_path, socket_temp,
914       sizeof (item->sockaddr.sun_path));
915
916     e = llentry_create (item->instance, item);
917     if (e == NULL)
918     {
919       ERROR ("powerdns plugin: llentry_create failed.");
920       status = -1;
921       break;
922     }
923     llist_append (list, e);
924
925     break;
926   }
927
928   if (status != 0)
929   {
930     sfree (socket_temp);
931     sfree (item);
932     return (-1);
933   }
934
935   DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
936
937   sfree (socket_temp);
938   return (0);
939 } /* }}} int powerdns_config_add_server */
940
941 static int powerdns_config (oconfig_item_t *ci) /* {{{ */
942 {
943   int i;
944
945   DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
946
947   if (list == NULL)
948   {
949     list = llist_create ();
950
951     if (list == NULL)
952     {
953       ERROR ("powerdns plugin: `llist_create' failed.");
954       return (-1);
955     }
956   }
957
958   for (i = 0; i < ci->children_num; i++)
959   {
960     oconfig_item_t *option = ci->children + i;
961
962     if ((strcasecmp ("Server", option->key) == 0)
963         || (strcasecmp ("Recursor", option->key) == 0))
964       powerdns_config_add_server (option);
965     else if (strcasecmp ("LocalSocket", option->key) == 0)
966     {
967       if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
968       {
969         WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
970       }
971       else
972       {
973         char *temp = strdup (option->values[0].value.string);
974         if (temp == NULL)
975           return (1);
976         sfree (local_sockpath);
977         local_sockpath = temp;
978       }
979     }
980     else
981     {
982       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
983     }
984   } /* for (i = 0; i < ci->children_num; i++) */
985
986   return (0);
987 } /* }}} int powerdns_config */
988
989 static int powerdns_read (void)
990 {
991   llentry_t *e;
992
993   for (e = llist_head (list); e != NULL; e = e->next)
994   {
995     list_item_t *item = e->value;
996     item->func (item);
997   }
998
999   return (0);
1000 } /* static int powerdns_read */
1001
1002 static int powerdns_shutdown (void)
1003 {
1004   llentry_t *e;
1005
1006   if (list == NULL)
1007     return (0);
1008
1009   for (e = llist_head (list); e != NULL; e = e->next)
1010   {
1011     list_item_t *item = (list_item_t *) e->value;
1012     e->value = NULL;
1013
1014     sfree (item->instance);
1015     sfree (item->command);
1016     sfree (item);
1017   }
1018
1019   llist_destroy (list);
1020   list = NULL;
1021
1022   return (0);
1023 } /* static int powerdns_shutdown */
1024
1025 void module_register (void)
1026 {
1027   plugin_register_complex_config ("powerdns", powerdns_config);
1028   plugin_register_read ("powerdns", powerdns_read);
1029   plugin_register_shutdown ("powerdns", powerdns_shutdown );
1030 } /* void module_register */
1031
1032 /* vim: set sw=2 sts=2 ts=8 fdm=marker : */