Mercurial > dovecot > core-2.2
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; +}