diff --git a/configure.ac b/configure.ac index 055acd5d86..c514ecae16 100644 --- a/configure.ac +++ b/configure.ac @@ -2923,6 +2923,8 @@ if test "$want_icu" != "no"; then fi AM_CONDITIONAL(BUILD_LIBICU, test "$have_icu" = "yes") +DOVECOT_WANT_APPARMOR + if test $have_lucene = no; then not_fts="$not_fts lucene" fi @@ -3086,6 +3088,7 @@ src/plugins/zlib/Makefile src/plugins/imap-zlib/Makefile src/plugins/mail-crypt/Makefile src/plugins/var-expand-crypt/Makefile +src/plugins/apparmor/Makefile stamp.h dovecot-config.in]) diff --git a/m4/want_apparmor.m4 b/m4/want_apparmor.m4 new file mode 100644 index 0000000000..6934ea79c3 --- /dev/null +++ b/m4/want_apparmor.m4 @@ -0,0 +1,24 @@ +AC_DEFUN([DOVECOT_WANT_APPARMOR], [ + want_apparmor=auto + AC_ARG_WITH([apparmor], + [AS_HELP_STRING([--with-apparmor], [enable apparmor plugin (default=auto)])], + [want_apparmor=$withval]) + + have_apparmor=no + if test $want_apparmor != no; then + AC_CHECK_HEADER([sys/apparmor.h], [ + AC_CHECK_LIB([apparmor], [aa_change_hat], [ + have_apparmor=yes + AC_SUBST([APPARMOR_LIBS], [-lapparmor]) + ]) + ]) + fi + + if test $want_apparmor = yes; then + if test $have_apparmor = no; then + AC_MSG_FAILURE([apparmor was not found]) + fi + fi + + AM_CONDITIONAL(HAVE_APPARMOR, test "$have_apparmor" = "yes") +]) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 4565163f71..c341f6ac0c 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -14,6 +14,10 @@ if HAVE_LDAP DICT_LDAP = dict-ldap endif +if HAVE_APPARMOR +APPARMOR = apparmor +endif + SUBDIRS = \ acl \ imap-acl \ @@ -45,5 +49,6 @@ SUBDIRS = \ $(FTS_LUCENE) \ $(FTS_SOLR) \ $(DICT_LDAP) \ + $(APPARMOR) \ fs-compress \ var-expand-crypt diff --git a/src/plugins/apparmor/Makefile.am b/src/plugins/apparmor/Makefile.am new file mode 100644 index 0000000000..510053bbff --- /dev/null +++ b/src/plugins/apparmor/Makefile.am @@ -0,0 +1,14 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +NOPLUGIN_LDFLAGS = +lib01_apparmor_plugin_la_LDFLAGS = -module -avoid-version +lib01_apparmor_plugin_la_LIBADD = $(APPARMOR_LIBS) +lib01_apparmor_plugin_la_SOURCES = \ + apparmor-plugin.c + +module_LTLIBRARIES = \ + lib01_apparmor_plugin.la diff --git a/src/plugins/apparmor/apparmor-plugin.c b/src/plugins/apparmor/apparmor-plugin.c new file mode 100644 index 0000000000..f42a58b01b --- /dev/null +++ b/src/plugins/apparmor/apparmor-plugin.c @@ -0,0 +1,115 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "module-dir.h" +#include "randgen.h" +#include "mail-user.h" +#include "mail-storage-private.h" +#include "mail-storage-hooks.h" +#include + +#define APPARMOR_PLUGIN_SETTING_HAT_PREFIX "apparmor_hat" + +const char *apparmor_plugin_version = DOVECOT_ABI_VERSION; + +/* hooks into user creation and deinit, will try to use + hats provided by apparmor_hat, apparmor_hat1... etc */ + +#define APPARMOR_USER_CONTEXT(obj) \ + (struct apparmor_mail_user*)MODULE_CONTEXT(obj, apparmor_mail_user_module) + +static MODULE_CONTEXT_DEFINE_INIT(apparmor_mail_user_module, + &mail_user_module_register); + +struct apparmor_mail_user { + union mail_user_module_context module_ctx; + unsigned long token; +}; + +void apparmor_plugin_init(struct module*); +void apparmor_plugin_deinit(void); + +static void apparmor_log_current_context(struct mail_user *user) +{ + char *con, *mode; + if (!user->mail_debug) + return; + + if (aa_getcon(&con, &mode) < 0) { + i_debug("aa_getcon() failed: %m"); + } else { + i_debug("apparmor: Current context=%s, mode=%s", + con, mode); + free(con); + } +} + +static void apparmor_mail_user_deinit(struct mail_user *user) +{ + struct apparmor_mail_user *auser = APPARMOR_USER_CONTEXT(user); + + if (user == NULL) + return; + + if (aa_change_hat(NULL, auser->token)<0) + i_fatal("aa_change_hat(NULL) failed: %m"); + + apparmor_log_current_context(user); +} + +static void apparmor_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct apparmor_mail_user *auser; + ARRAY_TYPE(const_string) hats; + /* see if we can find any hats */ + const char *hat = + mail_user_plugin_getenv(user, APPARMOR_PLUGIN_SETTING_HAT_PREFIX); + if (hat == NULL) + return; + + t_array_init(&hats, 8); + array_append(&hats, &hat, 1); + for(unsigned int i = 2;; i++) { + hat = mail_user_plugin_getenv(user, t_strdup_printf("%s%u", + APPARMOR_PLUGIN_SETTING_HAT_PREFIX, i)); + if (hat == NULL) break; + array_append(&hats, &hat, 1); + } + array_append_zero(&hats); + + /* we got hat(s) to try */ + auser = p_new(user->pool, struct apparmor_mail_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + v->deinit = apparmor_mail_user_deinit; + MODULE_CONTEXT_SET(user, apparmor_mail_user_module, auser); + + /* generate a magic token */ + random_fill(&auser->token, sizeof(auser->token)); + + /* try change hat */ + if (aa_change_hatv(array_idx_modifiable(&hats, 0), auser->token) < 0) { + i_fatal("aa_change_hatv(%s) failed: %m", + t_array_const_string_join(&hats, ",")); + } + + apparmor_log_current_context(user); +} + +static const struct mail_storage_hooks apparmor_hooks = { + .mail_user_created = apparmor_mail_user_created +}; + +void apparmor_plugin_init(struct module *module) +{ + random_init(); + mail_storage_hooks_add(module, &apparmor_hooks); +} + +void apparmor_plugin_deinit(void) +{ + random_deinit(); + mail_storage_hooks_remove(&apparmor_hooks); +}