changeset 10615:8f9fc7fa7c73 HEAD

Added dns-client service and library for doing async dns lookups.
author Timo Sirainen <tss@iki.fi>
date Sun, 31 Jan 2010 19:10:38 +0200
parents 45709a87e4b3
children 23956a9b915b
files configure.in src/Makefile.am src/dns/Makefile.am src/dns/dns-client-settings.c src/dns/dns-client.c src/lib-dns/Makefile.am src/lib-dns/dns-lookup.c src/lib-dns/dns-lookup.h src/lib-dovecot/Makefile.am
diffstat 9 files changed, 447 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sun Jan 31 19:09:44 2010 +0200
+++ b/configure.in	Sun Jan 31 19:10:38 2010 +0200
@@ -2407,7 +2407,7 @@
   LIBDOVECOT_STORAGE='$(top_builddir)/src/lib-storage/libdovecot-storage.la'
   LIBDOVECOT_LOGIN='$(top_builddir)/src/login-common/libdovecot-login.la'
 else
-  LIBDOVECOT_DEPS='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la'
+  LIBDOVECOT_DEPS='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la'
   LIBDOVECOT="$LIBDOVECOT_DEPS \$(LIBICONV)"
   LIBDOVECOT_STORAGE_LAST='$(top_builddir)/src/lib-storage/list/libstorage_list.la $(top_builddir)/src/lib-storage/index/libstorage_index.la $(top_builddir)/src/lib-storage/libstorage.la $(top_builddir)/src/lib-index/libindex.la'
   LIBDOVECOT_STORAGE_FIRST='$(top_builddir)/src/lib-storage/libstorage_service.la $(top_builddir)/src/lib-storage/register/libstorage_register.la'
@@ -2578,6 +2578,7 @@
 src/lib-auth/Makefile
 src/lib-charset/Makefile
 src/lib-dict/Makefile
+src/lib-dns/Makefile
 src/lib-imap/Makefile
 src/lib-index/Makefile
 src/lib-lda/Makefile
@@ -2609,6 +2610,7 @@
 src/log/Makefile
 src/lmtp/Makefile
 src/dict/Makefile
+src/dns/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
 src/login-common/Makefile
--- a/src/Makefile.am	Sun Jan 31 19:09:44 2010 +0200
+++ b/src/Makefile.am	Sun Jan 31 19:10:38 2010 +0200
@@ -2,6 +2,7 @@
 	lib \
 	lib-auth \
 	lib-charset \
+	lib-dns \
 	lib-mail \
 	lib-imap \
 	lib-master \
@@ -21,6 +22,7 @@
 	anvil \
 	auth \
 	dict \
