From 3add3c0e19b659427a2624ec85daf67019b8ed7f Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Mon, 9 Jun 2008 15:44:02 +0000 Subject: Lots of bug fixes, changes. --- tools/Makefile.am | 10 +- tools/notify-dns-slaves.c | 814 ++++++++++++++++++++++++++++++++++++++++++++++ tools/notify-slaves.c | 813 --------------------------------------------- 3 files changed, 819 insertions(+), 818 deletions(-) create mode 100644 tools/notify-dns-slaves.c delete mode 100644 tools/notify-slaves.c (limited to 'tools') diff --git a/tools/Makefile.am b/tools/Makefile.am index 19bbc50..fe0ffc5 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -4,14 +4,14 @@ INCLUDES = \ $(PTHREAD_CFLAGS) bin_PROGRAMS = \ - notify-slaves + notify-dns-slaves -notify_slaves_SOURCES = \ - notify-slaves.c \ +notify_dns_slaves_SOURCES = \ + notify-dns-slaves.c \ ../common/async-resolver.c \ ../common/server-mainloop.c \ ../common/sock-any.c # TODO: Somehow PTHREAD_LIBS isn't setup on linux gcc -notify_slaves_LDFLAGS = -pthread -notify_slaves_LIBS = $(PTHREAD_LIBS) \ No newline at end of file +notify_dns_slaves_LDFLAGS = -pthread +notify_dns_slaves_LIBS = $(PTHREAD_LIBS) \ No newline at end of file diff --git a/tools/notify-dns-slaves.c b/tools/notify-dns-slaves.c new file mode 100644 index 0000000..96f2ea7 --- /dev/null +++ b/tools/notify-dns-slaves.c @@ -0,0 +1,814 @@ + +/* + * Based on zonenotify by Morettoni Luca + */ + +/* + * Copyright (c) 2004 Morettoni Luca + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: zonenotify.c,v 1.4 2004/07/19 12:37:04 luca Exp $ + */ + +#include "config.h" + +#include "common/async-resolver.h" +#include "common/server-mainloop.h" +#include "common/sock-any.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef HAVE_INET6 +#include +#endif +#include +#include + +#include +#ifdef _BSD +#include +#endif + +/* ------------------------------------------------------------------------------ + * DECLARATIONS + */ + +static const char *DNS_ERRORS[] = { + "No error", + "Format error", + "Server failure", + "Non-existent domain", + "Not implemented", + "Query refused", + "Name exists when it should not", + "RR Set exists when it should not", + "RR Set that should exist does not", + "Server not authoritative for zone", + "Name not contained in zone", + "", "", "", "", "", /* available for assignment */ + "Bad OPT version", + "TSIG signature failure", + "Key not recognized", + "Signature out of time window", + "Bad TKEY mode", + "Duplicate key name", + "Algorithm not supported" +}; + +#define N_DNS_ERRORS (sizeof (DNS_ERRORS) / sizeof (DNS_ERRORS[0])) + +#define MAX_NAME 128 /* Maximum length of a zone or server name */ +#define BIND_TRIES 16 /* Number of times we try to get a port */ +#define RETRIES 3 /* Number of times we send out packet */ +#define RETRY_INTERVAL 400 /* Milliseconds between sending out packet */ +#define TIMEOUT_INTERVAL 5000 /* Timeout for response in milliseconds */ +#define DELAY_INTERVAL 5000 /* Number of milliseconds before processing input on stdin */ +#define LINE_LENGTH 1023 /* Maximum length of buffer on stdin */ + +static int the_socket4 = -1; +static int the_socket6 = -1; + +static char stdin_buffer[LINE_LENGTH + 1]; +static size_t stdin_offset = 0; + +static int processing_active = 0; +static int input_complete = 0; + +static unsigned int unique_identifier = 1; + +static int is_helper = 0; +static int debug_level = LOG_INFO; + +typedef struct _notification { + struct _notification *next; + int unique; + + int resolved; + char zone[MAX_NAME]; + char server[MAX_NAME]; + struct sockaddr_any address; + + unsigned char packet[PACKETSZ]; + size_t packet_len; + + uint64_t start; + uint64_t timeout; + uint64_t retry; + int retries; +} notification; + +static notification *the_notifications = NULL; + +#define WHITESPACE " \t\r\n\v" + +/* -------------------------------------------------------------------------------- + * WARNINGS AND LOGGING + */ + +static void +vmessage(int level, int erno, const char* msg, va_list ap) +{ + #define MAX_MSGLEN 1024 + char buf[MAX_MSGLEN]; + size_t len; + + if(debug_level < level) + return; + + assert (msg); + + strncpy (buf, msg, MAX_MSGLEN); + buf[MAX_MSGLEN - 1] = 0; + + if (erno) { + len = strlen (buf); + strncpy (buf + len, ": ", MAX_MSGLEN - len); + buf[MAX_MSGLEN - 1] = 0; + len = strlen (buf); + strncpy (buf + len, strerror (erno), MAX_MSGLEN - len); + buf[MAX_MSGLEN - 1] = 0; + } + + /* Either to syslog or stderr */ + if (is_helper && level != LOG_DEBUG) + vsyslog (level, buf, ap); + + vwarnx (buf, ap); +} + +static void +debug (const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_DEBUG, 0, msg, va); + va_end (va); +} + +static void +info (const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_INFO, 0, msg, va); + va_end (va); +} + +static void +warningx (const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_WARNING, 0, msg, va); + va_end (va); +} + +static void +warning (const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_WARNING, errno, msg, va); + va_end (va); +} + +static void +fatalx (int ret, const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_ERR, 0, msg, va); + va_end (va); + exit (1); +} + +void +fatal (int ret, const char *msg, ...) +{ + va_list va; + va_start (va, msg); + vmessage (LOG_ERR, errno, msg, va); + va_end (va); + exit (1); +} + +/* -------------------------------------------------------------------------------- + * HELPERS + */ + +static const char* +ltrim (const char *data) +{ + while (*data && strchr (WHITESPACE, *data)) + ++data; + return data; +} + +static void +rtrim (char *data) +{ + char *t = data + strlen (data); + while (t > data && strchr (WHITESPACE, *(t - 1))) { + t--; + *t = 0; + } +} + +static char* +trim (char *data) +{ + data = (char*)ltrim (data); + rtrim (data); + return data; +} + +/* -------------------------------------------------------------------------------- + * FUNCTIONALITY + */ + +static void +socket_callback (int fd, int type, void *arg) +{ + unsigned char packet[PACKETSZ]; + notification **not, *notif; + struct sockaddr_any sany; + HEADER *hdr; + ssize_t num; + + SANY_LEN (sany) = sizeof (sany); + num = recvfrom (fd, packet, sizeof (packet), 0, &SANY_ADDR (sany), &SANY_LEN (sany)); + if (num < 0) { + warning ("couldn't receive packet"); + return; + } + + if (num < 4) { + warningx ("received response packet that is too short (%d bytes)", num); + return; + } + + hdr = (HEADER*)packet; + + /* Find the notification that this refers to */ + for (not = &the_notifications; *not; not = &(*not)->next) { + notif = *not; + if (notif->unique == hdr->id) { + + /* Report any errors */ + if (hdr->qr && hdr->rcode) { + if (hdr->rcode < N_DNS_ERRORS) + warningx ("received error for server: %s: %s", notif->server, DNS_ERRORS[hdr->rcode]); + else + warningx ("received errer for server: %s: %d", notif->server, (int)hdr->rcode); + } else { + debug ("received successful response for server: %s", notif->server); + } + + /* Remove from notification queue */ + *not = notif->next; + free (notif); + + break; + } + } +} + +static int +process_all_packets (uint64_t when, void *unused) +{ + notification **not, *notif; + int rc; + + if (!processing_active) { + if (server_timer (RETRY_INTERVAL / 2, process_all_packets, NULL) < 0) + warning ("couldn't setup timer to process packets"); + processing_active = 1; + debug ("starting processing"); + } + + for (not = &the_notifications; *not; ) { + notif = *not; + + /* Is it ready? */ + if (notif->start < when && notif->resolved) { + + /* Timed out? */ + if (notif->timeout <= when) { + warningx ("notification to server timed out: %s", notif->server); + *not = notif->next; + free (notif); + continue; + } + + /* See if we should send */ + if (notif->retry <= when) { + + /* Calculate next retry */ + if (notif->retries) { + notif->retry = when + RETRY_INTERVAL; + --notif->retries; + } else { + /* Some time in the distant future */ + notif->retry = when + (TIMEOUT_INTERVAL * 1000); + notif->retries = 0; + } + + info ("sending notify for zone %s to %s", notif->zone, notif->server); + + if (SANY_TYPE (notif->address) == AF_INET) + rc = sendto (the_socket4, notif->packet, notif->packet_len, 0, + &SANY_ADDR (notif->address), SANY_LEN (notif->address)); + else if (SANY_TYPE (notif->address) == AF_INET6) + rc = sendto (the_socket6, notif->packet, notif->packet_len, 0, + &SANY_ADDR (notif->address), SANY_LEN (notif->address)); + else { + warningx ("unsupported address type: %d", SANY_TYPE (notif->address)); + *not = notif->next; + free (notif); + continue; + } + + if (rc < 0) + warning ("couldn't send packet to server: %s", notif->server); + } + } + + not = ¬if->next; + } + + /* Continue processing? */ + if (the_notifications) + return 1; + + if (input_complete) + server_stop (); + + debug ("processing done"); + processing_active = 0; + + return 0; +} + +static void +prepare_and_process (notification *notif, uint64_t when) +{ + assert (notif); + assert (SANY_TYPE (notif->address)); + assert (notif->resolved); + assert (notif->start); + + debug ("preparing notification to: %s", notif->server); + notif->timeout = notif->start + TIMEOUT_INTERVAL; + notif->retries = RETRIES; + notif->retry = when; + + process_all_packets (when, NULL); +} + +static void +address_resolved (int ecode, struct addrinfo *ai, void *arg) +{ + notification **not, *notif; + + for (not = &the_notifications; *not; not = &(*not)->next) { + notif = *not; + + /* We search for the notification, in case it has been freed */ + if (notif != arg) + continue; + + /* A bummer resolve */ + if (ecode) { + warningx ("couldn't resolve server: %s: %s", + notif->server, gai_strerror (ecode)); + *not = notif->next; + free (notif); + break; + + /* A successful resolve */ + } else { + debug ("resolved address for: %s", notif->server); + memcpy (&SANY_ADDR (notif->address), ai->ai_addr, ai->ai_addrlen); + SANY_LEN (notif->address) = ai->ai_addrlen; + notif->resolved = 1; + prepare_and_process (notif, server_get_time ()); + break; + } + } +} + +/* encode name string in ns query format */ +static int +ns_encode (const char *str, char *buff) +{ + char *pos; + int size; + int len = 0; + + for (;;) { + pos = (char *) strchr (str, '.'); + if (!pos) + break; + + size = pos - str; + *buff++ = size; + + strncpy (buff, str, size); + buff += size; + + len += size + 1; + str = pos + 1; + } + + size = strlen (str); + if (size) { + *buff++ = size; + strncpy (buff, str, size); + buff += size; + len += size + 1; + } + + *buff = 0; + + return len; +} + +static int +process_notify (const char *zone, const char *server, uint64_t delay) +{ + notification *notif, **not; + char name[MAX_NAME * 2]; + HEADER *hdr; + size_t reqlen; + u_int16_t val; + uint64_t when; + int rc; + + assert (zone && zone[0]); + assert (server && server[0]); + + if (strlen (zone) > MAX_NAME) { + warningx ("zone name too long, could not fit in packet"); + return -1; + } + if (strlen (server) > MAX_NAME) { + warningx ("server name too long, could not fit in packet"); + return -1; + } + + /* Search for this name in the list */ + for (not = &the_notifications; *not; not = &(*not)->next) { + notif = *not; + /* Find a match, just ignore this request */ + if (strcmp (zone, notif->zone) == 0 && + strcmp (server, notif->server) == 0) { + debug ("already have notification packet for %s to %s", zone, server); + return 0; + } + } + + debug ("building notification packet for %s to %s", zone, server); + + assert (MAX_NAME + sizeof (HEADER) <= PACKETSZ); + notif = calloc (1, sizeof (notification)); + if (!notif) { + warningx ("out of memory"); + return -1; + } + + hdr = (HEADER*)notif->packet; + hdr->qr = 0; + hdr->opcode = NS_NOTIFY_OP; + hdr->aa = 1; + hdr->tc = 0; + hdr->rd = 0; + hdr->ra = 0; + hdr->unused = 0; + hdr->rcode = 0; + hdr->qdcount = htons (1); + hdr->ancount = 0; + hdr->nscount = 0; + hdr->arcount = 0; + hdr->id = htons (unique_identifier++); + + /* the 0x00 at the end must be copied! */ + reqlen = ns_encode (zone, name) + 1; + assert (reqlen < MAX_NAME); + memcpy (notif->packet + sizeof (HEADER), name, reqlen); + + /* query type */ + val = htons (T_SOA); + memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2); + reqlen += 2; + + /* query class */ + val = htons (C_IN); + memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2); + reqlen += 2; + + notif->unique = hdr->id; + notif->packet_len = sizeof (HEADER) + reqlen; + + /* Copy the address in */ + strncpy (notif->zone, zone, sizeof (notif->zone)); + strncpy (notif->server, server, sizeof (notif->server)); + notif->server[sizeof (notif->server) - 1] = 0; + + /* Try and resolve the domain name */ + rc = sock_any_pton (notif->server, ¬if->address, SANY_OPT_DEFPORT(53) | SANY_OPT_NORESOLV); + if (rc < 0) { + warning ("could not parse server name: %s", notif->server); + free (notif); + return -1; + } + + /* Add it to the queue */ + notif->next = the_notifications; + the_notifications = notif; + + /* Delay before processing */ + when = server_get_time (); + notif->start = when + delay; + + /* Needs resolving */ + if (rc == SANY_AF_DNS) { + SANY_TYPE (notif->address) = 0; + debug ("resolving address: %s", notif->server); + async_resolver_queue (notif->server, "53", NULL, address_resolved, notif); + } else { + notif->resolved = 1; + prepare_and_process (notif, when); + } + + return 0; +} + +static void +process_stdin_line (char *line) +{ + char *zone, *server, *next; + size_t len; + + debug ("received line: %s", line); + + /* Ignore blank lines and comment lines */ + line = trim (line); + if (!*line || *line == '#') + return; + + next = strchr (line, ':'); + if (!next) { + warningx ("received invalid line: %s", line); + return; + } + + *next = 0; + ++next; + + /* Figure out what command it is */ + line = trim (line); + if (strcmp (line, "NOTIFY") != 0) { + warningx ("received invalid command: %s", line); + return; + } + + /* Figure out the zone */ + zone = trim (next); + len = strcspn (zone, WHITESPACE); + if (len == 0 || zone[len] == 0) { + warningx ("missing arguments to NOTIFY command"); + return; + } + zone[len] = 0; + + /* Figure out the server */ + server = trim (zone + len + 1); + len = strcspn (server, WHITESPACE); + if (len == 0) { + warningx ("missing arguments to NOTIFY command"); + return; + } + server[len] = 0; + + process_notify (zone, server, DELAY_INTERVAL); +} + +static void +stdin_callback (int fd, int type, void *arg) +{ + char *line; + int num; + + assert (fd == 0); + + num = read (fd, stdin_buffer, LINE_LENGTH - stdin_offset); + if (num < 0) { + if (errno == EAGAIN || errno == EINTR) + return; + warningx ("couldn't read from stdin"); + input_complete = 1; + } else if (num == 0) { + debug ("input closed"); + input_complete = 1; + } else { + stdin_offset += num; + } + + do { + line = strchr (stdin_buffer, '\n'); + + if (!line && input_complete) + line = stdin_buffer + stdin_offset; + + if (!line && stdin_offset >= LINE_LENGTH) { + warningx ("input line too long"); + line = stdin_buffer + LINE_LENGTH; + } + + if (!line) /* Wait for more data */ + break; + + *line = 0; + num = (line + 1) - stdin_buffer; + + process_stdin_line (stdin_buffer); + stdin_offset = LINE_LENGTH - num; + memmove (stdin_buffer, stdin_buffer + num, stdin_offset); + + } while (!input_complete); + + if (input_complete) { + if (!the_notifications) + server_stop (); + server_unwatch (fd); + } +} + +static void +make_sockets (void) +{ + int i, rand, isbound; + time_t now; + struct sockaddr_in addr4; +#ifdef HAVE_INET6 + struct sockaddr_in6 addr6; +#endif + + time (&now); + srandom ((long) now); + + the_socket4 = socket (AF_INET, SOCK_DGRAM, 0); +#ifdef HAVE_INET6 + the_socket6 = socket (AF_INET6, SOCK_DGRAM, 0); +#endif + if (the_socket4 < 0 && the_socket6 < 0) + fatal (1, "couldn't create socket for communication"); + + if (the_socket4 >= 0) { + isbound = 0; + + /* local port: random */ + memset (&addr4, 0, sizeof (addr4)); + addr4.sin_family = AF_INET; + for (i = 0; i < BIND_TRIES && !isbound; i++) { + rand = 1025 + (random () % 15000); + addr4.sin_port = htons (rand); + isbound = (bind (the_socket4, (struct sockaddr*) &addr4, sizeof (addr4)) >= 0); + } + + if (!isbound) + fatal (1, "couldn't bind to local port"); + + if (server_watch (the_socket4, SERVER_READ, socket_callback, NULL) < 0) + fatal (1, "couldn't watch socket"); + } + +#ifdef HAVE_INET6 + if (the_socket6 >= 0) { + isbound = 0; + + /* local port: random */ + memset (&addr6, 0, sizeof (addr6)); + addr6.sin_family = AF_INET6; + for (i = 0; i < BIND_TRIES && !isbound; i++) { + rand = 1025 + (random () % 15000); + addr6.sin6_port = htons (rand); + isbound = (bind (the_socket6, (struct sockaddr*) &addr6, sizeof (addr6)) == 0); + } + + if (!isbound) + fatal_error (1, "couldn't bind to local port"); + + if (server_watch (the_socket6, SERVER_READ, socket_callback, NULL) < 0) + fatal_error (1, "couldn't watch socket"); + } +#endif +} + +static void +usage() +{ + fprintf (stderr, "usage: slapi-dnsnotify-helper -s [-d level]\n"); + fprintf (stderr, "usage: slapi-dnsnotify-helper [-d level] zone server ...\n"); + exit (2); +} + +int +main(int argc, char *argv[]) +{ + char *str; + int ch = 0, i; + + while ((ch = getopt (argc, argv, "d:s")) != -1) { + switch (ch) + { + case 'd': + debug_level = strtol(optarg, &str, 10); + if (*str || debug_level > 4) + fatalx (1, "invalid debug log level: %s", optarg); + debug_level += LOG_ERR; + break; + case 's': + is_helper = 1; + break; + case '?': + default: + usage (); + } + } + + argc -= optind; + argv += optind; + + if (!is_helper && argc < 2) + usage (); + if (is_helper && argc > 0) + usage (); + + server_init (); + make_sockets (); + + if (async_resolver_init () < 0) + fatal (1, "couldn't initialize DNS resolver"); + + if (is_helper) { + openlog ("notify-dns-slaves", 0, LOG_DAEMON); + input_complete = 0; + /* Watch stdin for data */ + fcntl (0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK); + if (server_watch (0, SERVER_READ, stdin_callback, NULL) < 0) + fatal (1, "coludn't watch stdin for changes"); + } else { + input_complete = 1; + str = argv[0]; + for (i = 1; i < argc; ++i) + process_notify (str, argv[i], 0); + } + + if (server_run () < 0) + fatal (1, "couldn't run server"); + + if (the_socket4 >= 0) + close (the_socket4); + if (the_socket6 >= 0) + close (the_socket6); + + async_resolver_uninit (); + server_uninit (); + + return 0; +} diff --git a/tools/notify-slaves.c b/tools/notify-slaves.c deleted file mode 100644 index ebd2262..0000000 --- a/tools/notify-slaves.c +++ /dev/null @@ -1,813 +0,0 @@ - -/* - * Based on zonenotify by Morettoni Luca - */ - -/* - * Copyright (c) 2004 Morettoni Luca - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * $Id: zonenotify.c,v 1.4 2004/07/19 12:37:04 luca Exp $ - */ - -#include "config.h" - -#include "common/async-resolver.h" -#include "common/server-mainloop.h" -#include "common/sock-any.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#ifdef HAVE_INET6 -#include -#endif -#include -#include - -#include -#ifdef _BSD -#include -#endif - -/* ------------------------------------------------------------------------------ - * DECLARATIONS - */ - -static const char *DNS_ERRORS[] = { - "No error", - "Format error", - "Server failure", - "Non-existent domain", - "Not implemented", - "Query refused", - "Name exists when it should not", - "RR Set exists when it should not", - "RR Set that should exist does not", - "Server not authoritative for zone", - "Name not contained in zone", - "", "", "", "", "", /* available for assignment */ - "Bad OPT version", - "TSIG signature failure", - "Key not recognized", - "Signature out of time window", - "Bad TKEY mode", - "Duplicate key name", - "Algorithm not supported" -}; - -#define N_DNS_ERRORS (sizeof (DNS_ERRORS) / sizeof (DNS_ERRORS[0])) - -#define MAX_NAME 128 /* Maximum length of a zone or server name */ -#define BIND_TRIES 16 /* Number of times we try to get a port */ -#define RETRIES 3 /* Number of times we send out packet */ -#define RETRY_INTERVAL 400 /* Milliseconds between sending out packet */ -#define TIMEOUT_INTERVAL 5000 /* Timeout for response in milliseconds */ -#define DELAY_INTERVAL 5000 /* Number of milliseconds before processing input on stdin */ -#define LINE_LENGTH 1023 /* Maximum length of buffer on stdin */ - -static int the_socket4 = -1; -static int the_socket6 = -1; - -static char stdin_buffer[LINE_LENGTH + 1]; -static size_t stdin_offset = 0; - -static int stdin_closed = 0; -static int processing_active = 0; - -static unsigned int unique_identifier = 1; - -static int is_helper = 0; -static int debug_level = LOG_WARNING; - -typedef struct _notification { - struct _notification *next; - int unique; - - int resolved; - char zone[MAX_NAME]; - char server[MAX_NAME]; - struct sockaddr_any address; - - unsigned char packet[PACKETSZ]; - size_t packet_len; - - uint64_t start; - uint64_t timeout; - uint64_t retry; - int retries; -} notification; - -static notification *the_notifications = NULL; - -#define WHITESPACE " \t\r\n\v" - -/* -------------------------------------------------------------------------------- - * WARNINGS AND LOGGING - */ - -static void -vmessage(int level, int erno, const char* msg, va_list ap) -{ - #define MAX_MSGLEN 1024 - char buf[MAX_MSGLEN]; - size_t len; - - if(debug_level < level) - return; - - assert (msg); - - strncpy (buf, msg, MAX_MSGLEN); - buf[MAX_MSGLEN - 1] = 0; - - if (erno) { - len = strlen (buf); - strncpy (buf + len, ": ", MAX_MSGLEN - len); - buf[MAX_MSGLEN - 1] = 0; - len = strlen (buf); - strncpy (buf + len, strerror (erno), MAX_MSGLEN - len); - buf[MAX_MSGLEN - 1] = 0; - } - - /* Either to syslog or stderr */ - if (is_helper && level < LOG_DEBUG) - vsyslog (level, buf, ap); - vwarnx(buf, ap); -} - -static void -debug (const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_DEBUG, 0, msg, va); - va_end (va); -} - -static void -info (const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_INFO, 0, msg, va); - va_end (va); -} - -static void -warningx (const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_WARNING, 0, msg, va); - va_end (va); -} - -static void -warning (const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_WARNING, errno, msg, va); - va_end (va); -} - -static void -fatalx (int ret, const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_ERR, 0, msg, va); - va_end (va); - exit (1); -} - -void -fatal (int ret, const char *msg, ...) -{ - va_list va; - va_start (va, msg); - vmessage (LOG_ERR, errno, msg, va); - va_end (va); - exit (1); -} - -/* -------------------------------------------------------------------------------- - * HELPERS - */ - -static const char* -ltrim (const char *data) -{ - while (*data && strchr (WHITESPACE, *data)) - ++data; - return data; -} - -static void -rtrim (char *data) -{ - char *t = data + strlen (data); - while (t > data && strchr (WHITESPACE, *(t - 1))) { - t--; - *t = 0; - } -} - -static char* -trim (char *data) -{ - data = (char*)ltrim (data); - rtrim (data); - return data; -} - -/* -------------------------------------------------------------------------------- - * FUNCTIONALITY - */ - -static void -socket_callback (int fd, int type, void *arg) -{ - unsigned char packet[PACKETSZ]; - notification **not, *notif; - struct sockaddr_any sany; - HEADER *hdr; - ssize_t num; - - SANY_LEN (sany) = sizeof (sany); - num = recvfrom (fd, packet, sizeof (packet), 0, &SANY_ADDR (sany), &SANY_LEN (sany)); - if (num < 0) { - warning ("couldn't receive packet"); - return; - } - - if (num < 4) { - warningx ("received response packet that is too short (%d bytes)", num); - return; - } - - hdr = (HEADER*)packet; - - /* Find the notification that this refers to */ - for (not = &the_notifications; *not; not = &(*not)->next) { - notif = *not; - if (notif->unique == hdr->id) { - - /* Report any errors */ - if (hdr->qr && hdr->rcode) { - if (hdr->rcode < N_DNS_ERRORS) - warningx ("received error for server: %s: %s", notif->server, DNS_ERRORS[hdr->rcode]); - else - warningx ("received errer for server: %s: %d", notif->server, (int)hdr->rcode); - } else { - debug ("received successful response for server: %s", notif->server); - } - - /* Remove from notification queue */ - *not = notif->next; - free (notif); - - break; - } - } -} - -static int -process_all_packets (uint64_t when, void *unused) -{ - notification **not, *notif; - int rc; - - if (!processing_active) { - if (server_timer (RETRY_INTERVAL / 2, process_all_packets, NULL) < 0) - warning ("couldn't setup timer to process packets"); - processing_active = 1; - debug ("starting processing"); - } - - for (not = &the_notifications; *not; ) { - notif = *not; - - /* Is it ready? */ - if (notif->start < when && notif->resolved) { - - /* Timed out? */ - if (notif->timeout <= when) { - warningx ("notification to server timed out: %s", notif->server); - *not = notif->next; - free (notif); - continue; - } - - /* See if we should send */ - if (notif->retry <= when) { - - /* Calculate next retry */ - if (notif->retries) { - notif->retry = when + RETRY_INTERVAL; - --notif->retries; - } else { - /* Some time in the distant future */ - notif->retry = when + (TIMEOUT_INTERVAL * 1000); - notif->retries = 0; - } - - info ("sending notify for zone %s to %s", notif->zone, notif->server); - - if (SANY_TYPE (notif->address) == AF_INET) - rc = sendto (the_socket4, notif->packet, notif->packet_len, 0, - &SANY_ADDR (notif->address), SANY_LEN (notif->address)); - else if (SANY_TYPE (notif->address) == AF_INET6) - rc = sendto (the_socket6, notif->packet, notif->packet_len, 0, - &SANY_ADDR (notif->address), SANY_LEN (notif->address)); - else { - warningx ("unsupported address type: %d", SANY_TYPE (notif->address)); - *not = notif->next; - free (notif); - continue; - } - - if (rc < 0) - warning ("couldn't send packet to server: %s", notif->server); - } - } - - not = &(*not)->next; - } - - /* Continue processing? */ - if (the_notifications) - return 1; - - if (stdin_closed) { - debug ("processing done, and no more input, stopping"); - server_stop (); - } else { - debug ("processing done for now"); - processing_active = 0; - } - - return 0; -} - -static void -prepare_and_process (notification *notif, uint64_t when) -{ - assert (notif); - assert (SANY_TYPE (notif->address)); - assert (notif->resolved); - assert (notif->start); - - debug ("preparing notification to: %s", notif->server); - notif->timeout = notif->start + TIMEOUT_INTERVAL; - notif->retries = RETRIES; - notif->retry = when; - - process_all_packets (when, NULL); -} - -static void -address_resolved (int ecode, struct addrinfo *ai, void *arg) -{ - notification **not, *notif; - - for (not = &the_notifications; *not; not = &(*not)->next) { - notif = *not; - - /* We search for the notification, in case it has been freed */ - if (notif != arg) - continue; - - /* A bummer resolve */ - if (ecode) { - warningx ("couldn't resolve server: %s: %s", - notif->server, gai_strerror (ecode)); - *not = notif->next; - free (notif); - - /* A successful resolve */ - } else { - debug ("resolved address for: %s", notif->server); - memcpy (&SANY_ADDR (notif->address), ai->ai_addr, ai->ai_addrlen); - SANY_LEN (notif->address) = ai->ai_addrlen; - notif->resolved = 1; - prepare_and_process (notif, server_get_time ()); - } - - break; - } -} - -/* encode name string in ns query format */ -static int -ns_encode (const char *str, char *buff) -{ - char *pos; - int size; - int len = 0; - - for (;;) { - pos = (char *) strchr (str, '.'); - if (!pos) - break; - - size = pos - str; - *buff++ = size; - - strncpy (buff, str, size); - buff += size; - - len += size + 1; - str = pos + 1; - } - - size = strlen (str); - if (size) { - *buff++ = size; - strncpy (buff, str, size); - buff += size; - len += size + 1; - } - - *buff = 0; - - return len; -} - -static int -process_notify (const char *zone, const char *server, uint64_t delay) -{ - notification *notif, **not; - char name[MAX_NAME * 2]; - HEADER *hdr; - size_t reqlen; - u_int16_t val; - uint64_t when; - int rc; - - assert (zone && zone[0]); - assert (server && server[0]); - - if (strlen (zone) > MAX_NAME) { - warningx ("zone name too long, could not fit in packet"); - return -1; - } - if (strlen (server) > MAX_NAME) { - warningx ("server name too long, could not fit in packet"); - return -1; - } - - /* Search for this name in the list */ - for (not = &the_notifications; *not; not = &(*not)->next) { - notif = *not; - /* Find a match, just ignore this request */ - if (strcmp (zone, notif->zone) == 0 && - strcmp (server, notif->server) == 0) { - debug ("already have notification packet for %s to %s", zone, server); - return 0; - } - } - - debug ("building notification packet for %s to %s", zone, server); - - assert (MAX_NAME + sizeof (HEADER) <= PACKETSZ); - notif = calloc (1, sizeof (notification)); - if (!notif) { - warningx ("out of memory"); - return -1; - } - - hdr = (HEADER*)notif->packet; - hdr->qr = 0; - hdr->opcode = NS_NOTIFY_OP; - hdr->aa = 1; - hdr->tc = 0; - hdr->rd = 0; - hdr->ra = 0; - hdr->unused = 0; - hdr->rcode = 0; - hdr->qdcount = htons (1); - hdr->ancount = 0; - hdr->nscount = 0; - hdr->arcount = 0; - hdr->id = htons (unique_identifier++); - - /* the 0x00 at the end must be copied! */ - reqlen = ns_encode (zone, name) + 1; - assert (reqlen < MAX_NAME); - memcpy (notif->packet + sizeof (HEADER), name, reqlen); - - /* query type */ - val = htons (T_SOA); - memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2); - reqlen += 2; - - /* query class */ - val = htons (C_IN); - memcpy (notif->packet + sizeof (HEADER) + reqlen, &val, 2); - reqlen += 2; - - notif->unique = hdr->id; - notif->packet_len = sizeof (HEADER) + reqlen; - - /* Copy the address in */ - strncpy (notif->zone, zone, sizeof (notif->zone)); - strncpy (notif->server, server, sizeof (notif->server)); - notif->server[sizeof (notif->server) - 1] = 0; - - /* Try and resolve the domain name */ - rc = sock_any_pton (notif->server, ¬if->address, SANY_OPT_DEFPORT(53) | SANY_OPT_NORESOLV); - if (rc < 0) { - warning ("could not parse server name: %s", notif->server); - free (notif); - return -1; - } - - /* Add it to the queue */ - notif->next = the_notifications; - the_notifications = notif; - - /* Delay before processing */ - when = server_get_time (); - notif->start = when + delay; - - /* Needs resolving */ - if (rc == SANY_AF_DNS) { - SANY_TYPE (notif->address) = 0; - debug ("resolving address: %s", notif->server); - async_resolver_queue (notif->server, "53", NULL, address_resolved, notif); - } else { - notif->resolved = 1; - prepare_and_process (notif, when); - } - - return 0; -} - -static void -process_stdin_line (char *line) -{ - char *zone, *server, *next; - size_t len; - - debug ("received line: %s", line); - - /* Ignore blank lines and comment lines */ - line = trim (line); - if (!*line || *line == '#') - return; - - next = strchr (line, ':'); - if (!next) { - warningx ("received invalid line: %s", line); - return; - } - - *next = 0; - ++next; - - /* Figure out what command it is */ - line = trim (line); - if (strcmp (line, "NOTIFY") != 0) { - warningx ("received invalid command: %s", line); - return; - } - - /* Figure out the zone */ - zone = trim (next); - len = strcspn (zone, WHITESPACE); - if (len == 0 || zone[len] == 0) { - warningx ("missing arguments to NOTIFY command"); - return; - } - zone[len] = 0; - - /* Figure out the server */ - server = trim (zone + len + 1); - len = strcspn (server, WHITESPACE); - if (len == 0) { - warningx ("missing arguments to NOTIFY command"); - return; - } - server[len] = 0; - - process_notify (zone, server, DELAY_INTERVAL); -} - -static void -stdin_callback (int fd, int type, void *arg) -{ - char *line; - int num; - - assert (fd == 0); - - num = read (fd, stdin_buffer, LINE_LENGTH - stdin_offset); - if (num < 0) { - if (errno == EAGAIN || errno == EINTR) - return; - warningx ("couldn't read from stdin"); - server_unwatch (fd); - stdin_closed = 1; - return; - } else if (num == 0) { - stdin_closed = 1; - server_unwatch (fd); - } - - stdin_offset += num; - - for (;;) { - line = strchr (stdin_buffer, '\n'); - - if (!line && stdin_offset >= LINE_LENGTH) { - warningx ("input line too long"); - line = stdin_buffer + LINE_LENGTH; - } - - if (!line && stdin_closed) - line = stdin_buffer + stdin_offset; - - if (!line) /* Wait for more data */ - break; - - *line = 0; - num = (line + 1) - stdin_buffer; - - process_stdin_line (stdin_buffer); - stdin_offset = LINE_LENGTH - num; - memmove (stdin_buffer, stdin_buffer + num, stdin_offset); - - if (stdin_closed) - break; - } -} - -static void -make_sockets (void) -{ - int i, rand, isbound; - time_t now; - struct sockaddr_in addr4; -#ifdef HAVE_INET6 - struct sockaddr_in6 addr6; -#endif - - time (&now); - srandom ((long) now); - - the_socket4 = socket (AF_INET, SOCK_DGRAM, 0); -#ifdef HAVE_INET6 - the_socket6 = socket (AF_INET6, SOCK_DGRAM, 0); -#endif - if (the_socket4 < 0 && the_socket6 < 0) - fatal (1, "couldn't create socket for communication"); - - if (the_socket4 >= 0) { - isbound = 0; - - /* local port: random */ - memset (&addr4, 0, sizeof (addr4)); - addr4.sin_family = AF_INET; - for (i = 0; i < BIND_TRIES && !isbound; i++) { - rand = 1025 + (random () % 15000); - addr4.sin_port = htons (rand); - isbound = (bind (the_socket4, (struct sockaddr*) &addr4, sizeof (addr4)) >= 0); - } - - if (!isbound) - fatal (1, "couldn't bind to local port"); - - if (server_watch (the_socket4, SERVER_READ, socket_callback, NULL) < 0) - fatal (1, "couldn't watch socket"); - } - -#ifdef HAVE_INET6 - if (the_socket6 >= 0) { - isbound = 0; - - /* local port: random */ - memset (&addr6, 0, sizeof (addr6)); - addr6.sin_family = AF_INET6; - for (i = 0; i < BIND_TRIES && !isbound; i++) { - rand = 1025 + (random () % 15000); - addr6.sin6_port = htons (rand); - isbound = (bind (the_socket6, (struct sockaddr*) &addr6, sizeof (addr6)) == 0); - } - - if (!isbound) - fatal_error (1, "couldn't bind to local port"); - - if (server_watch (the_socket6, SERVER_READ, socket_callback, NULL) < 0) - fatal_error (1, "couldn't watch socket"); - } -#endif -} - -static void -usage() -{ - fprintf (stderr, "usage: slapi-dnsnotify-helper -s [-d level]\n"); - fprintf (stderr, "usage: slapi-dnsnotify-helper [-d level] zone server ...\n"); - exit (2); -} - -int -main(int argc, char *argv[]) -{ - char *str; - int ch = 0, i; - - while ((ch = getopt (argc, argv, "d:s")) != -1) { - switch (ch) - { - case 'd': - debug_level = strtol(optarg, &str, 10); - if (*str || debug_level > 4) - fatalx (1, "invalid debug log level: %s", optarg); - debug_level += LOG_ERR; - break; - case 's': - is_helper = 1; - break; - case '?': - default: - usage (); - } - } - - argc -= optind; - argv += optind; - - if (!is_helper && argc < 2) - usage (); - if (is_helper && argc > 0) - usage (); - - server_init (); - make_sockets (); - - if (async_resolver_init () < 0) - fatal (1, "couldn't initialize DNS resolver"); - - if (is_helper) { - stdin_closed = 0; - - /* Watch stdin for data */ - fcntl (0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK); - if (server_watch (0, SERVER_READ, stdin_callback, NULL) < 0) - fatal (1, "coludn't watch stdin for changes"); - } else { - stdin_closed = 1; - str = argv[0]; - for (i = 1; i < argc; ++i) - process_notify (str, argv[i], 0); - } - - if (server_run () < 0) - fatal (1, "couldn't run server"); - - if (the_socket4 >= 0) - close (the_socket4); - if (the_socket6 >= 0) - close (the_socket6); - - async_resolver_uninit (); - server_uninit (); - - return 0; -} -- cgit v1.2.3