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