changeset 22444:280d78e7fec8

apparmor: Add apparmor plugin It lets dovecot temporarily switch to a new apparmor context for a user.
author Aki Tuomi <aki.tuomi@dovecot.fi>
date Thu, 13 Jul 2017 10:02:26 +0300
parents 6ad4c4203dba
children a8a699e23949
files configure.ac m4/want_apparmor.m4 src/plugins/Makefile.am src/plugins/apparmor/Makefile.am src/plugins/apparmor/apparmor-plugin.c
diffstat 5 files changed, 161 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Fri Aug 18 17:38:34 2017 +0300
+++ b/configure.ac	Thu Jul 13 10:02:26 2017 +0300
@@ -2923,6 +2923,8 @@
 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/imap-zlib/Makefile
 src/plugins/mail-crypt/Makefile
 src/plugins/var-expand-crypt/Makefile
+src/plugins/apparmor/Makefile
 stamp.h
 dovecot-config.in])
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m4/want_apparmor.m4	Thu Jul 13 10:02:26 2017 +0300
@@ -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")
+])
--- a/src/plugins/Makefile.am	Fri Aug 18 17:38:34 2017 +0300
+++ b/src/plugins/Makefile.am	Thu Jul 13 10:02:26 2017 +0300
@@ -14,6 +14,10 @@
 DICT_LDAP = dict-ldap
 endif
 
+if HAVE_APPARMOR
+APPARMOR = apparmor
+endif
+
 SUBDIRS = \
 	acl \
 	imap-acl \
@@ -45,5 +49,6 @@
 	$(FTS_LUCENE) \
 	$(FTS_SOLR) \
 	$(DICT_LDAP) \
+	$(APPARMOR) \
 	fs-compress \
 	var-expand-crypt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/apparmor/Makefile.am	Thu Jul 13 10:02:26 2017 +0300
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/apparmor/apparmor-plugin.c	Thu Jul 13 10:02:26 2017 +0300
@@ -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 <sys/apparmor.h>
+
+#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);
+}