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