write_http plugin: Be extra conservative in wh_reset_buffer().
[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 "configfile.h"
32 #include "utils_llist.h"
33
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/un.h>
42
43 #ifndef UNIX_PATH_MAX
44 # define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
45 #endif
46 #define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", 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)
305 {
306   value_list_t vl = VALUE_LIST_INIT;
307   value_t values[1];
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);
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, &values[0], ds->ds[0].type))
350   {
351     ERROR ("powerdns plugin: Cannot convert `%s' "
352         "to a number.", value);
353     return;
354   }
355
356   vl.values = values;
357   vl.values_len = 1;
358   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
359   sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
360   sstrncpy (vl.type, type, sizeof (vl.type));
361   if (type_instance != NULL)
362     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
363   sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
364
365   plugin_dispatch_values (&vl);
366 } /* }}} static void submit */
367
368 static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
369     char **ret_buffer,
370     size_t *ret_buffer_size)
371 {
372   int sd;
373   int status;
374
375   char temp[4096];
376   char *buffer = NULL;
377   size_t buffer_size = 0;
378
379   struct sockaddr_un sa_unix = { 0 };
380
381   struct timeval stv_timeout;
382   cdtime_t cdt_timeout;
383
384   sd = socket (PF_UNIX, item->socktype, 0);
385   if (sd < 0)
386   {
387     FUNC_ERROR ("socket");
388     return (-1);
389   }
390
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 : */