]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added dns-client service and library for doing async dns lookups.
authorTimo Sirainen <tss@iki.fi>
Sun, 31 Jan 2010 17:10:38 +0000 (19:10 +0200)
committerTimo Sirainen <tss@iki.fi>
Sun, 31 Jan 2010 17:10:38 +0000 (19:10 +0200)
--HG--
branch : HEAD

configure.in
src/Makefile.am
src/dns/Makefile.am [new file with mode: 0644]
src/dns/dns-client-settings.c [new file with mode: 0644]
src/dns/dns-client.c [new file with mode: 0644]
src/lib-dns/Makefile.am [new file with mode: 0644]
src/lib-dns/dns-lookup.c [new file with mode: 0644]
src/lib-dns/dns-lookup.h [new file with mode: 0644]
src/lib-dovecot/Makefile.am

index b6e73b352a6ee4a53535c206404fd7e7164829f1..c448b8c4bc1a26ade9deafaac358553ecd9e43b7 100644 (file)
@@ -2407,7 +2407,7 @@ if test "$want_shared_libs" = "yes"; then
   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-sql/Makefile
 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/lda/Makefile
 src/log/Makefile
 src/lmtp/Makefile
 src/dict/Makefile
+src/dns/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
 src/login-common/Makefile
index 1d3c2ef0c548beb84c656f8652337a69168024d6..17eec0fd03034048c7aa1a2a24e073b2e298b219 100644 (file)
@@ -2,6 +2,7 @@ LIBDOVECOT_SUBDIRS = \
        lib \
        lib-auth \
        lib-charset \
+       lib-dns \
        lib-mail \
        lib-imap \
        lib-master \
@@ -21,6 +22,7 @@ SUBDIRS = \
        anvil \
        auth \
        dict \
+       dns \
        master \
        login-common \
        imap-login \
diff --git a/src/dns/Makefile.am b/src/dns/Makefile.am
new file mode 100644 (file)
index 0000000..3cffd59
--- /dev/null
@@ -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
diff --git a/src/dns/dns-client-settings.c b/src/dns/dns-client-settings.c
new file mode 100644 (file)
index 0000000..87ecb7a
--- /dev/null
@@ -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
+};
diff --git a/src/dns/dns-client.c b/src/dns/dns-client.c
new file mode 100644 (file)
index 0000000..fc44b60
--- /dev/null
@@ -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;
+}
diff --git a/src/lib-dns/Makefile.am b/src/lib-dns/Makefile.am
new file mode 100644 (file)
index 0000000..9adaf9d
--- /dev/null
@@ -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
diff --git a/src/lib-dns/dns-lookup.c b/src/lib-dns/dns-lookup.c
new file mode 100644 (file)
index 0000000..d594901
--- /dev/null
@@ -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);
+}
diff --git a/src/lib-dns/dns-lookup.h b/src/lib-dns/dns-lookup.h
new file mode 100644 (file)
index 0000000..585c81a
--- /dev/null
@@ -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
index be5e28e9a3608e533986260afd06f4522d180018..b0ce3f6ef85c9fb7556e496dc3dd437809d09a17 100644 (file)
@@ -4,6 +4,7 @@ libs = \
        ../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