Code

c392f0f64e128970f5f52ed9977c309f1a9054d3
[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  **/
27 #include "collectd.h"
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_llist.h"
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>
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)
47 #define SERVER_SOCKET  LOCALSTATEDIR"/run/pdns.controlsocket"
48 #define SERVER_COMMAND "SHOW * \n"
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"
55 struct list_item_s;
56 typedef struct list_item_s list_item_t;
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;
68   char **fields;
69   int fields_num;
70   char *command;
72   struct sockaddr_un sockaddr;
73   int socktype;
74 };
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;
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 }}} */
146 static const char* const default_server_fields[] = /* {{{ */
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);
163 static statname_lookup_t lookup_table[] = /* {{{ */
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"},
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"},
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"},
196   /* Latency */
197   {"latency",                "latency",      NULL},
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"},
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"},
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"},
230   /* CPU utilization */
231   {"sys-msec",             "cpu",          "system"},
232   {"user-msec",            "cpu",          "user"},
234   /* Question-to-answer latency */
235   {"qa-latency",           "latency",      NULL},
237   /* Cache */
238   {"cache-entries",        "cache_size",   NULL},
239   {"cache-hits",           "cache_result", "hit"},
240   {"cache-misses",         "cache_result", "miss"},
242   /* Total number of questions.. */
243   {"questions",            "dns_qtype",    "total"},
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);
290 static llist_t *list = NULL;
292 #define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
293 static char *local_sockpath = NULL;
295 /* TODO: Do this before 4.4:
296  * - Update the collectd.conf(5) manpage.
297  *
298  * -octo
299  */
301 /* <https://doc.powerdns.com/md/recursor/stats/> */
302 static void submit (const char *plugin_instance, /* {{{ */
303     const char *pdns_type, const char *value)
305   value_list_t vl = VALUE_LIST_INIT;
306   value_t values[1];
308   const char *type = NULL;
309   const char *type_instance = NULL;
310   const data_set_t *ds;
312   int i;
314   for (i = 0; i < lookup_table_length; i++)
315     if (strcmp (lookup_table[i].name, pdns_type) == 0)
316       break;
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   }
325   if (lookup_table[i].type == NULL)
326     return;
328   type = lookup_table[i].type;
329   type_instance = lookup_table[i].type_instance;
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   }
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   }
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   }
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));
364   plugin_dispatch_values (&vl);
365 } /* }}} static void submit */
367 static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
368     char **ret_buffer,
369     size_t *ret_buffer_size)
371   int sd;
372   int status;
374   char temp[4096];
375   char *buffer = NULL;
376   size_t buffer_size = 0;
378   struct sockaddr_un sa_unix = { 0 };
380   struct timeval stv_timeout;
381   cdtime_t cdt_timeout;
383   sd = socket (PF_UNIX, item->socktype, 0);
384   if (sd < 0)
385   {
386     FUNC_ERROR ("socket");
387     return (-1);
388   }
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));
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   }
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     }
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     }
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);
426     CDTIME_T_TO_TIMEVAL (cdt_timeout, &stv_timeout);
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     }
435     status = connect (sd, (struct sockaddr *) &item->sockaddr,
436         sizeof (item->sockaddr));
437     if (status != 0)
438     {
439       FUNC_ERROR ("connect");
440       break;
441     }
443     status = send (sd, item->command, strlen (item->command), 0);
444     if (status < 0)
445     {
446       FUNC_ERROR ("send");
447       break;
448     }
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);
460   close (sd);
461   unlink (sa_unix.sun_path);
463   if (status != 0)
464     return (-1);
466   assert (buffer_size > 0);
467   buffer = malloc (buffer_size);
468   if (buffer == NULL)
469   {
470     FUNC_ERROR ("malloc");
471     return (-1);
472   }
474   memcpy (buffer, temp, buffer_size - 1);
475   buffer[buffer_size - 1] = 0;
477   *ret_buffer = buffer;
478   *ret_buffer_size = buffer_size;
480   return (0);
481 } /* }}} int powerdns_get_data_dgram */
483 static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
484     char **ret_buffer,
485     size_t *ret_buffer_size)
487   int sd;
488   int status;
490   char temp[4096];
491   char *buffer = NULL;
492   size_t buffer_size = 0;
494   sd = socket (PF_UNIX, item->socktype, 0);
495   if (sd < 0)
496   {
497     FUNC_ERROR ("socket");
498     return (-1);
499   }
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   }
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   }
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   }
531   while (42)
532   {
533     char *buffer_new;
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;
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;
553     memcpy (buffer + buffer_size, temp, status);
554     buffer_size += status;
555     buffer[buffer_size] = 0;
556   } /* while (42) */
557   close (sd);
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   }
570   return (status);
571 } /* }}} int powerdns_get_data_stream */
573 static int powerdns_get_data (list_item_t *item, char **ret_buffer,
574     size_t *ret_buffer_size)
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 */
587 static int powerdns_read_server (list_item_t *item) /* {{{ */
589   char *buffer = NULL;
590   size_t buffer_size = 0;
591   int status;
593   char *dummy;
594   char *saveptr;
596   char *key;
597   char *value;
599   const char* const *fields;
600   int fields_num;
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   }
610   status = powerdns_get_data (item, &buffer, &buffer_size);
611   if (status != 0)
612     return (-1);
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   }
625   assert (fields != NULL);
626   assert (fields_num > 0);
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     dummy = NULL;
635     value = strchr (key, '=');
636     if (value == NULL)
637       break;
639     *value = '\0';
640     value++;
642     if (value[0] == '\0')
643       continue;
645     /* Check if this item was requested. */
646     int i;
647     for (i = 0; i < fields_num; i++)
648       if (strcasecmp (key, fields[i]) == 0)
649         break;
650     if (i >= fields_num)
651       continue;
653     submit (item->instance, key, value);
654   } /* while (strtok_r) */
656   sfree (buffer);
658   return (0);
659 } /* }}} int powerdns_read_server */
661 /*
662  * powerdns_update_recursor_command
663  *
664  * Creates a string that holds the command to be sent to the recursor. This
665  * string is stores in the `command' member of the `list_item_t' passed to the
666  * function. This function is called by `powerdns_read_recursor'.
667  */
668 static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
670   char buffer[4096];
671   int status;
673   if (li == NULL)
674     return (0);
676   if (li->fields_num < 1)
677   {
678     sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
679   }
680   else
681   {
682     sstrncpy (buffer, "get ", sizeof (buffer));
683     status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
684         li->fields, li->fields_num,
685         /* seperator = */ " ");
686     if (status < 0)
687     {
688       ERROR ("powerdns plugin: strjoin failed.");
689       return (-1);
690     }
691     buffer[sizeof (buffer) - 1] = 0;
692     size_t len = strlen (buffer);
693     if (len < sizeof (buffer) - 2)
694     {
695       buffer[len++] = ' ';
696       buffer[len++] = '\n';
697       buffer[len++] = '\0';
698     }
699   }
701   buffer[sizeof (buffer) - 1] = 0;
702   li->command = strdup (buffer);
703   if (li->command == NULL)
704   {
705     ERROR ("powerdns plugin: strdup failed.");
706     return (-1);
707   }
709   return (0);
710 } /* }}} int powerdns_update_recursor_command */
712 static int powerdns_read_recursor (list_item_t *item) /* {{{ */
714   char *buffer = NULL;
715   size_t buffer_size = 0;
716   int status;
718   char *dummy;
720   char *keys_list;
721   char *key;
722   char *key_saveptr;
723   char *value;
724   char *value_saveptr;
726   if (item->command == NULL)
727   {
728     status = powerdns_update_recursor_command (item);
729     if (status != 0)
730     {
731       ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
732       return (-1);
733     }
735     DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
736         item->command);
737   }
738   assert (item->command != NULL);
740   status = powerdns_get_data (item, &buffer, &buffer_size);
741   if (status != 0)
742   {
743     ERROR ("powerdns plugin: powerdns_get_data failed.");
744     return (-1);
745   }
747   keys_list = strdup (item->command);
748   if (keys_list == NULL)
749   {
750     FUNC_ERROR ("strdup");
751     sfree (buffer);
752     return (-1);
753   }
755   key_saveptr = NULL;
756   value_saveptr = NULL;
758   /* Skip the `get' at the beginning */
759   strtok_r (keys_list, " \t", &key_saveptr);
761   dummy = buffer;
762   while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
763   {
764     dummy = NULL;
766     key = strtok_r (NULL, " \t", &key_saveptr);
767     if (key == NULL)
768       break;
770     submit (item->instance, key, value);
771   } /* while (strtok_r) */
773   sfree (buffer);
774   sfree (keys_list);
776   return (0);
777 } /* }}} int powerdns_read_recursor */
779 static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
780     oconfig_item_t *ci)
782   char **temp;
784   if (ci->values_num < 1)
785   {
786     WARNING ("powerdns plugin: The `Collect' option needs "
787         "at least one argument.");
788     return (-1);
789   }
791   for (int i = 0; i < ci->values_num; i++)
792     if (ci->values[i].type != OCONFIG_TYPE_STRING)
793     {
794       WARNING ("powerdns plugin: Only string arguments are allowed to "
795           "the `Collect' option.");
796       return (-1);
797     }
799   temp = realloc (li->fields,
800       sizeof (char *) * (li->fields_num + ci->values_num));
801   if (temp == NULL)
802   {
803     WARNING ("powerdns plugin: realloc failed.");
804     return (-1);
805   }
806   li->fields = temp;
808   for (int i = 0; i < ci->values_num; i++)
809   {
810     li->fields[li->fields_num] = strdup (ci->values[i].value.string);
811     if (li->fields[li->fields_num] == NULL)
812     {
813       WARNING ("powerdns plugin: strdup failed.");
814       continue;
815     }
816     li->fields_num++;
817   }
819   /* Invalidate a previously computed command */
820   sfree (li->command);
822   return (0);
823 } /* }}} int powerdns_config_add_collect */
825 static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
827   char *socket_temp;
829   list_item_t *item;
830   int status;
832   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
833   {
834     WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
835         ci->key);
836     return (-1);
837   }
839   item = calloc (1, sizeof (*item));
840   if (item == NULL)
841   {
842     ERROR ("powerdns plugin: calloc failed.");
843     return (-1);
844   }
846   item->instance = strdup (ci->values[0].value.string);
847   if (item->instance == NULL)
848   {
849     ERROR ("powerdns plugin: strdup failed.");
850     sfree (item);
851     return (-1);
852   }
854   /*
855    * Set default values for the members of list_item_t
856    */
857   if (strcasecmp ("Server", ci->key) == 0)
858   {
859     item->server_type = SRV_AUTHORITATIVE;
860     item->func = powerdns_read_server;
861     item->socktype = SOCK_STREAM;
862     socket_temp = strdup (SERVER_SOCKET);
863   }
864   else if (strcasecmp ("Recursor", ci->key) == 0)
865   {
866     item->server_type = SRV_RECURSOR;
867     item->func = powerdns_read_recursor;
868     item->socktype = SOCK_DGRAM;
869     socket_temp = strdup (RECURSOR_SOCKET);
870   }
871   else
872   {
873     /* We must never get here.. */
874     assert (0);
875     return (-1);
876   }
878   status = 0;
879   for (int i = 0; i < ci->children_num; i++)
880   {
881     oconfig_item_t *option = ci->children + i;
883     if (strcasecmp ("Collect", option->key) == 0)
884       status = powerdns_config_add_collect (item, option);
885     else if (strcasecmp ("Socket", option->key) == 0)
886       status = cf_util_get_string (option, &socket_temp);
887     else
888     {
889       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
890       status = -1;
891     }
893     if (status != 0)
894       break;
895   }
897   while (status == 0)
898   {
899     llentry_t *e;
901     if (socket_temp == NULL)
902     {
903       ERROR ("powerdns plugin: socket_temp == NULL.");
904       status = -1;
905       break;
906     }
908     item->sockaddr.sun_family = AF_UNIX;
909     sstrncpy (item->sockaddr.sun_path, socket_temp,
910       sizeof (item->sockaddr.sun_path));
912     e = llentry_create (item->instance, item);
913     if (e == NULL)
914     {
915       ERROR ("powerdns plugin: llentry_create failed.");
916       status = -1;
917       break;
918     }
919     llist_append (list, e);
921     break;
922   }
924   if (status != 0)
925   {
926     sfree (socket_temp);
927     sfree (item);
928     return (-1);
929   }
931   DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
933   sfree (socket_temp);
934   return (0);
935 } /* }}} int powerdns_config_add_server */
937 static int powerdns_config (oconfig_item_t *ci) /* {{{ */
939   DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
941   if (list == NULL)
942   {
943     list = llist_create ();
945     if (list == NULL)
946     {
947       ERROR ("powerdns plugin: `llist_create' failed.");
948       return (-1);
949     }
950   }
952   for (int i = 0; i < ci->children_num; i++)
953   {
954     oconfig_item_t *option = ci->children + i;
956     if ((strcasecmp ("Server", option->key) == 0)
957         || (strcasecmp ("Recursor", option->key) == 0))
958       powerdns_config_add_server (option);
959     else if (strcasecmp ("LocalSocket", option->key) == 0)
960     {
961       if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
962       {
963         WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
964       }
965       else
966       {
967         char *temp = strdup (option->values[0].value.string);
968         if (temp == NULL)
969           return (1);
970         sfree (local_sockpath);
971         local_sockpath = temp;
972       }
973     }
974     else
975     {
976       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
977     }
978   } /* for (i = 0; i < ci->children_num; i++) */
980   return (0);
981 } /* }}} int powerdns_config */
983 static int powerdns_read (void)
985   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
986   {
987     list_item_t *item = e->value;
988     item->func (item);
989   }
991   return (0);
992 } /* static int powerdns_read */
994 static int powerdns_shutdown (void)
996   if (list == NULL)
997     return (0);
999   for (llentry_t *e = llist_head (list); e != NULL; e = e->next)
1000   {
1001     list_item_t *item = (list_item_t *) e->value;
1002     e->value = NULL;
1004     sfree (item->instance);
1005     sfree (item->command);
1006     sfree (item);
1007   }
1009   llist_destroy (list);
1010   list = NULL;
1012   return (0);
1013 } /* static int powerdns_shutdown */
1015 void module_register (void)
1017   plugin_register_complex_config ("powerdns", powerdns_config);
1018   plugin_register_read ("powerdns", powerdns_read);
1019   plugin_register_shutdown ("powerdns", powerdns_shutdown );
1020 } /* void module_register */
1022 /* vim: set sw=2 sts=2 ts=8 fdm=marker : */