summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am17
-rw-r--r--tools/notify-slaves.c813
2 files changed, 830 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..19bbc50
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,17 @@
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(PTHREAD_CFLAGS)
+
+bin_PROGRAMS = \
+ notify-slaves
+
+notify_slaves_SOURCES = \
+ notify-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
diff --git a/tools/notify-slaves.c b/tools/notify-slaves.c
new file mode 100644
index 0000000..ebd2262
--- /dev/null
+++ b/tools/notify-slaves.c
@@ -0,0 +1,813 @@
+
+/*
+ * 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 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, &notif->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;
+}