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) /* {{{ */
577 {
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) /* {{{ */
654 {
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) /* {{{ */
692 {
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) /* {{{ */
794 {
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) /* {{{ */
892 {
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 */