]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added support for tcpwrappers and potentially other login access checks.
authorTimo Sirainen <tss@iki.fi>
Sat, 6 Feb 2010 17:46:40 +0000 (19:46 +0200)
committerTimo Sirainen <tss@iki.fi>
Sat, 6 Feb 2010 17:46:40 +0000 (19:46 +0200)
--HG--
branch : HEAD

12 files changed:
.hgignore
configure.in
doc/example-config/dovecot.conf
src/login-common/Makefile.am
src/login-common/access-lookup.c [new file with mode: 0644]
src/login-common/access-lookup.h [new file with mode: 0644]
src/login-common/login-settings.c
src/login-common/login-settings.h
src/login-common/main.c
src/util/Makefile.am
src/util/tcpwrap-settings.c [new file with mode: 0644]
src/util/tcpwrap.c [new file with mode: 0644]

index ab9c34fbcdd32f46c78d696dd2605180d4863c27..934bc94fba5c319acec384575336f001673eeaae 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -60,6 +60,7 @@ src/config/config
 src/config/doveconf
 src/lda/dovecot-lda
 src/dict/dict
+src/dns/dns-client
 src/doveadm/doveadm
 src/dsync/dsync
 src/imap-login/imap-login
@@ -84,6 +85,7 @@ src/util/listview
 src/util/maildirlock
 src/util/rawlog
 src/util/script
+src/util/tcpwrap
 src/plugins/quota/rquota_xdr.c
 src/plugins/quota/rquota.h
 
index 3128502100b0ef83c79bcb5afe8c7733a76b932d..6deeeabb96fb17f99811296cce3807ac6507ad05 100644 (file)
@@ -169,6 +169,11 @@ AC_ARG_WITH(libcap,
   TEST_WITH(libcap, $withval),
   want_libcap=auto)
 
+AC_ARG_WITH(libwrap,
+[  --with-libwrap          Build with libwrap, ie. TCP-wrappers (auto)],
+  TEST_WITH(libwrap, $withval),
+  want_libwrap=auto)
+
 AC_ARG_WITH(ssl,
 [  --with-ssl=gnutls|openssl Build with GNUTLS or OpenSSL (default)],
        if test x$withval = xno; then
@@ -329,6 +334,28 @@ if test $want_libcap != no; then
   ])
 fi
 
