changeset 10647:51a978045f47 HEAD

Added support for tcpwrappers and potentially other login access checks.
author Timo Sirainen <tss@iki.fi>
date Sat, 06 Feb 2010 19:46:40 +0200
parents d4fd28f07507
children 518572a630a8
files .hgignore configure.in doc/example-config/dovecot.conf src/login-common/Makefile.am src/login-common/access-lookup.c src/login-common/access-lookup.h 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 src/util/tcpwrap.c
diffstat 12 files changed, 434 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Feb 06 17:31:14 2010 +0200
+++ b/.hgignore	Sat Feb 06 19:46:40 2010 +0200
@@ -60,6 +60,7 @@
 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/maildirlock
 src/util/rawlog
 src/util/script
+src/util/tcpwrap
 src/plugins/quota/rquota_xdr.c
 src/plugins/quota/rquota.h
 
--- a/configure.in	Sat Feb 06 17:31:14 2010 +0200
+++ b/configure.in	Sat Feb 06 19:46:40 2010 +0200
@@ -169,6 +169,11 @@
   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 @@
   ])
 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
--- a/doc/example-config/dovecot.conf	Sat Feb 06 17:31:14 2010 +0200
+++ b/doc/example-config/dovecot.conf	Sat Feb 06 19:46:40 2010 +0200
@@ -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).
--- a/src/login-common/Makefile.am	Sat Feb 06 17:31:14 2010 +0200
+++ b/src/login-common/Makefile.am	Sat Feb 06 19:46:40 2010 +0200
@@ -9,6 +9,7 @@
 	-DPKG_STATEDIR=\""$(statedir)"\"
 
 liblogin_la_SOURCES = \
+	access-lookup.c \
 	client-common.c \
 	client-common-auth.c \
 	login-proxy.c \
@@ -24,6 +25,7 @@
 	$(SSL_LIBS)
 
 headers = \
+	access-lookup.h \
 	client-common.h \
 	login-common.h \
 	login-proxy.h \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login-common/access-lookup.c	Sat Feb 06 19:46:40 2010 +0200
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login-common/access-lookup.h	Sat Feb 06 19:46:40 2010 +0200
@@ -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
--- a/src/login-common/login-settings.c	Sat Feb 06 17:31:14 2010 +0200
+++ b/src/login-common/login-settings.c	Sat Feb 06 19:46:40 2010 +0200
@@ -21,6 +21,7 @@
 	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 @@
 	.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 = "",
--- a/src/login-common/login-settings.h	Sat Feb 06 17:31:14 2010 +0200
+++ b/src/login-common/login-settings.h	Sat Feb 06 19:46:40 2010 +0200
@@ -7,6 +7,7 @@
 	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;
--- a/src/login-common/main.c	Sat Feb 06 17:31:14 2010 +0200
+++ b/src/login-common/main.c	Sat Feb 06 19:46:40 2010 +0200
@@ -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"
@@ -19,6 +20,14 @@
 #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 @@
 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 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 @@
 		if (fd_ssl == -1) {
 			net_disconnect(conn->fd);
 			pool_unref(&pool);
+			master_service_client_connection_destroyed(master_service);
 			return;
 		}
 
@@ -105,6 +118,91 @@
 	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)
 {
--- a/src/util/Makefile.am	Sat Feb 06 17:31:14 2010 +0200
+++ b/src/util/Makefile.am	Sat Feb 06 19:46:40 2010 +0200
@@ -3,6 +3,7 @@
 pkglibexec_PROGRAMS = \
 	rawlog \
 	script \
+	$(TCPWRAP_BIN) \
 	gdbhelper \
 	imap-utf7 \
 	listview \
@@ -32,6 +33,15 @@
 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 = \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util/tcpwrap-settings.c	Sat Feb 06 19:46:40 2010 +0200
@@ -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
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util/tcpwrap.c	Sat Feb 06 19:46:40 2010 +0200
@@ -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;
+}