+	dns \
 	master \
 	login-common \
 	imap-login \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dns/Makefile.am	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,14 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = dns-client
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-settings
+
+dns_client_LDADD = $(LIBDOVECOT) $(MODULE_LIBS)
+dns_client_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+dns_client_SOURCES = \
+	dns-client.c \
+	dns-client-settings.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dns/dns-client-settings.c	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,47 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+
+#include <stddef.h>
+
+/* <settings checks> */
+static struct file_listener_settings dns_client_unix_listeners_array[] = {
+	{ "dns-client", 0666, "", "" },
+	{ "login/dns-client", 0666, "", "" }
+};
+static struct file_listener_settings *dns_client_unix_listeners[] = {
+	&dns_client_unix_listeners_array[0],
+	&dns_client_unix_listeners_array[1]
+};
+static buffer_t dns_client_unix_listeners_buf = {
+	dns_client_unix_listeners, sizeof(dns_client_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings dns_client_service_settings = {
+	.name = "dns_client",
+	.protocol = "",
+	.type = "",
+	.executable = "dns-client",
+	.user = "dovecot",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 0,
+	.client_limit = 1,
+	.service_count = 0,
+	.vsz_limit = -1U,
+
+	.unix_listeners = { { &dns_client_unix_listeners_buf,
+			      sizeof(dns_client_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dns/dns-client.c	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,147 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "restrict-access.h"
+#include "master-service.h"
+
+#include <unistd.h>
+
+struct dns_client {
+	int fd;
+	struct istream *input;
+	struct ostream *output;
+	struct io *io;
+	struct timeout *to;
+};
+
+#define MAX_INBUF_SIZE 1024
+#define MAX_OUTBUF_SIZE (1024*64)
+#define INPUT_TIMEOUT_MSECS (1000*10)
+
+static struct dns_client *dns_client = NULL;
+
+static void dns_client_destroy(struct dns_client **client);
+
+static int dns_client_input_line(struct dns_client *client, const char *line)
+{
+	struct ip_addr *ips;
+	unsigned int i, ips_count;
+	int ret;
+
+	if (strncmp(line, "IP\t", 3) == 0) {
+		ret = net_gethostbyname(line + 3, &ips, &ips_count);
+		if (ret == 0 && ips_count == 0) {
+			/* shouldn't happen, but fix it anyway.. */
+			ret = NO_ADDRESS;
+		}
+		if (ret != 0) {
+			o_stream_send_str(client->output,
+				t_strdup_printf("%d\n", ret));
+		} else {
+			o_stream_send_str(client->output,
+				t_strdup_printf("0 %u\n", ips_count));
+			for (i = 0; i < ips_count; i++) {
+				o_stream_send_str(client->output, t_strconcat(
+					net_ip2addr(&ips[i]), "\n", NULL));
+			}
+		}
+	} else if (strcmp(line, "QUIT") == 0) {
+		return -1;
+	} else {
+		o_stream_send_str(client->output, "Unknown command\n");
+	}
+
+	if (client->output->overflow)
+		return -1;
+	return 0;
+}
+
+static void dns_client_input(struct dns_client *client)
+{
+	const char *line;
+	int ret = 0;
+
+	o_stream_cork(client->output);
+	while ((line = i_stream_read_next_line(client->input)) != NULL) {
+		if (dns_client_input_line(client, line) < 0) {
+			ret = -1;
+			break;
+		}
+	}
+	o_stream_uncork(client->output);
+	timeout_reset(client->to);
+
+	if (client->input->eof || client->input->stream_errno != 0 || ret < 0)
+		dns_client_destroy(&client);
+}
+
+static void dns_client_timeout(struct dns_client *client)
+{
+	dns_client_destroy(&client);
+}
+
+static struct dns_client *dns_client_create(int fd)
+{
+	struct dns_client *client;
+
+	client = i_new(struct dns_client, 1);
+	client->fd = fd;
+	client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
+	client->output = o_stream_create_fd(fd, MAX_OUTBUF_SIZE, FALSE);
+	client->io = io_add(fd, IO_READ, dns_client_input, client);
+	client->to = timeout_add(INPUT_TIMEOUT_MSECS, dns_client_timeout,
+				 client);
+	return client;
+}
+
+static void dns_client_destroy(struct dns_client **_client)
+{
+	struct dns_client *client = *_client;
+
+	*_client = NULL;
+
+	timeout_remove(&client->to);
+	io_remove(&client->io);
+	i_stream_destroy(&client->input);
+	o_stream_destroy(&client->output);
+	if (close(client->fd) < 0)
+		i_error("close() failed: %m");
+	i_free(client);
+
+	dns_client = NULL;
+	master_service_client_connection_destroyed(master_service);
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+	if (dns_client != NULL) {
+		i_error("dns-client must be configured with client_limit=1");
+		(void)close(conn->fd);
+		return;
+	}
+	dns_client = dns_client_create(conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+	master_service = master_service_init("dns-client", 0,
+					     &argc, &argv, NULL);
+	if (master_getopt(master_service) > 0)
+		return FATAL_DEFAULT;
+
+	master_service_init_log(master_service, "dns-client: ");
+	restrict_access_by_env(NULL, FALSE);
+	restrict_access_allow_coredumps(TRUE);
+
+	master_service_init_finish(master_service);
+
+	master_service_run(master_service, client_connected);
+	if (dns_client != NULL)
+		dns_client_destroy(&dns_client);
+
+	master_service_deinit(&master_service);
+        return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dns/Makefile.am	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,17 @@
+noinst_LTLIBRARIES = libdns.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib
+
+libdns_la_SOURCES = \
+	dns-lookup.c
+
+headers = \
+	dns-lookup.h
+
+if INSTALL_HEADERS
+  pkginc_libdir=$(pkgincludedir)
+  pkginc_lib_HEADERS = $(headers)
+else
+  noinst_HEADERS = $(headers)
+endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dns/dns-lookup.c	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,184 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "network.h"
+#include "istream.h"
+#include "write-full.h"
+#include "time-util.h"
+#include "dns-lookup.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define MAX_INBUF_SIZE 512
+
+struct dns_lookup {
+	int fd;
+	char *path;
+
+	struct istream *input;
+	struct io *io;
+	struct timeout *to;
+
+	struct timeval start_time;
+	unsigned int warn_msecs;
+
+	struct dns_lookup_result result;
+	struct ip_addr *ips;
+	unsigned int ip_idx;
+
+	dns_lookup_callback_t *callback;
+	void *context;
+};
+
+static void dns_lookup_free(struct dns_lookup **_lookup);
+
+static int dns_lookup_input_line(struct dns_lookup *lookup, const char *line)
+{
+	struct dns_lookup_result *result = &lookup->result;
+
+	if (result->ips_count == 0) {
+		/* first line: <ret> <ip count> */
+		if (sscanf(line, "%d %u", &result->ret,
+			   &result->ips_count) == 0)
+			return -1;
+		if (result->ret != 0) {
+			result->error = net_gethosterror(result->ret);
+			return 1;
+		}
+		if (result->ips_count == 0)
+			return -1;
+
+		result->ips = lookup->ips =
+			i_new(struct ip_addr, result->ips_count);
+	} else {
+		if (net_addr2ip(line, &lookup->ips[lookup->ip_idx]) < 0)
+			return -1;
+		if (++lookup->ip_idx == result->ips_count) {
+			result->ret = 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static void dns_lookup_save_msecs(struct dns_lookup *lookup)
+{
+	struct timeval now;
+	int diff;
+
+	if (gettimeofday(&now, NULL) < 0)
+		i_fatal("gettimeofday() failed: %m");
+
+	diff = timeval_diff_msecs(&now, &lookup->start_time);
+	if (diff > 0)
+		lookup->result.msecs = diff;
+}
+
+static void dns_lookup_input(struct dns_lookup *lookup)
+{
+	const char *line;
+	struct dns_lookup_result *result = &lookup->result;
+	int ret = 0;
+
+	while ((line = i_stream_read_next_line(lookup->input)) != NULL) {
+		ret = dns_lookup_input_line(lookup, line);
+		if (ret > 0)
+			break;
+		if (ret < 0) {
+			result->error = t_strdup_printf(
+				"Invalid input from %s", lookup->path);
+			break;
+		}
+	}
+
+	if (result->error != NULL) {
+		/* already got the error */
+	} else if (lookup->input->stream_errno != 0) {
+		result->error = t_strdup_printf("read(%s) failed: %m",
+						lookup->path);
+		ret = -1;
+	} else if (lookup->input->eof) {
+		result->error = t_strdup_printf("Unexpected EOF from %s",
+						lookup->path);
+		ret = -1;
+	}
+	if (ret != 0) {
+		dns_lookup_save_msecs(lookup);
+		lookup->callback(result, lookup->context);
+		dns_lookup_free(&lookup);
+	}
+}
+
+static void dns_lookup_timeout(struct dns_lookup *lookup)
+{
+	lookup->result.error = "DNS lookup timed out";
+
+	lookup->callback(&lookup->result, lookup->context);
+	dns_lookup_free(&lookup);
+}
+
+#undef dns_lookup
+int dns_lookup(const char *host, const struct dns_lookup_settings *set,
+	       dns_lookup_callback_t *callback, void *context)
+{
+	struct dns_lookup *lookup;
+	struct dns_lookup_result result;
+	const char *cmd;
+	int fd;
+
+	memset(&result, 0, sizeof(result));
+	result.ret = NO_RECOVERY;
+
+	fd = net_connect_unix(set->dns_client_socket_path);
+	if (fd == -1) {
+		result.error = t_strdup_printf("connect(%s) failed: %m",
+					       set->dns_client_socket_path);
+		callback(&result, context);
+		return -1;
+	}
+
+	cmd = t_strconcat("IP\t", host, "\n", NULL);
+	if (write_full(fd, cmd, strlen(cmd)) < 0) {
+		result.error = t_strdup_printf("write(%s) failed: %m",
+					       set->dns_client_socket_path);
+		(void)close(fd);
+		callback(&result, context);
+		return -1;
+	}
+
+	lookup = i_new(struct dns_lookup, 1);
+	lookup->fd = fd;
+	lookup->path = i_strdup(set->dns_client_socket_path);
+	lookup->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
+	lookup->io = io_add(fd, IO_READ, dns_lookup_input, lookup);
+	if (set->timeout_msecs != 0) {
+		lookup->to = timeout_add(set->timeout_msecs,
+					 dns_lookup_timeout, lookup);
+	}
+	lookup->result.ret = NO_RECOVERY;
+	lookup->callback = callback;
+	lookup->context = context;
+	if (gettimeofday(&lookup->start_time, NULL) < 0)
+		i_fatal("gettimeofday() failed: %m");
+	return 0;
+}
+
+static void dns_lookup_free(struct dns_lookup **_lookup)
+{
+	struct dns_lookup *lookup = *_lookup;
+
+	*_lookup = NULL;
+
+	if (lookup->to != NULL)
+		timeout_remove(&lookup->to);
+	io_remove(&lookup->io);
+	i_stream_destroy(&lookup->input);
+	if (close(lookup->fd) < 0)
+		i_error("close(%s) failed: %m", lookup->path);
+
+	i_free(lookup->ips);
+	i_free(lookup->path);
+	i_free(lookup);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-dns/dns-lookup.h	Sun Jan 31 19:10:38 2010 +0200
@@ -0,0 +1,32 @@
+#ifndef DNS_LOOKUP_H
+#define DNS_LOOKUP_H
+
+struct dns_lookup_settings {
+	const char *dns_client_socket_path;
+	unsigned int timeout_msecs;
+};
+
+struct dns_lookup_result {
+	/* all is ok if ret=0, otherwise it contains net_gethosterror()
+	   compatible error code. error string is always set if ret != 0. */
+	int ret;
+	const char *error;
+
+	/* how many milliseconds the lookup took. */
+	unsigned int msecs;
+
+	unsigned int ips_count;
+	const struct ip_addr *ips;
+};
+
+typedef void dns_lookup_callback_t(const struct dns_lookup_result *result,
+				   void *context);
+
+int dns_lookup(const char *host, const struct dns_lookup_settings *set,
+	       dns_lookup_callback_t *callback, void *context);
+#define dns_lookup(host, set, callback, context) \
+	CONTEXT_CALLBACK2(dns_lookup, dns_lookup_callback_t, \
+			  callback, const struct dns_lookup_result *, \
+			  context, host, set)
+
+#endif
--- a/src/lib-dovecot/Makefile.am	Sun Jan 31 19:09:44 2010 +0200
+++ b/src/lib-dovecot/Makefile.am	Sun Jan 31 19:10:38 2010 +0200
@@ -4,6 +4,7 @@
 	../lib-imap/libimap.la \
 	../lib-mail/libmail.la \
 	../lib-auth/libauth.la \
+	../lib-dns/libdns.la \
 	../lib-charset/libcharset.la \
 	../lib-master/libmaster.la \
 	../lib/liblib.la