Code

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