diff options
Diffstat (limited to 'tools/notify-dns-slaves.c')
-rw-r--r-- | tools/notify-dns-slaves.c | 814 |
1 files changed, 814 insertions, 0 deletions
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 <luca@morettoni.net> + * 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 <sys/socket.h> +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <stdarg.h> +#include <syslog.h> +#include <unistd.h> +#include <err.h> +#include <time.h> +#include <errno.h> +#include <fcntl.h> + +#include <netinet/in.h> +#ifdef HAVE_INET6 +#include <netinet6/in6.h> +#endif +#include <arpa/inet.h> +#include <netdb.h> + +#include <arpa/nameser.h> +#ifdef _BSD +#include <arpa/nameser_compat.h> +#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; +} |