From: Timo Sirainen Date: Sat, 6 Feb 2010 17:46:40 +0000 (+0200) Subject: Added support for tcpwrappers and potentially other login access checks. X-Git-Tag: 2.0.beta3~151 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=79977d368df2efc3f3dc0bbb260748b076538103;p=thirdparty%2Fdovecot%2Fcore.git Added support for tcpwrappers and potentially other login access checks. --HG-- branch : HEAD --- diff --git a/.hgignore b/.hgignore index ab9c34fbcd..934bc94fba 100644 --- 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 diff --git a/configure.in b/configure.in index 3128502100..6deeeabb96 100644 --- a/configure.in +++ b/configure.in @@ -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 diff --git a/doc/example-config/dovecot.conf b/doc/example-config/dovecot.conf index 834b0307cb..7aa56c3373 100644 --- a/doc/example-config/dovecot.conf +++ b/doc/example-config/dovecot.conf @@ -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). diff --git a/src/login-common/Makefile.am b/src/login-common/Makefile.am index 7102442cbb..ba7b99d35f 100644 --- a/src/login-common/Makefile.am +++ b/src/login-common/Makefile.am @@ -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 index 0000000000..11f7465607 --- /dev/null +++ b/src/login-common/access-lookup.c @@ -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 + +#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 index 0000000000..6ebb5824bd --- /dev/null +++ b/src/login-common/access-lookup.h @@ -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 diff --git a/src/login-common/login-settings.c b/src/login-common/login-settings.c index 2d97c120b0..6970b30341 100644 --- a/src/login-common/login-settings.c +++ b/src/login-common/login-settings.c @@ -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 = "", diff --git a/src/login-common/login-settings.h b/src/login-common/login-settings.h index 2d57b8e2fb..b144f8d7fd 100644 --- a/src/login-common/login-settings.h +++ b/src/login-common/login-settings.h @@ -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; diff --git a/src/login-common/main.c b/src/login-common/main.c index 6ac9f8bb9c..b585ec825a 100644 --- a/src/login-common/main.c +++ b/src/login-common/main.c @@ -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 #include +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) { diff --git a/src/util/Makefile.am b/src/util/Makefile.am index e54d4c540e..3ffe7078dc 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -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 index 0000000000..9bb62b394c --- /dev/null +++ b/src/util/tcpwrap-settings.c @@ -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 + +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 index 0000000000..e6d0a7ec1e --- /dev/null +++ b/src/util/tcpwrap.c @@ -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 +#include +#include + +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; +}