Code

Merge pull request #1831 from rubenk/ai_hints-cleanup
[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"
28 #include "common.h"
29 #include "plugin.h"
30 #include "configfile.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     int i;
635     dummy = NULL;
637     value = strchr (key, '=');
638     if (value == NULL)
639       break;
641     *value = '\0';
642     value++;
644     if (value[0] == '\0')
645       continue;
647     /* Check if this item was requested. */
648     for (i = 0; i < fields_num; i++)
649       if (strcasecmp (key, fields[i]) == 0)
650         break;
651     if (i >= fields_num)
652       continue;
654     submit (item->instance, key, value);
655   } /* while (strtok_r) */
657   sfree (buffer);
659   return (0);
660 } /* }}} int powerdns_read_server */
662 /*
663  * powerdns_update_recursor_command
664  *
665  * Creates a string that holds the command to be sent to the recursor. This
666  * string is stores in the `command' member of the `list_item_t' passed to the
667  * function. This function is called by `powerdns_read_recursor'.
668  */
669 static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
671   char buffer[4096];
672   int status;
674   if (li == NULL)
675     return (0);
677   if (li->fields_num < 1)
678   {
679     sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
680   }
681   else
682   {
683     sstrncpy (buffer, "get ", sizeof (buffer));
684     status = strjoin (&buffer[strlen("get ")], sizeof (buffer) - strlen ("get "),
685         li->fields, li->fields_num,
686         /* seperator = */ " ");
687     if (status < 0)
688     {
689       ERROR ("powerdns plugin: strjoin failed.");
690       return (-1);
691     }
692     buffer[sizeof (buffer) - 1] = 0;
693     size_t len = strlen (buffer);
694     if (len < sizeof (buffer) - 2)
695     {
696       buffer[len++] = ' ';
697       buffer[len++] = '\n';
698       buffer[len++] = '\0';
699     }
700   }
702   buffer[sizeof (buffer) - 1] = 0;
703   li->command = strdup (buffer);
704   if (li->command == NULL)
705   {
706     ERROR ("powerdns plugin: strdup failed.");
707     return (-1);
708   }
710   return (0);
711 } /* }}} int powerdns_update_recursor_command */
713 static int powerdns_read_recursor (list_item_t *item) /* {{{ */
715   char *buffer = NULL;
716   size_t buffer_size = 0;
717   int status;
719   char *dummy;
721   char *keys_list;
722   char *key;
723   char *key_saveptr;
724   char *value;
725   char *value_saveptr;
727   if (item->command == NULL)
728   {
729     status = powerdns_update_recursor_command (item);
730     if (status != 0)
731     {
732       ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
733       return (-1);
734     }
736     DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
737         item->command);
738   }
739   assert (item->command != NULL);
741   status = powerdns_get_data (item, &buffer, &buffer_size);
742   if (status != 0)
743   {
744     ERROR ("powerdns plugin: powerdns_get_data failed.");
745     return (-1);
746   }
748   keys_list = strdup (item->command);
749   if (keys_list == NULL)
750   {
751     FUNC_ERROR ("strdup");
752     sfree (buffer);
753     return (-1);
754   }
756   key_saveptr = NULL;
757   value_saveptr = NULL;
759   /* Skip the `get' at the beginning */
760   strtok_r (keys_list, " \t", &key_saveptr);
762   dummy = buffer;
763   while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
764   {
765     dummy = NULL;
767     key = strtok_r (NULL, " \t", &key_saveptr);
768     if (key == NULL)
769       break;
771     submit (item->instance, key, value);
772   } /* while (strtok_r) */
774   sfree (buffer);
775   sfree (keys_list);
777   return (0);
778 } /* }}} int powerdns_read_recursor */
780 static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
781     oconfig_item_t *ci)
783   int i;
784   char **temp;
786   if (ci->values_num < 1)
787   {
788     WARNING ("powerdns plugin: The `Collect' option needs "
789         "at least one argument.");
790     return (-1);
791   }
793   for (i = 0; i < ci->values_num; i++)
794     if (ci->values[i].type != OCONFIG_TYPE_STRING)
795     {
796       WARNING ("powerdns plugin: Only string arguments are allowed to "
797           "the `Collect' option.");
798       return (-1);
799     }
801   temp = realloc (li->fields,
802       sizeof (char *) * (li->fields_num + ci->values_num));
803   if (temp == NULL)
804   {
805     WARNING ("powerdns plugin: realloc failed.");
806     return (-1);
807   }
808   li->fields = temp;
810   for (i = 0; i < ci->values_num; i++)
811   {
812     li->fields[li->fields_num] = strdup (ci->values[i].value.string);
813     if (li->fields[li->fields_num] == NULL)
814     {
815       WARNING ("powerdns plugin: strdup failed.");
816       continue;
817     }
818     li->fields_num++;
819   }
821   /* Invalidate a previously computed command */
822   sfree (li->command);
824   return (0);
825 } /* }}} int powerdns_config_add_collect */
827 static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
829   char *socket_temp;
831   list_item_t *item;
832   int status;
833   int i;
835   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
836   {
837     WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
838         ci->key);
839     return (-1);
840   }
842   item = calloc (1, sizeof (*item));
843   if (item == NULL)
844   {
845     ERROR ("powerdns plugin: calloc failed.");
846     return (-1);
847   }
849   item->instance = strdup (ci->values[0].value.string);
850   if (item->instance == NULL)
851   {
852     ERROR ("powerdns plugin: strdup failed.");
853     sfree (item);
854     return (-1);
855   }
857   /*
858    * Set default values for the members of list_item_t
859    */
860   if (strcasecmp ("Server", ci->key) == 0)
861   {
862     item->server_type = SRV_AUTHORITATIVE;
863     item->func = powerdns_read_server;
864     item->socktype = SOCK_STREAM;
865     socket_temp = strdup (SERVER_SOCKET);
866   }
867   else if (strcasecmp ("Recursor", ci->key) == 0)
868   {
869     item->server_type = SRV_RECURSOR;
870     item->func = powerdns_read_recursor;
871     item->socktype = SOCK_DGRAM;
872     socket_temp = strdup (RECURSOR_SOCKET);
873   }
874   else
875   {
876     /* We must never get here.. */
877     assert (0);
878     return (-1);
879   }
881   status = 0;
882   for (i = 0; i < ci->children_num; i++)
883   {
884     oconfig_item_t *option = ci->children + i;
886     if (strcasecmp ("Collect", option->key) == 0)
887       status = powerdns_config_add_collect (item, option);
888     else if (strcasecmp ("Socket", option->key) == 0)
889       status = cf_util_get_string (option, &socket_temp);
890     else
891     {
892       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
893       status = -1;
894     }
896     if (status != 0)
897       break;
898   }
900   while (status == 0)
901   {
902     llentry_t *e;
904     if (socket_temp == NULL)
905     {
906       ERROR ("powerdns plugin: socket_temp == NULL.");
907       status = -1;
908       break;
909     }
911     item->sockaddr.sun_family = AF_UNIX;
912     sstrncpy (item->sockaddr.sun_path, socket_temp,
913       sizeof (item->sockaddr.sun_path));
915     e = llentry_create (item->instance, item);
916     if (e == NULL)
917     {
918       ERROR ("powerdns plugin: llentry_create failed.");
919       status = -1;
920       break;
921     }
922     llist_append (list, e);
924     break;
925   }
927   if (status != 0)
928   {
929     sfree (socket_temp);
930     sfree (item);
931     return (-1);
932   }
934   DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
936   sfree (socket_temp);
937   return (0);
938 } /* }}} int powerdns_config_add_server */
940 static int powerdns_config (oconfig_item_t *ci) /* {{{ */
942   int i;
944   DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
946   if (list == NULL)
947   {
948     list = llist_create ();
950     if (list == NULL)
951     {
952       ERROR ("powerdns plugin: `llist_create' failed.");
953       return (-1);
954     }
955   }
957   for (i = 0; i < ci->children_num; i++)
958   {
959     oconfig_item_t *option = ci->children + i;
961     if ((strcasecmp ("Server", option->key) == 0)
962         || (strcasecmp ("Recursor", option->key) == 0))
963       powerdns_config_add_server (option);
964     else if (strcasecmp ("LocalSocket", option->key) == 0)
965     {
966       if ((option->values_num != 1) || (option->values[0].type != OCONFIG_TYPE_STRING))
967       {
968         WARNING ("powerdns plugin: `%s' needs exactly one string argument.", option->key);
969       }
970       else
971       {
972         char *temp = strdup (option->values[0].value.string);
973         if (temp == NULL)
974           return (1);
975         sfree (local_sockpath);
976         local_sockpath = temp;
977       }
978     }
979     else
980     {
981       ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
982     }
983   } /* for (i = 0; i < ci->children_num; i++) */
985   return (0);
986 } /* }}} int powerdns_config */
988 static int powerdns_read (void)
990   llentry_t *e;
992   for (e = llist_head (list); e != NULL; e = e->next)
993   {
994     list_item_t *item = e->value;
995     item->func (item);
996   }
998   return (0);
999 } /* static int powerdns_read */
1001 static int powerdns_shutdown (void)
1003   llentry_t *e;
1005   if (list == NULL)
1006     return (0);
1008   for (e = llist_head (list); e != NULL; e = e->next)
1009   {
1010     list_item_t *item = (list_item_t *) e->value;
1011     e->value = NULL;
1013     sfree (item->instance);
1014     sfree (item->command);
1015     sfree (item);
1016   }
1018   llist_destroy (list);
1019   list = NULL;
1021   return (0);
1022 } /* static int powerdns_shutdown */
1024 void module_register (void)
1026   plugin_register_complex_config ("powerdns", powerdns_config);
1027   plugin_register_read ("powerdns", powerdns_read);
1028   plugin_register_shutdown ("powerdns", powerdns_shutdown );
1029 } /* void module_register */
1031 /* vim: set sw=2 sts=2 ts=8 fdm=marker : */