+have_libwrap=no
+if test $want_libwrap != no; then
+  AC_CHECK_HEADER(tcpd.h, [
+    AC_CHECK_LIB(wrap, request_init, [
+      AC_DEFINE(HAVE_LIBWRAP,, Define if you have libwrap)
+      LIBWRAP_LIBS=-lwrap
+      AC_SUBST(LIBWRAP_LIBS)
+      have_libwrap=yes
+    ], [
+      if test "$want_libwrap" = "yes"; then
+       AC_ERROR([Can't build with libwrap support: libwrap not found])
+      fi
+    ])
+    LIBS=$old_LIBS
+  ], [
+    if test "$want_libwrap" = "yes"; then
+      AC_ERROR([Can't build with libwrap support: tcpd.h not found])
+    fi
+  ])
+fi
+AM_CONDITIONAL(TCPWRAPPERS, test "$have_libwrap" = "yes")
+
 AC_DEFINE(PACKAGE_WEBPAGE, "http://www.dovecot.org/", Support URL)
 
 dnl * after -lsocket and -lnsl tests, inet_aton() may be in them
index 834b0307cba30035c19d4c3dd7aadb744ca3d767..7aa56c33731bbd27f4c065c05dac543d011d5bc1 100644 (file)
@@ -46,6 +46,9 @@
 # these networks. Typically you'd specify your IMAP proxy servers here.
 #login_trusted_networks =
 
+# Sepace separated list of login access check sockets (e.g. tcpwrap)
+#login_access_sockets = 
+
 # Show more verbose process titles (in ps). Currently shows user name and
 # IP address. Useful for seeing who are actually using the IMAP processes
 # (eg. shared mailboxes or if same uid is used for multiple accounts).
index 7102442cbb952fe64db1cb16f52768d5800eb3b2..ba7b99d35f9982486acb087a8a15d5636015d7fd 100644 (file)
@@ -9,6 +9,7 @@ AM_CPPFLAGS = \
        -DPKG_STATEDIR=\""$(statedir)"\"
 
 liblogin_la_SOURCES = \
+       access-lookup.c \
        client-common.c \
        client-common-auth.c \
        login-proxy.c \
@@ -24,6 +25,7 @@ liblogin_la_LIBADD = \
        $(SSL_LIBS)
 
 headers = \
+       access-lookup.h \
        client-common.h \
        login-common.h \
        login-proxy.h \
diff --git a/src/login-common/access-lookup.c b/src/login-common/access-lookup.c
new file mode 100644 (file)
index 0000000..11f7465
--- /dev/null
@@ -0,0 +1,119 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "network.h"
+#include "fdpass.h"
+#include "access-lookup.h"
+
+#include <unistd.h>
+
+#define ACCESS_LOOKUP_TIMEOUT_MSECS (1000*60)
+
+struct access_lookup {
+       int refcount;
+
+       int fd;
+       char *path;
+
+       struct io *io;
+       struct timeout *to;
+
+       access_lookup_callback_t *callback;
+       void *context;
+};
+
+static void access_lookup_input(struct access_lookup *lookup)
+{
+       unsigned char buf[3];
+       ssize_t ret;
+       bool success = FALSE;
+
+       ret = read(lookup->fd, buf, sizeof(buf));
+       if (ret < 0) {
+               i_error("read(%s) failed: %m", lookup->path);
+       } else if (ret == 0) {
+               /* connection close -> no success */
+       } else if (ret == 2 && buf[0] == '0' && buf[1] == '\n') {
+               /* no success */
+       } else if (ret == 2 && buf[0] == '1' && buf[1] == '\n') {
+               success = TRUE;
+       } else {
+               i_error("access(%s): Invalid input", lookup->path);
+       }
+
+       lookup->refcount++;
+       lookup->callback(success, lookup->context);
+       if (lookup->refcount > 1)
+               access_lookup_destroy(&lookup);
+       access_lookup_destroy(&lookup);
+}
+
+static void access_lookup_timeout(struct access_lookup *lookup)
+{
+       i_error("access(%s): Timed out while waiting for reply", lookup->path);
+
+       lookup->refcount++;
+       lookup->callback(FALSE, lookup->context);
+       if (lookup->refcount > 1)
+               access_lookup_destroy(&lookup);
+       access_lookup_destroy(&lookup);
+}
+
+struct access_lookup *
+access_lookup(const char *path, int client_fd, const char *daemon_name,
+             access_lookup_callback_t *callback, void *context)
+{
+       struct access_lookup *lookup;
+       const char *cmd;
+       ssize_t ret;
+       int fd;
+
+       fd = net_connect_unix(path);
+       if (fd == -1) {
+               i_error("connect(%s) failed: %m", path);
+               return NULL;
+       }
+
+       cmd = t_strconcat(daemon_name, "\n", NULL);
+       ret = fd_send(fd, client_fd, cmd, strlen(cmd));
+       if (ret != (ssize_t)strlen(cmd)) {
+               if (ret < 0)
+                       i_error("fd_send(%s) failed: %m", path);
+               else
+                       i_error("fd_send(%s) didn't write enough bytes", path);
+               (void)close(fd);
+               return NULL;
+       }
+
+       lookup = i_new(struct access_lookup, 1);
+       lookup->refcount = 1;
+       lookup->fd = fd;
+       lookup->path = i_strdup(path);
+       lookup->io = io_add(fd, IO_READ, access_lookup_input, lookup);
+       lookup->to = timeout_add(ACCESS_LOOKUP_TIMEOUT_MSECS,
+                                access_lookup_timeout, lookup);
+       lookup->callback = callback;
+       lookup->context = context;
+       return lookup;
+}
+
+void access_lookup_destroy(struct access_lookup **_lookup)
+{
+       struct access_lookup *lookup = *_lookup;
+
+       i_assert(lookup->refcount > 0);
+       if (--lookup->refcount > 0)
+               return;
+
+       *_lookup = NULL;
+
+       if (lookup->to != NULL)
+               timeout_remove(&lookup->to);
+       io_remove(&lookup->io);
+       if (close(lookup->fd) < 0)
+               i_error("close(%s) failed: %m", lookup->path);
+
+       i_free(lookup->path);
+       i_free(lookup);
+}
diff --git a/src/login-common/access-lookup.h b/src/login-common/access-lookup.h
new file mode 100644 (file)
index 0000000..6ebb582
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef ACCESS_LOOKUP_H
+#define ACCESS_LOOKUP_H
+
+typedef void access_lookup_callback_t(bool success, void *context);
+
+struct access_lookup *
+access_lookup(const char *path, int client_fd, const char *daemon_name,
+             access_lookup_callback_t *callback, void *context);
+void access_lookup_destroy(struct access_lookup **lookup);
+
+#endif
index 2d97c120b01dc55303df341ed19c00adb09699fe..6970b303410cd7471787a64384aa237ce1720014 100644 (file)
@@ -21,6 +21,7 @@ static const struct setting_define login_setting_defines[] = {
        DEF(SET_STR_VARS, login_greeting),
        DEF(SET_STR, login_log_format_elements),
        DEF(SET_STR, login_log_format),
+       DEF(SET_STR, login_access_sockets),
 
        DEF(SET_ENUM, ssl),
        DEF(SET_STR, ssl_ca),
@@ -50,6 +51,7 @@ static const struct login_settings login_default_settings = {
        .login_greeting = PACKAGE_NAME" ready.",
        .login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l %c",
        .login_log_format = "%$: %s",
+       .login_access_sockets = "",
 
        .ssl = "yes:no:required",
        .ssl_ca = "",
index 2d57b8e2fb2c39f92a634e2063900ddd55478ae5..b144f8d7fdd03b914e352b27b249f36d911a13fa 100644 (file)
@@ -7,6 +7,7 @@ struct login_settings {
        const char *login_trusted_networks;
        const char *login_greeting;
        const char *login_log_format_elements, *login_log_format;
+       const char *login_access_sockets;
 
        const char *ssl;
        const char *ssl_ca;
index 6ac9f8bb9c3d58ac79f3a87bc5fa988b9c3c7576..b585ec825a68b006ae61cf2a46d0d05aaacb7844 100644 (file)
@@ -10,6 +10,7 @@
 #include "master-service.h"
 #include "master-interface.h"
 #include "client-common.h"
+#include "access-lookup.h"
 #include "anvil-client.h"
 #include "auth-client.h"
 #include "ssl-proxy.h"
 #include <unistd.h>
 #include <syslog.h>
 
+struct login_access_lookup {
+       struct master_service_connection conn;
+       struct io *io;
+
+       char **sockets, **next_socket;
+       struct access_lookup *access;
+};
+
 struct auth_client *auth_client;
 struct master_auth *master_auth;
 bool closing_down;
@@ -30,6 +39,8 @@ void **global_other_settings;
 static bool shutting_down = FALSE;
 static bool ssl_connections = FALSE;
 
+static void login_access_lookup_next(struct login_access_lookup *lookup);
+
 void login_refresh_proctitle(void)
 {
        struct client *client = clients;
@@ -62,7 +73,8 @@ static void login_die(void)
        }
 }
 
-static void client_connected(const struct master_service_connection *conn)
+static void
+client_connected_finish(const struct master_service_connection *conn)
 {
        struct client *client;
        struct ssl_proxy *proxy;
@@ -91,6 +103,7 @@ static void client_connected(const struct master_service_connection *conn)
                if (fd_ssl == -1) {
                        net_disconnect(conn->fd);
                        pool_unref(&pool);
+                       master_service_client_connection_destroyed(master_service);
                        return;
                }
 
@@ -105,6 +118,91 @@ static void client_connected(const struct master_service_connection *conn)
        client->local_port = local_port;
 }
 
+static void login_access_lookup_free(struct login_access_lookup *lookup)
+{
+       if (lookup->io != NULL)
+               io_remove(&lookup->io);
+       if (lookup->access != NULL)
+               access_lookup_destroy(&lookup->access);
+       if (lookup->conn.fd != -1) {
+               if (close(lookup->conn.fd) < 0)
+                       i_error("close(client) failed: %m");
+               master_service_client_connection_destroyed(master_service);
+       }
+
+       p_strsplit_free(default_pool, lookup->sockets);
+       i_free(lookup);
+}
+
+static void login_access_callback(bool success, void *context)
+{
+       struct login_access_lookup *lookup = context;
+
+       if (!success) {
+               i_info("access(%s): Client refused (rip=%s)",
+                      *lookup->next_socket,
+                      net_ip2addr(&lookup->conn.remote_ip));
+               login_access_lookup_free(lookup);
+       } else {
+               lookup->next_socket++;
+               login_access_lookup_next(lookup);
+       }
+}
+
+static void login_access_lookup_next(struct login_access_lookup *lookup)
+{
+       if (*lookup->next_socket == NULL) {
+               /* last one */
+               client_connected_finish(&lookup->conn);
+               lookup->conn.fd = -1;
+               login_access_lookup_free(lookup);
+               return;
+       }
+       lookup->access = access_lookup(*lookup->next_socket, lookup->conn.fd,
+                                      login_protocol, login_access_callback,
+                                      lookup);
+       if (lookup->access == NULL)
+               login_access_lookup_free(lookup);
+}
+
+static void client_input_error(struct login_access_lookup *lookup)
+{
+       char c;
+       int ret;
+
+       ret = recv(lookup->conn.fd, &c, 1, MSG_PEEK);
+       if (ret <= 0) {
+               i_info("access(%s): Client disconnected during lookup (rip=%s)",
+                      *lookup->next_socket,
+                      net_ip2addr(&lookup->conn.remote_ip));
+               login_access_lookup_free(lookup);
+       } else {
+               /* actual input. stop listening until lookup is done. */
+               io_remove(&lookup->io);
+       }
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+       const char *access_sockets =
+               global_login_settings->login_access_sockets;
+       struct login_access_lookup *lookup;
+
+       if (*access_sockets == '\0') {
+               /* no access checks */
+               client_connected_finish(conn);
+               return;
+       }
+
+       lookup = i_new(struct login_access_lookup, 1);
+       lookup->conn = *conn;
+       lookup->io = io_add(conn->fd, IO_READ, client_input_error, lookup);
+       lookup->sockets = p_strsplit_spaces(default_pool, access_sockets, " ");
+       lookup->next_socket = lookup->sockets;
+
+       login_access_lookup_next(lookup);
+}
+
 static void auth_connect_notify(struct auth_client *client ATTR_UNUSED,
                                bool connected, void *context ATTR_UNUSED)
 {
index e54d4c540efd215984013f306f0fbc7b4adb1e6f..3ffe7078dc66e5c248fdde204b8d3b75c2c5d412 100644 (file)
@@ -3,6 +3,7 @@ pkglibexecdir = $(libexecdir)/dovecot
 pkglibexec_PROGRAMS = \
        rawlog \
        script \
+       $(TCPWRAP_BIN) \
        gdbhelper \
        imap-utf7 \
        listview \
@@ -32,6 +33,15 @@ script_DEPENDENCIES = $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_DEPS)
 script_SOURCES = \
        script.c
 
+if TCPWRAPPERS
+TCPWRAP_BIN = tcpwrap
+tcpwrap_LDADD = $(LIBDOVECOT) $(MODULE_LIBS) $(LIBWRAP_LIBS)
+tcpwrap_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+tcpwrap_SOURCES = \
+       tcpwrap.c \
+       tcpwrap-settings.c
+endif
+
 gdbhelper_LDADD = $(LIBDOVECOT)
 gdbhelper_DEPENDENCIES = $(LIBDOVECOT_DEPS)
 gdbhelper_SOURCES = \
diff --git a/src/util/tcpwrap-settings.c b/src/util/tcpwrap-settings.c
new file mode 100644 (file)
index 0000000..9bb62b3
--- /dev/null
@@ -0,0 +1,32 @@
+/* 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>
+
+struct service_settings tcpwrap_service_settings = {
+       .name = "tcpwrap",
+       .protocol = "",
+       .type = "",
+       .executable = "tcpwrap",
+       .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 = ARRAY_INIT,
+       .fifo_listeners = ARRAY_INIT,
+       .inet_listeners = ARRAY_INIT
+};
diff --git a/src/util/tcpwrap.c b/src/util/tcpwrap.c
new file mode 100644 (file)
index 0000000..e6d0a7e
--- /dev/null
@@ -0,0 +1,126 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "fdpass.h"
+#include "write-full.h"
+#include "restrict-access.h"
+#include "master-service.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <tcpd.h>
+
+struct tcpwrap_client {
+       int fd;
+       struct io *io;
+       struct timeout *to;
+};
+
+#define INPUT_TIMEOUT_MSECS (1000*10)
+
+static struct tcpwrap_client *tcpwrap_client = NULL;
+
+static void tcpwrap_client_destroy(struct tcpwrap_client **client);
+
+static void tcpwrap_client_handle(struct tcpwrap_client *client, int check_fd,
+                                 const char *daemon_name)
+{
+       struct request_info request;
+
+       request_init(&request, RQ_DAEMON, daemon_name,
+                    RQ_FILE, check_fd, 0);
+       fromhost(&request);
+
+       if (!hosts_access(&request))
+               (void)write_full(client->fd, "0\n", 2);
+       else
+               (void)write_full(client->fd, "1\n", 2);
+       exit(0);
+}
+
+static void tcpwrap_client_input(struct tcpwrap_client *client)
+{
+       unsigned char buf[1024];
+       ssize_t ret;
+       int check_fd = -1;
+
+       ret = fd_read(client->fd, buf, sizeof(buf), &check_fd);
+       if (ret <= 0) {
+               i_error("fd_read() failed: %m");
+       } else if (ret > 1 && (size_t)ret < sizeof(buf) && buf[ret-1] == '\n') {
+               tcpwrap_client_handle(client, check_fd, t_strndup(buf, ret-1));
+       } else {
+               i_error("Invalid input from client");
+       }
+
+       if (check_fd != -1) {
+               if (close(check_fd) < 0)
+                       i_error("close(fdread fd) failed: %m");
+       }
+       tcpwrap_client_destroy(&client);
+}
+
+static void tcpwrap_client_timeout(struct tcpwrap_client *client)
+{
+       tcpwrap_client_destroy(&client);
+}
+
+static struct tcpwrap_client *tcpwrap_client_create(int fd)
+{
+       struct tcpwrap_client *client;
+
+       client = i_new(struct tcpwrap_client, 1);
+       client->fd = fd;
+       client->io = io_add(fd, IO_READ, tcpwrap_client_input, client);
+       client->to = timeout_add(INPUT_TIMEOUT_MSECS, tcpwrap_client_timeout,
+                                client);
+       return client;
+}
+
+static void tcpwrap_client_destroy(struct tcpwrap_client **_client)
+{
+       struct tcpwrap_client *client = *_client;
+
+       *_client = NULL;
+
+       timeout_remove(&client->to);
+       io_remove(&client->io);
+       if (close(client->fd) < 0)
+               i_error("close() failed: %m");
+       i_free(client);
+
+       tcpwrap_client = NULL;
+       master_service_client_connection_destroyed(master_service);
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+       if (tcpwrap_client != NULL) {
+               i_error("tcpwrap must be configured with client_limit=1");
+               (void)close(conn->fd);
+               return;
+       }
+       tcpwrap_client = tcpwrap_client_create(conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+       master_service = master_service_init("tcpwrap", 0,
+                                            &argc, &argv, NULL);
+       if (master_getopt(master_service) > 0)
+               return FATAL_DEFAULT;
+
+       master_service_init_log(master_service, "tcpwrap: ");
+       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 (tcpwrap_client != NULL)
+               tcpwrap_client_destroy(&tcpwrap_client);
+
+       master_service_deinit(&master_service);
+        return 0;
+}