/* * 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; }