diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fc25f0..0c03567 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,52 @@
 # Change Log
 
+## 1.1.1 - 2022-03-15
+
+### Fixed
+- Restore support for access control filenames without a group
+
+## 1.1.0 - 2022-02-24
+
+### Added
+- Started building with C++17
+- Tree-like list-devices output
+- Added CAP_AUDIT_WRITE capability to service file
+- Added support for lower OpenSSL versions prior to 1.1.0
+- Added a new signal: DevicePolicyApplied
+
+### Fixed/Changed
+- Moved PIDFile from /var/run to /run
+- Fixed linker isssues with disable-static
+- Enhanced bash-completion script
+- Make username/group checking consistent with useradd manual page definition 
+  (with addition of capital letters)
+- Fixed multiple IPC related bugs
+- Fixed race condition when accessing port/connect_type for USB devices
+- Using bundled catch v2.13.8 
+- Using bundled PEGTL v3.2.5
+- Fixed usbguard-rule-parser file opening
+- Fix unauthorized access via D-Bus [CVE-2019-25058]
+
+
+## 1.0.0 - 2021-01-13
+
+### Added
+- Added openssl support
+- Starting with libtool versioning
+- Added interface for IPC permission query
+- Introduced partial rule concept fo CLI
+- Added WithConnectType for ldap rule
+
+### Fixed/Changed
+- Daemon does not apply the policy when
+  "change" action event appears anymore
+- IPCClientPrivate@disconnect is thread safe
+- Enforced loading of files from .d/ directory
+  in alphabetical order
+- Improved CLI behaviour to be consistent
+- Clarified rule's label documentation
+
+
 ## 0.7.8 - 2020-05-20
 
 ### Fixed
@@ -141,7 +188,7 @@
 
 ### Changed
 - Qt Applet: disabled session management
-- usbguard-daemon console logging output is enabled by default now.   Previously,
+- usbguard-daemon console logging output is enabled by default now. Previously,
   the -k option had to be passed to enable the output.
 - Replaced --enable-maintainer-mode configure option with --enable-full-test-suite
   option. When the new option is not used during the configure phase, only a basic
@@ -152,7 +199,7 @@
 - Reformatted source code to conform to the code style.
 - Made the configuration parser strict. Unknown directives and wrong syntax will
   cause an error.
-- Reformated documentation from markdown to asciidoc format.
+- Reformatted documentation from markdown to asciidoc format.
 
 ## 0.7.0 - 2017-04-12
 ### Added
@@ -181,7 +228,7 @@
 
 ### Removed
 - Removed UDev based device manager backend and UDev related dependencies.
-- Removed UDev development files/API dependecy
+- Removed UDev development files/API dependency
 
 ### Changed
 - Reset Linux root hub bcdDevice value before updating device hash. This is
@@ -442,4 +489,3 @@
 
 ### Fixed
 - Resolved issues: #1 #2 #5 #6 #10 #11
-
diff --git a/LICENSE b/LICENSE
index d6a9326..d159169 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
-GNU GENERAL PUBLIC LICENSE
+                    GNU GENERAL PUBLIC LICENSE
                        Version 2, June 1991
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
@@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
 convey the exclusion of warranty; and each file should have at least
 the "copyright" line and a pointer to where the full notice is found.
 
-    {description}
-    Copyright (C) {year}  {fullname}
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -329,7 +329,7 @@ necessary.  Here is a sample; alter the names:
   Yoyodyne, Inc., hereby disclaims all copyright interest in the program
   `Gnomovision' (which makes passes at compilers) written by James Hacker.
 
-  {signature of Ty Coon}, 1 April 1989
+  <signature of Ty Coon>, 1 April 1989
   Ty Coon, President of Vice
 
 This General Public License does not permit incorporating your program into
@@ -337,4 +337,3 @@ proprietary programs.  If your program is a subroutine library, you may
 consider it more useful to permit linking proprietary applications with the
 library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.
-
diff --git a/Makefile.am b/Makefile.am
index 8673dc4..f4ce03d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,6 +16,7 @@
 ##
 ## Authors: Daniel Kopecek <dkopecek@redhat.com>
 ##          Jiri Vymazal   <jvymazal@redhat.com>
+##          Attila Lakatos <alakatos@redhat.com>
 ##
 SUBDIRS=src/Tests/
 
@@ -27,6 +28,7 @@ EXTRA_DIST =\
 	VERSION \
 	CHANGELOG.md \
 	src/astylerc \
+	src/test_filesystem.cpp \
 	scripts/usbguard-zsh-completion \
 	scripts/modeline.vim \
 	scripts/astyle.sh \
@@ -50,6 +52,11 @@ $(top_builddir)/src/build-config.h: $(top_builddir)/src/build-config.h.in
 DISTCLEANFILES=\
 	$(BUILT_SOURCES)
 
+AM_DISTCHECK_CONFIGURE_FLAGS=\
+	--enable-full-test-suite \
+	--with-bundled-catch \
+	--with-bundled-pegtl
+
 CLEANFILES=
 
 man_ADOC_FILES=\
@@ -166,6 +173,7 @@ libusbguard_la_CPPFLAGS=\
 	-I$(top_srcdir)/src/Library/public \
 	-I$(top_builddir)/src/Library/IPC \
 	${BOOST_CPPFLAGS} \
+	${PTHREAD_CPPFLAGS} \
 	@qb_CFLAGS@ \
 	@protobuf_CFLAGS@ \
 	@crypto_CFLAGS@ \
@@ -184,7 +192,9 @@ libusbguard_la_LIBADD=\
 	@pegtl_LIBS@ \
 	@atomic_LIBS@ \
 	@umockdev_LIBS@ \
-	${BOOST_IOSTREAMS_LIB}
+	${BOOST_IOSTREAMS_LIB} \
+	${PTHREAD_CFLAGS} \
+	${PTHREAD_LIBS}
 
 EXTRA_DIST+=\
 	src/Library/IPC/Devices.proto \
@@ -228,6 +238,10 @@ libusbguard_la_SOURCES=\
 	src/Library/Base64.hpp \
 	src/Library/ConfigFilePrivate.cpp \
 	src/Library/ConfigFilePrivate.hpp \
+	src/Library/DeviceBase.cpp \
+	src/Library/DeviceBase.hpp \
+	src/Library/DeviceManagerBase.cpp \
+	src/Library/DeviceManagerBase.hpp \
 	src/Library/DeviceManagerPrivate.cpp \
 	src/Library/DeviceManagerPrivate.hpp \
 	src/Library/DevicePrivate.cpp \
@@ -391,6 +405,8 @@ usbguard_SOURCES=\
 	src/CLI/usbguard-block-device.cpp \
 	src/CLI/usbguard-reject-device.hpp \
 	src/CLI/usbguard-reject-device.cpp \
+	src/CLI/usbguard-apply-device-policy.hpp \
+	src/CLI/usbguard-apply-device-policy.cpp \
 	src/CLI/usbguard-list-rules.hpp \
 	src/CLI/usbguard-list-rules.cpp \
 	src/CLI/usbguard-append-rule.hpp \
@@ -428,7 +444,7 @@ usbguard_LDADD=\
 	$(top_builddir)/libusbguard.la \
 	${PTHREAD_LIBS}
 
-if BASH_COMPLETION_ENABLED
+if ENABLE_BASH_COMPLETION
 bashcompletiondir = $(BASH_COMPLETION_DIR)
 dist_bashcompletion_DATA = $(top_srcdir)/scripts/bash_completion/usbguard
 endif
diff --git a/Makefile.in b/Makefile.in
index 0842dc5..abfc039 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# Makefile.in generated by automake 1.16.2 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -177,13 +177,17 @@ am__uninstall_files_from_dir = { \
          $(am__cd) "$$dir" && rm -f $$files; }; \
   }
 LTLIBRARIES = $(lib_LTLIBRARIES)
-libusbguard_la_DEPENDENCIES =
+am__DEPENDENCIES_1 =
+libusbguard_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1)
 am__dirstamp = $(am__leading_dot)dirstamp
 am_libusbguard_la_OBJECTS = src/Common/libusbguard_la-Utility.lo \
 	src/Common/libusbguard_la-LDAPUtil.lo \
 	src/Library/libusbguard_la-AllowedMatchesCondition.lo \
 	src/Library/libusbguard_la-Base64.lo \
 	src/Library/libusbguard_la-ConfigFilePrivate.lo \
+	src/Library/libusbguard_la-DeviceBase.lo \
+	src/Library/libusbguard_la-DeviceManagerBase.lo \
 	src/Library/libusbguard_la-DeviceManagerPrivate.lo \
 	src/Library/libusbguard_la-DevicePrivate.lo \
 	src/Library/libusbguard_la-FixedStateCondition.lo \
@@ -245,6 +249,7 @@ am_usbguard_OBJECTS = src/CLI/usbguard-usbguard.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-allow-device.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-block-device.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-reject-device.$(OBJEXT) \
+	src/CLI/usbguard-usbguard-apply-device-policy.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-list-rules.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-append-rule.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-remove-rule.$(OBJEXT) \
@@ -258,7 +263,6 @@ am_usbguard_OBJECTS = src/CLI/usbguard-usbguard.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-add-user.$(OBJEXT) \
 	src/CLI/usbguard-usbguard-remove-user.$(OBJEXT)
 usbguard_OBJECTS = $(am_usbguard_OBJECTS)
-am__DEPENDENCIES_1 =
 usbguard_DEPENDENCIES = $(top_builddir)/libusbguard.la \
 	$(am__DEPENDENCIES_1)
 usbguard_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
@@ -314,6 +318,7 @@ am__depfiles_remade = src/CLI/$(DEPDIR)/usbguard-IPCSignalWatcher.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-add-user.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-allow-device.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-append-rule.Po \
+	src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-block-device.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-generate-policy.Po \
 	src/CLI/$(DEPDIR)/usbguard-usbguard-get-parameter.Po \
@@ -345,6 +350,8 @@ am__depfiles_remade = src/CLI/$(DEPDIR)/usbguard-IPCSignalWatcher.Po \
 	src/Library/$(DEPDIR)/libusbguard_la-AllowedMatchesCondition.Plo \
 	src/Library/$(DEPDIR)/libusbguard_la-Base64.Plo \
 	src/Library/$(DEPDIR)/libusbguard_la-ConfigFilePrivate.Plo \
+	src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Plo \
+	src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Plo \
 	src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Plo \
 	src/Library/$(DEPDIR)/libusbguard_la-DevicePrivate.Plo \
 	src/Library/$(DEPDIR)/libusbguard_la-FixedStateCondition.Plo \
@@ -675,6 +682,8 @@ ldap_CFLAGS = @ldap_CFLAGS@
 ldap_LIBS = @ldap_LIBS@
 libcapng_CFLAGS = @libcapng_CFLAGS@
 libcapng_LIBS = @libcapng_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
 libdir = @libdir@
 libexecdir = @libexecdir@
 localedir = @localedir@
@@ -695,6 +704,7 @@ protobuf_LIBS = @protobuf_LIBS@
 psdir = @psdir@
 qb_CFLAGS = @qb_CFLAGS@
 qb_LIBS = @qb_LIBS@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 seccomp_CFLAGS = @seccomp_CFLAGS@
 seccomp_LIBS = @seccomp_LIBS@
@@ -712,7 +722,7 @@ umockdev_LIBS = @umockdev_LIBS@
 SUBDIRS = src/Tests/
 ACLOCAL_AMFLAGS = -I m4
 EXTRA_DIST = LICENSE usbguard-daemon.conf.in usbguard.service.in \
-	VERSION CHANGELOG.md src/astylerc \
+	VERSION CHANGELOG.md src/astylerc src/test_filesystem.cpp \
 	scripts/usbguard-zsh-completion scripts/modeline.vim \
 	scripts/astyle.sh scripts/reformat-sources.sh \
 	scripts/usb-descriptor-collect.sh $(man_ADOC_FILES) \
@@ -726,6 +736,11 @@ BUILT_SOURCES = src/build-config.h $(am__append_1) \
 DISTCLEANFILES = \
 	$(BUILT_SOURCES)
 
+AM_DISTCHECK_CONFIGURE_FLAGS = \
+	--enable-full-test-suite \
+	--with-bundled-catch \
+	--with-bundled-pegtl
+
 CLEANFILES = $(man_ROFF_FILES) $(man_ROFF_FILES:.roff=) \
 	$(top_builddir)/usbguard-daemon.conf $(am__append_2) \
 	$(nodist_libusbguard_la_SOURCES) $(am__append_7)
@@ -771,6 +786,7 @@ libusbguard_la_CPPFLAGS = \
 	-I$(top_srcdir)/src/Library/public \
 	-I$(top_builddir)/src/Library/IPC \
 	${BOOST_CPPFLAGS} \
+	${PTHREAD_CPPFLAGS} \
 	@qb_CFLAGS@ \
 	@protobuf_CFLAGS@ \
 	@crypto_CFLAGS@ \
@@ -789,7 +805,9 @@ libusbguard_la_LIBADD = \
 	@pegtl_LIBS@ \
 	@atomic_LIBS@ \
 	@umockdev_LIBS@ \
-	${BOOST_IOSTREAMS_LIB}
+	${BOOST_IOSTREAMS_LIB} \
+	${PTHREAD_CFLAGS} \
+	${PTHREAD_LIBS}
 
 nodist_libusbguard_la_SOURCES = \
 	src/Library/IPC/Message.pb.cc \
@@ -819,6 +837,10 @@ libusbguard_la_SOURCES = \
 	src/Library/Base64.hpp \
 	src/Library/ConfigFilePrivate.cpp \
 	src/Library/ConfigFilePrivate.hpp \
+	src/Library/DeviceBase.cpp \
+	src/Library/DeviceBase.hpp \
+	src/Library/DeviceManagerBase.cpp \
+	src/Library/DeviceManagerBase.hpp \
 	src/Library/DeviceManagerPrivate.cpp \
 	src/Library/DeviceManagerPrivate.hpp \
 	src/Library/DevicePrivate.cpp \
@@ -957,6 +979,8 @@ usbguard_SOURCES = \
 	src/CLI/usbguard-block-device.cpp \
 	src/CLI/usbguard-reject-device.hpp \
 	src/CLI/usbguard-reject-device.cpp \
+	src/CLI/usbguard-apply-device-policy.hpp \
+	src/CLI/usbguard-apply-device-policy.cpp \
 	src/CLI/usbguard-list-rules.hpp \
 	src/CLI/usbguard-list-rules.cpp \
 	src/CLI/usbguard-append-rule.hpp \
@@ -994,8 +1018,8 @@ usbguard_LDADD = \
 	$(top_builddir)/libusbguard.la \
 	${PTHREAD_LIBS}
 
-@BASH_COMPLETION_ENABLED_TRUE@bashcompletiondir = $(BASH_COMPLETION_DIR)
-@BASH_COMPLETION_ENABLED_TRUE@dist_bashcompletion_DATA = $(top_srcdir)/scripts/bash_completion/usbguard
+@ENABLE_BASH_COMPLETION_TRUE@bashcompletiondir = $(BASH_COMPLETION_DIR)
+@ENABLE_BASH_COMPLETION_TRUE@dist_bashcompletion_DATA = $(top_srcdir)/scripts/bash_completion/usbguard
 usbguard_rule_parser_SOURCES = \
 	src/CLI/usbguard-rule-parser.cpp
 
@@ -1234,6 +1258,11 @@ src/Library/libusbguard_la-Base64.lo: src/Library/$(am__dirstamp) \
 src/Library/libusbguard_la-ConfigFilePrivate.lo:  \
 	src/Library/$(am__dirstamp) \
 	src/Library/$(DEPDIR)/$(am__dirstamp)
+src/Library/libusbguard_la-DeviceBase.lo: src/Library/$(am__dirstamp) \
+	src/Library/$(DEPDIR)/$(am__dirstamp)
+src/Library/libusbguard_la-DeviceManagerBase.lo:  \
+	src/Library/$(am__dirstamp) \
+	src/Library/$(DEPDIR)/$(am__dirstamp)
 src/Library/libusbguard_la-DeviceManagerPrivate.lo:  \
 	src/Library/$(am__dirstamp) \
 	src/Library/$(DEPDIR)/$(am__dirstamp)
@@ -1395,6 +1424,8 @@ src/CLI/usbguard-usbguard-block-device.$(OBJEXT):  \
 	src/CLI/$(am__dirstamp) src/CLI/$(DEPDIR)/$(am__dirstamp)
 src/CLI/usbguard-usbguard-reject-device.$(OBJEXT):  \
 	src/CLI/$(am__dirstamp) src/CLI/$(DEPDIR)/$(am__dirstamp)
+src/CLI/usbguard-usbguard-apply-device-policy.$(OBJEXT):  \
+	src/CLI/$(am__dirstamp) src/CLI/$(DEPDIR)/$(am__dirstamp)
 src/CLI/usbguard-usbguard-list-rules.$(OBJEXT):  \
 	src/CLI/$(am__dirstamp) src/CLI/$(DEPDIR)/$(am__dirstamp)
 src/CLI/usbguard-usbguard-append-rule.$(OBJEXT):  \
@@ -1508,6 +1539,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-add-user.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-allow-device.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-append-rule.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-block-device.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-generate-policy.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/CLI/$(DEPDIR)/usbguard-usbguard-get-parameter.Po@am__quote@ # am--include-marker
@@ -1539,6 +1571,8 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-AllowedMatchesCondition.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-Base64.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-ConfigFilePrivate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-DevicePrivate.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@src/Library/$(DEPDIR)/libusbguard_la-FixedStateCondition.Plo@am__quote@ # am--include-marker
@@ -1688,6 +1722,20 @@ src/Library/libusbguard_la-ConfigFilePrivate.lo: src/Library/ConfigFilePrivate.c
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/Library/libusbguard_la-ConfigFilePrivate.lo `test -f 'src/Library/ConfigFilePrivate.cpp' || echo '$(srcdir)/'`src/Library/ConfigFilePrivate.cpp
 
+src/Library/libusbguard_la-DeviceBase.lo: src/Library/DeviceBase.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/Library/libusbguard_la-DeviceBase.lo -MD -MP -MF src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Tpo -c -o src/Library/libusbguard_la-DeviceBase.lo `test -f 'src/Library/DeviceBase.cpp' || echo '$(srcdir)/'`src/Library/DeviceBase.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Tpo src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='src/Library/DeviceBase.cpp' object='src/Library/libusbguard_la-DeviceBase.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/Library/libusbguard_la-DeviceBase.lo `test -f 'src/Library/DeviceBase.cpp' || echo '$(srcdir)/'`src/Library/DeviceBase.cpp
+
+src/Library/libusbguard_la-DeviceManagerBase.lo: src/Library/DeviceManagerBase.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/Library/libusbguard_la-DeviceManagerBase.lo -MD -MP -MF src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Tpo -c -o src/Library/libusbguard_la-DeviceManagerBase.lo `test -f 'src/Library/DeviceManagerBase.cpp' || echo '$(srcdir)/'`src/Library/DeviceManagerBase.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Tpo src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='src/Library/DeviceManagerBase.cpp' object='src/Library/libusbguard_la-DeviceManagerBase.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o src/Library/libusbguard_la-DeviceManagerBase.lo `test -f 'src/Library/DeviceManagerBase.cpp' || echo '$(srcdir)/'`src/Library/DeviceManagerBase.cpp
+
 src/Library/libusbguard_la-DeviceManagerPrivate.lo: src/Library/DeviceManagerPrivate.cpp
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libusbguard_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT src/Library/libusbguard_la-DeviceManagerPrivate.lo -MD -MP -MF src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Tpo -c -o src/Library/libusbguard_la-DeviceManagerPrivate.lo `test -f 'src/Library/DeviceManagerPrivate.cpp' || echo '$(srcdir)/'`src/Library/DeviceManagerPrivate.cpp
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Tpo src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Plo
@@ -2073,6 +2121,20 @@ src/CLI/usbguard-usbguard-reject-device.obj: src/CLI/usbguard-reject-device.cpp
 @AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -c -o src/CLI/usbguard-usbguard-reject-device.obj `if test -f 'src/CLI/usbguard-reject-device.cpp'; then $(CYGPATH_W) 'src/CLI/usbguard-reject-device.cpp'; else $(CYGPATH_W) '$(srcdir)/src/CLI/usbguard-reject-device.cpp'; fi`
 
+src/CLI/usbguard-usbguard-apply-device-policy.o: src/CLI/usbguard-apply-device-policy.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -MT src/CLI/usbguard-usbguard-apply-device-policy.o -MD -MP -MF src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Tpo -c -o src/CLI/usbguard-usbguard-apply-device-policy.o `test -f 'src/CLI/usbguard-apply-device-policy.cpp' || echo '$(srcdir)/'`src/CLI/usbguard-apply-device-policy.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Tpo src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='src/CLI/usbguard-apply-device-policy.cpp' object='src/CLI/usbguard-usbguard-apply-device-policy.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -c -o src/CLI/usbguard-usbguard-apply-device-policy.o `test -f 'src/CLI/usbguard-apply-device-policy.cpp' || echo '$(srcdir)/'`src/CLI/usbguard-apply-device-policy.cpp
+
+src/CLI/usbguard-usbguard-apply-device-policy.obj: src/CLI/usbguard-apply-device-policy.cpp
+@am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -MT src/CLI/usbguard-usbguard-apply-device-policy.obj -MD -MP -MF src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Tpo -c -o src/CLI/usbguard-usbguard-apply-device-policy.obj `if test -f 'src/CLI/usbguard-apply-device-policy.cpp'; then $(CYGPATH_W) 'src/CLI/usbguard-apply-device-policy.cpp'; else $(CYGPATH_W) '$(srcdir)/src/CLI/usbguard-apply-device-policy.cpp'; fi`
+@am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Tpo src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	$(AM_V_CXX)source='src/CLI/usbguard-apply-device-policy.cpp' object='src/CLI/usbguard-usbguard-apply-device-policy.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@	DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@	$(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -c -o src/CLI/usbguard-usbguard-apply-device-policy.obj `if test -f 'src/CLI/usbguard-apply-device-policy.cpp'; then $(CYGPATH_W) 'src/CLI/usbguard-apply-device-policy.cpp'; else $(CYGPATH_W) '$(srcdir)/src/CLI/usbguard-apply-device-policy.cpp'; fi`
+
 src/CLI/usbguard-usbguard-list-rules.o: src/CLI/usbguard-list-rules.cpp
 @am__fastdepCXX_TRUE@	$(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(usbguard_CPPFLAGS) $(CPPFLAGS) $(usbguard_CXXFLAGS) $(CXXFLAGS) -MT src/CLI/usbguard-usbguard-list-rules.o -MD -MP -MF src/CLI/$(DEPDIR)/usbguard-usbguard-list-rules.Tpo -c -o src/CLI/usbguard-usbguard-list-rules.o `test -f 'src/CLI/usbguard-list-rules.cpp' || echo '$(srcdir)/'`src/CLI/usbguard-list-rules.cpp
 @am__fastdepCXX_TRUE@	$(AM_V_at)$(am__mv) src/CLI/$(DEPDIR)/usbguard-usbguard-list-rules.Tpo src/CLI/$(DEPDIR)/usbguard-usbguard-list-rules.Po
@@ -2835,6 +2897,10 @@ dist-xz: distdir
 	tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
 	$(am__post_remove_distdir)
 
+dist-zstd: distdir
+	tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst
+	$(am__post_remove_distdir)
+
 dist-tarZ: distdir
 	@echo WARNING: "Support for distribution archives compressed with" \
 		       "legacy program 'compress' is deprecated." >&2
@@ -2877,6 +2943,8 @@ distcheck: dist
 	  eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
 	*.zip*) \
 	  unzip $(distdir).zip ;;\
+	*.tar.zst*) \
+	  zstd -dc $(distdir).tar.zst | $(am__untar) ;;\
 	esac
 	chmod -R a-w $(distdir)
 	chmod u+w $(distdir)
@@ -3014,6 +3082,7 @@ distclean: distclean-recursive
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-add-user.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-allow-device.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-append-rule.Po
+	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-block-device.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-generate-policy.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-get-parameter.Po
@@ -3045,6 +3114,8 @@ distclean: distclean-recursive
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-AllowedMatchesCondition.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-Base64.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-ConfigFilePrivate.Plo
+	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Plo
+	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DevicePrivate.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-FixedStateCondition.Plo
@@ -3145,6 +3216,7 @@ maintainer-clean: maintainer-clean-recursive
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-add-user.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-allow-device.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-append-rule.Po
+	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-apply-device-policy.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-block-device.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-generate-policy.Po
 	-rm -f src/CLI/$(DEPDIR)/usbguard-usbguard-get-parameter.Po
@@ -3176,6 +3248,8 @@ maintainer-clean: maintainer-clean-recursive
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-AllowedMatchesCondition.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-Base64.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-ConfigFilePrivate.Plo
+	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceBase.Plo
+	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerBase.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DeviceManagerPrivate.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-DevicePrivate.Plo
 	-rm -f src/Library/$(DEPDIR)/libusbguard_la-FixedStateCondition.Plo
@@ -3253,26 +3327,26 @@ uninstall-man: uninstall-man1 uninstall-man5 uninstall-man8
 	clean-libLTLIBRARIES clean-libtool clean-sbinPROGRAMS cscope \
 	cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \
 	dist-gzip dist-lzip dist-shar dist-tarZ dist-xz dist-zip \
-	distcheck distclean distclean-compile distclean-generic \
-	distclean-hdr distclean-libtool distclean-tags distcleancheck \
-	distdir distuninstallcheck dvi dvi-am html html-am info \
-	info-am install install-am install-binPROGRAMS install-data \
-	install-data-am install-data-hook \
-	install-dist_bashcompletionDATA install-dvi install-dvi-am \
-	install-exec install-exec-am install-html install-html-am \
-	install-info install-info-am install-libLTLIBRARIES \
-	install-man install-man1 install-man5 install-man8 install-pdf \
-	install-pdf-am install-pkgconfigDATA install-pkgincludeHEADERS \
-	install-ps install-ps-am install-sbinPROGRAMS install-strip \
-	installcheck installcheck-am installdirs installdirs-am \
-	maintainer-clean maintainer-clean-generic mostlyclean \
-	mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
-	pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
-	uninstall-binPROGRAMS uninstall-dist_bashcompletionDATA \
-	uninstall-hook uninstall-libLTLIBRARIES uninstall-man \
-	uninstall-man1 uninstall-man5 uninstall-man8 \
-	uninstall-pkgconfigDATA uninstall-pkgincludeHEADERS \
-	uninstall-sbinPROGRAMS
+	dist-zstd distcheck distclean distclean-compile \
+	distclean-generic distclean-hdr distclean-libtool \
+	distclean-tags distcleancheck distdir distuninstallcheck dvi \
+	dvi-am html html-am info info-am install install-am \
+	install-binPROGRAMS install-data install-data-am \
+	install-data-hook install-dist_bashcompletionDATA install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am \
+	install-libLTLIBRARIES install-man install-man1 install-man5 \
+	install-man8 install-pdf install-pdf-am install-pkgconfigDATA \
+	install-pkgincludeHEADERS install-ps install-ps-am \
+	install-sbinPROGRAMS install-strip installcheck \
+	installcheck-am installdirs installdirs-am maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \
+	uninstall-dist_bashcompletionDATA uninstall-hook \
+	uninstall-libLTLIBRARIES uninstall-man uninstall-man1 \
+	uninstall-man5 uninstall-man8 uninstall-pkgconfigDATA \
+	uninstall-pkgincludeHEADERS uninstall-sbinPROGRAMS
 
 .PRECIOUS: Makefile
 
diff --git a/README.adoc b/README.adoc
index ae4a6c2..624b37d 100644
--- a/README.adoc
+++ b/README.adoc
@@ -15,13 +15,7 @@ image::https://img.shields.io/github/license/USBGuard/usbguard.svg[License, link
 == About
 
 USBGuard is a software framework for implementing USB device authorization policies (what kind of USB devices are authorized) as well as method of use policies (how a USB device may interact with the system).
-Simply put, it is a USB device whitelisting tool.
-
-WARNING: The 0.x releases are not production ready packages.
-They serve for tech-preview and user feedback purposes only.
-Please share your feedback or request a feature in the Github issue trackers for each project:
-
- * https://github.com/USBGuard/usbguard/issues/new[Report a bug or request a feature in *usbguard*]
+Simply put, it is a USB device allowlisting tool.
 
 == Documentation
 
@@ -35,7 +29,16 @@ Please share your feedback or request a feature in the Github issue trackers for
 
 == Compilation & Installation
 
-To compile the sources from a release tarball, you'll need the development files for:
+WARNING: *Prior to starting the USBGuard daemon (or service) for the first time*
+         (but after installation)
+         we need to
+         generate a rules file for USBGuard so that the currently attached
+         USB devices (in particular mouse and keyboard) keep working
+         so that you will not **get locked out of your system**.
+         More on that below at <<before-the-first-start, Before the First Start>>.
+
+To compile the source code, you will require at least C{plus}{plus}17. +
+If you are compiling sources from a release tarball, you'll need the development files for:
 
  * https://github.com/ClusterLabs/libqb[libqb] - used for local UNIX socket based IPC
  * https://github.com/google/protobuf[protobuf] - used for IPC message (de)serialization
@@ -47,19 +50,61 @@ Optionally, you may want to install:
  * https://github.com/seccomp/libseccomp[libseccomp] - used to implement a syscall whitelist
  * https://people.redhat.com/sgrubb/libcap-ng/[libcap-ng] - used to drop process capabilities
 
+If you are on a Debian based GNU/Linux distribution like Ubuntu 21.10,
+installation of all build dependencies would be something like this:
+
+    $ sudo apt update && \
+      sudo apt install --no-install-recommends -V \
+        asciidoc autoconf automake bash-completion build-essential catch2 \
+        docbook-xml docbook-xsl git ldap-utils libaudit-dev libcap-ng-dev \
+        libdbus-glib-1-dev libldap-dev libpolkit-gobject-1-dev libprotobuf-dev \
+        libqb-dev libseccomp-dev libsodium-dev libtool libxml2-utils \
+        libumockdev-dev pkg-config protobuf-compiler sudo tao-pegtl-dev xsltproc
+
 And then do:
 
-    $ ./configure --with-crypto-library=sodium # or "gcrypt", based on your preference
+    $ ./configure        # for arguments of interest see below
     $ make
+    $ make check         # if you would like to run the test suite
     $ sudo make install
 
-After the sources are successfully built, you can run the test suite by executing:
+Configure arguments that deserve explicit mentioning (quoting `./configure --help` output):
+
+      --enable-systemd        install the systemd service unit file (default=no)
+
+      --with-crypto-library   Select crypto backend library. Supported values:
+                              sodium, gcrypt, openssl.
+
+      --with-bundled-catch    Build using the bundled Catch library
+
+      --with-bundled-pegtl    Build using the bundled PEGTL library
 
-    $ make check
+      --with-ldap             Build USBGuard with ldap support
 
 If you want to compile the sources in a cloned repository, you'll have to run the `./autogen.sh` script.
 It will fetch the sources (via git submodules) of https://github.com/taocpp/PEGTL/[PEGTL] and https://github.com/philsquared/Catch[Catch].
-The script will then initialize the autotools based build system.
+The script will then initialize the autotools based build system, e.g. generate the `./configure` script.
+
+== Before the First Start
+
+*Prior to starting the USBGuard daemon (or service) for the first time*
+(but after installation)
+we need to
+generate a rules file for USBGuard so that the currently attached
+USB devices (in particular mouse and keyboard) keep working
+so that you will not **get locked out of your system**.
+
+A rules file can be generated like this:
+
+    $ sudo sh -c 'usbguard generate-policy > /etc/usbguard/rules.conf'
+
+After that, you can safely start service `usbguard`:
+
+    $ sudo systemctl start usbguard.service
+
+And you can make systemd start the service every time your boot your machine:
+
+    $ sudo systemctl enable usbguard.service
 
 == License
 
diff --git a/VERSION b/VERSION
index e7c7d3c..524cb55 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.7.8
+1.1.1
diff --git a/aclocal.m4 b/aclocal.m4
index fa9dabb..ed66b07 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -1,6 +1,6 @@
-# generated automatically by aclocal 1.16.1 -*- Autoconf -*-
+# generated automatically by aclocal 1.16.2 -*- Autoconf -*-
 
-# Copyright (C) 1996-2018 Free Software Foundation, Inc.
+# Copyright (C) 1996-2020 Free Software Foundation, Inc.
 
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -364,7 +364,7 @@ AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"],
         [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])])
 ])dnl PKG_HAVE_DEFINE_WITH_MODULES
 
-# Copyright (C) 2002-2018 Free Software Foundation, Inc.
+# Copyright (C) 2002-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -379,7 +379,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
 [am__api_version='1.16'
 dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
 dnl require some minimum version.  Point them to the right macro.
-m4_if([$1], [1.16.1], [],
+m4_if([$1], [1.16.2], [],
       [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
 ])
 
@@ -395,14 +395,14 @@ m4_define([_AM_AUTOCONF_VERSION], [])
 # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
 # This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
 AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
-[AM_AUTOMAKE_VERSION([1.16.1])dnl
+[AM_AUTOMAKE_VERSION([1.16.2])dnl
 m4_ifndef([AC_AUTOCONF_VERSION],
   [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
 _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
 
 # AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -454,7 +454,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd`
 
 # AM_CONDITIONAL                                            -*- Autoconf -*-
 
-# Copyright (C) 1997-2018 Free Software Foundation, Inc.
+# Copyright (C) 1997-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -485,7 +485,7 @@ AC_CONFIG_COMMANDS_PRE(
 Usually this means the macro was only invoked conditionally.]])
 fi])])
 
-# Copyright (C) 1999-2018 Free Software Foundation, Inc.
+# Copyright (C) 1999-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -676,7 +676,7 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl
 
 # Generate code to set up dependency tracking.              -*- Autoconf -*-
 
-# Copyright (C) 1999-2018 Free Software Foundation, Inc.
+# Copyright (C) 1999-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -715,7 +715,9 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
   done
   if test $am_rc -ne 0; then
     AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments
-    for automatic dependency tracking.  Try re-running configure with the
+    for automatic dependency tracking.  If GNU make was not used, consider
+    re-running the configure script with MAKE="gmake" (or whatever is
+    necessary).  You can also try re-running configure with the
     '--disable-dependency-tracking' option to at least be able to build
     the package (albeit without support for automatic dependency tracking).])
   fi
@@ -742,7 +744,7 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
 
 # Do all the work for Automake.                             -*- Autoconf -*-
 
-# Copyright (C) 1996-2018 Free Software Foundation, Inc.
+# Copyright (C) 1996-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -939,7 +941,7 @@ for _am_header in $config_headers :; do
 done
 echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -960,7 +962,7 @@ if test x"${install_sh+set}" != xset; then
 fi
 AC_SUBST([install_sh])])
 
-# Copyright (C) 2003-2018 Free Software Foundation, Inc.
+# Copyright (C) 2003-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -981,7 +983,7 @@ AC_SUBST([am__leading_dot])])
 
 # Check to see how 'make' treats includes.	            -*- Autoconf -*-
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1024,7 +1026,7 @@ AC_SUBST([am__quote])])
 
 # Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
 
-# Copyright (C) 1997-2018 Free Software Foundation, Inc.
+# Copyright (C) 1997-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1063,7 +1065,7 @@ fi
 
 # Helper functions for option handling.                     -*- Autoconf -*-
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1092,7 +1094,7 @@ AC_DEFUN([_AM_SET_OPTIONS],
 AC_DEFUN([_AM_IF_OPTION],
 [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
 
-# Copyright (C) 1999-2018 Free Software Foundation, Inc.
+# Copyright (C) 1999-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1139,7 +1141,7 @@ AC_LANG_POP([C])])
 # For backward compatibility.
 AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1158,7 +1160,7 @@ AC_DEFUN([AM_RUN_LOG],
 
 # Check to make sure that the build environment is sane.    -*- Autoconf -*-
 
-# Copyright (C) 1996-2018 Free Software Foundation, Inc.
+# Copyright (C) 1996-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1239,7 +1241,7 @@ AC_CONFIG_COMMANDS_PRE(
 rm -f conftest.file
 ])
 
-# Copyright (C) 2009-2018 Free Software Foundation, Inc.
+# Copyright (C) 2009-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1299,7 +1301,7 @@ AC_SUBST([AM_BACKSLASH])dnl
 _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
 ])
 
-# Copyright (C) 2001-2018 Free Software Foundation, Inc.
+# Copyright (C) 2001-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1327,7 +1329,7 @@ fi
 INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
 AC_SUBST([INSTALL_STRIP_PROGRAM])])
 
-# Copyright (C) 2006-2018 Free Software Foundation, Inc.
+# Copyright (C) 2006-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1346,7 +1348,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
 
 # Check how to create a tarball.                            -*- Autoconf -*-
 
-# Copyright (C) 2004-2018 Free Software Foundation, Inc.
+# Copyright (C) 2004-2020 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
diff --git a/config/compile b/config/compile
index 99e5052..23fcba0 100755
--- a/config/compile
+++ b/config/compile
@@ -3,7 +3,7 @@
 
 scriptversion=2018-03-07.03; # UTC
 
-# Copyright (C) 1999-2018 Free Software Foundation, Inc.
+# Copyright (C) 1999-2020 Free Software Foundation, Inc.
 # Written by Tom Tromey <tromey@cygnus.com>.
 #
 # This program is free software; you can redistribute it and/or modify
@@ -53,7 +53,7 @@ func_file_conv ()
 	  MINGW*)
 	    file_conv=mingw
 	    ;;
-	  CYGWIN*)
+	  CYGWIN* | MSYS*)
 	    file_conv=cygwin
 	    ;;
 	  *)
@@ -67,7 +67,7 @@ func_file_conv ()
 	mingw/*)
 	  file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
 	  ;;
-	cygwin/*)
+	cygwin/* | msys/*)
 	  file=`cygpath -m "$file" || echo "$file"`
 	  ;;
 	wine/*)
diff --git a/config/depcomp b/config/depcomp
index 65cbf70..6b39162 100755
--- a/config/depcomp
+++ b/config/depcomp
@@ -3,7 +3,7 @@
 
 scriptversion=2018-03-07.03; # UTC
 
-# Copyright (C) 1999-2018 Free Software Foundation, Inc.
+# Copyright (C) 1999-2020 Free Software Foundation, Inc.
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
diff --git a/config/install-sh b/config/install-sh
index 8175c64..20d8b2e 100755
--- a/config/install-sh
+++ b/config/install-sh
@@ -451,7 +451,18 @@ do
     trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
 
     # Copy the file name to the temp name.
-    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+    (umask $cp_umask &&
+     { test -z "$stripcmd" || {
+	 # Create $dsttmp read-write so that cp doesn't create it read-only,
+	 # which would cause strip to fail.
+	 if test -z "$doit"; then
+	   : >"$dsttmp" # No need to fork-exec 'touch'.
+	 else
+	   $doit touch "$dsttmp"
+	 fi
+       }
+     } &&
+     $doit_exec $cpprog "$src" "$dsttmp") &&
 
     # and set any options; do chmod last to preserve setuid bits.
     #
diff --git a/config/ltmain.sh b/config/ltmain.sh
index 7f3523d..8dab662 100644
--- a/config/ltmain.sh
+++ b/config/ltmain.sh
@@ -2124,7 +2124,7 @@ fi
 # a configuration failure hint, and exit.
 func_fatal_configuration ()
 {
-    func_fatal_error ${1+"$@"} \
+    func__fatal_error ${1+"$@"} \
       "See the $PACKAGE documentation for more information." \
       "Fatal configuration error."
 }
@@ -2415,17 +2415,10 @@ libtool_validate_options ()
     # preserve --debug
     test : = "$debug_cmd" || func_append preserve_args " --debug"
 
-    case $host in
-      # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452
-      # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788
-      *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*)
-        # don't eliminate duplications in $postdeps and $predeps
-        opt_duplicate_compiler_generated_deps=:
-        ;;
-      *)
-        opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps
-        ;;
-    esac
+    # Keeping compiler generated duplicates in $postdeps and $predeps is not
+    # harmful, and is necessary in a majority of systems that use it to satisfy
+    # symbol dependencies.
+    opt_duplicate_compiler_generated_deps=:
 
     $opt_help || {
       # Sanity checks first:
@@ -7272,12 +7265,10 @@ func_mode_link ()
       # -tp=*                Portland pgcc target processor selection
       # --sysroot=*          for sysroot support
       # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization
-      # -specs=*             GCC specs files
       # -stdlib=*            select c++ std lib with clang
       -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \
       -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \
-      -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \
-      -specs=*)
+      -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*)
         func_quote_for_eval "$arg"
 	arg=$func_quote_for_eval_result
         func_append compile_command " $arg"
diff --git a/config/missing b/config/missing
index 625aeb1..8d0eaad 100755
--- a/config/missing
+++ b/config/missing
@@ -3,7 +3,7 @@
 
 scriptversion=2018-03-07.03; # UTC
 
-# Copyright (C) 1996-2018 Free Software Foundation, Inc.
+# Copyright (C) 1996-2020 Free Software Foundation, Inc.
 # Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
 
 # This program is free software; you can redistribute it and/or modify
diff --git a/config/test-driver b/config/test-driver
index b8521a4..89dba1e 100755
--- a/config/test-driver
+++ b/config/test-driver
@@ -3,7 +3,7 @@
 
 scriptversion=2018-03-07.03; # UTC
 
-# Copyright (C) 2011-2018 Free Software Foundation, Inc.
+# Copyright (C) 2011-2020 Free Software Foundation, Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
diff --git a/configure b/configure
index 73a0f84..2093b67 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for usbguard 0.7.8.
+# Generated by GNU Autoconf 2.69 for usbguard 1.1.1.
 #
 # Report bugs to <usbguard@usbguard.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='usbguard'
 PACKAGE_TARNAME='usbguard'
-PACKAGE_VERSION='0.7.8'
-PACKAGE_STRING='usbguard 0.7.8'
+PACKAGE_VERSION='1.1.1'
+PACKAGE_STRING='usbguard 1.1.1'
 PACKAGE_BUGREPORT='usbguard@usbguard.org'
 PACKAGE_URL=''
 
@@ -649,6 +649,8 @@ DBUS_ENABLED_FALSE
 DBUS_ENABLED_TRUE
 SYSTEMD_SUPPORT_ENABLED_FALSE
 SYSTEMD_SUPPORT_ENABLED_TRUE
+ENABLE_BASH_COMPLETION_FALSE
+ENABLE_BASH_COMPLETION_TRUE
 BASH_COMPLETION_DIR
 ANALYZE_CONFIGURE_ARGS
 SYSTEMD_UNIT_DIR
@@ -691,6 +693,8 @@ crypto_CFLAGS
 LIBGCRYPT_LIBS
 LIBGCRYPT_CFLAGS
 LIBGCRYPT_CONFIG
+libcrypto_LIBS
+libcrypto_CFLAGS
 sodium_LIBS
 sodium_CFLAGS
 qb_LIBS
@@ -806,6 +810,7 @@ infodir
 docdir
 oldincludedir
 includedir
+runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -857,6 +862,7 @@ enable_tsan
 enable_full_test_suite
 enable_debug_build
 enable_systemd
+with_bash_completion_dir
 '
       ac_precious_vars='build_alias
 host_alias
@@ -879,6 +885,8 @@ qb_CFLAGS
 qb_LIBS
 sodium_CFLAGS
 sodium_LIBS
+libcrypto_CFLAGS
+libcrypto_LIBS
 audit_CFLAGS
 audit_LIBS
 seccomp_CFLAGS
@@ -933,6 +941,7 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1185,6 +1194,15 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1322,7 +1340,7 @@ fi
 for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
 		datadir sysconfdir sharedstatedir localstatedir includedir \
 		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-		libdir localedir mandir
+		libdir localedir mandir runstatedir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1435,7 +1453,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures usbguard 0.7.8 to adapt to many kinds of systems.
+\`configure' configures usbguard 1.1.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1475,6 +1493,7 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -1505,7 +1524,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of usbguard 0.7.8:";;
+     short | recursive ) echo "Configuration of usbguard 1.1.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1532,7 +1551,7 @@ Optional Features:
   --enable-seccomp        Enables Seccomp support if available (default=yes)
   --enable-libcapng       Enables POSIX 1003.1e capability support if
                           available (default=yes)
-  --enable-umockdev       Enables Seccomp support if available (default=yes)
+  --enable-umockdev       Enables umockdev support if available (default=yes)
   --enable-asan           Enable Address Sanitizer
   --enable-tsan           Enable Thread Sanitizer
   --enable-full-test-suite
@@ -1554,13 +1573,16 @@ Optional Packages:
   --with-libgcrypt-prefix=PFX
                           prefix where LIBGCRYPT is installed (optional)
   --with-crypto-library   Select crypto backend library. Supported values:
-                          sodium, gcrypt.
+                          sodium, gcrypt, openssl.
   --with-bundled-catch    Build using the bundled Catch library
   --with-bundled-pegtl    Build using the bundled PEGTL library
   --with-dbus             Build the DBus Bridge service
   --with-polkit           Install the PolicyKit configuration if D-Bus support
                           is also enabled
   --with-ldap             Build USBGuard with ldap support
+  --with-bash-completion-dir=PATH
+                          Enable bash auto-completion. Uses pkgconfig if no
+                          path given. [default=yes]
 
 Some influential environment variables:
   CC          C compiler command
@@ -1586,6 +1608,10 @@ Some influential environment variables:
   sodium_CFLAGS
               C compiler flags for sodium, overriding pkg-config
   sodium_LIBS linker flags for sodium, overriding pkg-config
+  libcrypto_CFLAGS
+              C compiler flags for libcrypto, overriding pkg-config
+  libcrypto_LIBS
+              linker flags for libcrypto, overriding pkg-config
   audit_CFLAGS
               C compiler flags for audit, overriding pkg-config
   audit_LIBS  linker flags for audit, overriding pkg-config
@@ -1681,7 +1707,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-usbguard configure 0.7.8
+usbguard configure 1.1.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2601,7 +2627,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by usbguard $as_me 0.7.8, which was
+It was created by usbguard $as_me 1.1.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -3469,7 +3495,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='usbguard'
- VERSION='0.7.8'
+ VERSION='1.1.1'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -3697,27 +3723,27 @@ EXTERNAL_CFLAGS="$CFLAGS"
 EXTERNAL_CPPFLAGS="$CPPFLAGS"
 
 COMMON_WARNING_FLAGS=" -pedantic"
-COMMON_WARNING_FLAGS+=" -Wno-unknown-pragmas"
-COMMON_WARNING_FLAGS+=" -Wall"
-COMMON_WARNING_FLAGS+=" -Wextra"
-COMMON_WARNING_FLAGS+=" -Wformat=2"
-COMMON_WARNING_FLAGS+=" -Wredundant-decls"
-COMMON_WARNING_FLAGS+=" -Wcast-align"
-COMMON_WARNING_FLAGS+=" -Wmissing-declarations"
-COMMON_WARNING_FLAGS+=" -Wmissing-include-dirs"
-COMMON_WARNING_FLAGS+=" -Wmissing-format-attribute"
-COMMON_WARNING_FLAGS+=" -Wswitch-enum"
-COMMON_WARNING_FLAGS+=" -Wswitch-default"
-COMMON_WARNING_FLAGS+=" -Winvalid-pch"
-COMMON_WARNING_FLAGS+=" -Wformat-nonliteral"
-COMMON_WARNING_FLAGS+=" -Wno-deprecated-register"
-#COMMON_WARNING_FLAGS+=" -flto -Wodr"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-unknown-pragmas"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wall"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wextra"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wformat=2"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wredundant-decls"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wcast-align"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-declarations"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-include-dirs"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-format-attribute"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wswitch-enum"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wswitch-default"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Winvalid-pch"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wformat-nonliteral"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-deprecated-register"
+#COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -flto -Wodr"
 
 #
 # Workaround for older compilers warning about { }
 # not initializing all struct fields.
 #
-COMMON_WARNING_FLAGS+=" -Wno-missing-field-initializers"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-missing-field-initializers"
 
 #
 # Don't warn about implicit fallthrough
@@ -4795,7 +4821,7 @@ fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags___Wno_implicit_fallthrough" >&5
 $as_echo "$ax_cv_check_cflags___Wno_implicit_fallthrough" >&6; }
 if test "x$ax_cv_check_cflags___Wno_implicit_fallthrough" = xyes; then :
-  COMMON_WARNING_FLAGS+=" -Wno-implicit-fallthrough"
+  COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-implicit-fallthrough"
 else
   :
 fi
@@ -4804,7 +4830,7 @@ fi
 #
 # Final project CXXFLAGS are set after configure checks.
 #
-CXXFLAGS="-std=c++11 $EXTERNAL_CXXFLAGS"
+CXXFLAGS="-std=c++17 $EXTERNAL_CXXFLAGS"
 CFLAGS="-std=c99 $EXTERNAL_CFLAGS"
 CPPFLAGS="-DHAVE_BUILD_CONFIG_H $EXTERNAL_CPPFLAGS"
 
@@ -4825,15 +4851,29 @@ CXXFLAGS_DEBUG_DISABLED=""
 # Libtool Versioning
 # See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details
 
-## increment if the interface has additions, changes, removals.
-LT_CURRENT=0
+#
+# The most recent interface number.
+#
+# Increment if the interface has additions, changes or removals.
+#
+LT_CURRENT=1
 
-## increment any time the source changes; set 0 to if you increment CURRENT
-LT_REVISION=0
+#
+# The implementation number of the current interface.
+#
+# Increment if library source code changed at all.
+# Set to 0 if you increment CURRENT.
+#
+LT_REVISION=1
 
-## increment if any interfaces have been added; set to 0
-## if any interfaces have been changed or removed. removal has
-## precedence over adding, so set to 0 if both happened.
+#
+# The difference between the newest and oldest interfaces
+# that the library implements.
+#
+# Increment if any interfaces have been added.
+# Set to 0 if any interfaces have been changed or removed.
+# Removal has precedence over adding, so set to 0 if both happened.
+#
 LT_AGE=0
 
 
@@ -16860,7 +16900,6 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 
-
 # Check if libatomic is available, might be required for emulating
 # atomic intrinsics on some platforms.
 #
@@ -16931,6 +16970,103 @@ fi
 
 
 
+# GNU (or just POSIX) basename(3) function?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for basename function" >&5
+$as_echo_n "checking for basename function... " >&6; }
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+    #define _GNU_SOURCE
+    #include <cstring>
+    int main(int argc, char ** argv) {
+        ::basename(*argv);
+        return 0;
+    }
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+    have_gnu_basename=yes
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: GNU" >&5
+$as_echo "GNU" >&6; }
+
+else
+
+    have_gnu_basename=no
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: POSIX" >&5
+$as_echo "POSIX" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${have_gnu_basename}" = xyes; then :
+
+
+$as_echo "#define HAVE_GNU_BASENAME 1" >>confdefs.h
+
+
+fi
+
+# GNU or XSI strerror_r(3) function?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for strerror_r function" >&5
+$as_echo_n "checking for strerror_r function... " >&6; }
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+    #undef _POSIX_C_SOURCE
+    #define _GNU_SOURCE
+    #include <cstring>
+    int main(int argc, char ** argv) {
+        char * res = ::strerror_r(0, 0, 0);
+        return 0;
+    }
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+    have_gnu_strerror_r=yes
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: GNU" >&5
+$as_echo "GNU" >&6; }
+
+else
+
+    have_gnu_strerror_r=no
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: XSI" >&5
+$as_echo "XSI" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${have_gnu_strerror_r}" = xyes; then :
+
+
+$as_echo "#define HAVE_GNU_STRERROR_R 1" >>confdefs.h
+
+
+fi
+
 #
 # Checks for required libraries.
 #
@@ -17891,6 +18027,87 @@ libsodium_summary="system-wide; $sodium_CFLAGS $sodium_LIBS"
 libsodium_available=yes
 fi
 
+#
+# libcrypto library (OpenSSL)
+#
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcrypto" >&5
+$as_echo_n "checking for libcrypto... " >&6; }
+
+if test -n "$libcrypto_CFLAGS"; then
+    pkg_cv_libcrypto_CFLAGS="$libcrypto_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcrypto >= 1.0.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libcrypto >= 1.0.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_libcrypto_CFLAGS=`$PKG_CONFIG --cflags "libcrypto >= 1.0.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$libcrypto_LIBS"; then
+    pkg_cv_libcrypto_LIBS="$libcrypto_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcrypto >= 1.0.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libcrypto >= 1.0.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_libcrypto_LIBS=`$PKG_CONFIG --libs "libcrypto >= 1.0.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        libcrypto_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcrypto >= 1.0.0" 2>&1`
+        else
+	        libcrypto_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcrypto >= 1.0.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$libcrypto_PKG_ERRORS" >&5
+
+
+
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+
+else
+	libcrypto_CFLAGS=$pkg_cv_libcrypto_CFLAGS
+	libcrypto_LIBS=$pkg_cv_libcrypto_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAVE_LIBCRYPTO 1" >>confdefs.h
+
+libcrypto_summary="system-wide; $libcrypto_CFLAGS $libcrypto_LIBS"
+libcrypto_available=yes
+fi
+
 #
 # gcrypt library
 #
@@ -18123,6 +18340,7 @@ $as_echo "$as_me: WARNING:
 #
 #  sodium ... libsodium
 #  gcrypt ... libgcrypt
+#  openssl ... libcrypto
 #
 
 # Check whether --with-crypto-library was given.
@@ -18134,6 +18352,18 @@ fi
 
 
 case "$with_crypto_library" in
+  openssl)
+    if test "x$libcrypto_available" = xyes; then
+      crypto_CFLAGS="$libcrypto_CFLAGS"
+      crypto_LIBS="$libcrypto_LIBS"
+      crypto_summary="$libcrypto_summary"
+
+$as_echo "#define USBGUARD_USE_OPENSSL 1" >>confdefs.h
+
+     else
+      as_fn_error $? "The selected crypto backend library is not available." "$LINENO" 5
+    fi
+  ;;
   sodium)
     if test "x$libsodium_available" = xyes; then
       crypto_CFLAGS="$sodium_CFLAGS"
@@ -18161,7 +18391,7 @@ $as_echo "#define USBGUARD_USE_LIBGCRYPT 1" >>confdefs.h
   *)
   { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "Invalid crypto library selector. Supported selectors: sodium, gcrypt
+as_fn_error $? "Invalid crypto library selector. Supported selectors: sodium, gcrypt, openssl
 See \`config.log' for more details" "$LINENO" 5; }
 esac
 
@@ -18687,14 +18917,14 @@ else
 fi
 
 if test "x$with_bundled_catch" = xyes; then
-	catch_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/Catch/include"
+	catch_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/Catch/single_include/catch2"
 	catch_LIBS=""
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Using bundled Catch library" >&5
 $as_echo "$as_me: Using bundled Catch library" >&6;}
 	catch_summary="bundled; $catch_CFLAGS $catch_LIBS"
 else
 	SAVE_CPPFLAGS=$CPPFLAGS
-	CPPFLAGS="-std=c++11 $CPPFLAGS -I/usr/include/catch"
+	CPPFLAGS="-std=c++17 $CPPFLAGS -I/usr/include/catch2"
 	ac_ext=cpp
 ac_cpp='$CXXCPP $CPPFLAGS'
 ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -18719,7 +18949,7 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-	catch_CFLAGS="-I/usr/include/catch"
+	catch_CFLAGS="-I/usr/include/catch2"
 	catch_LIBS=""
 	CPPFLAGS=$SAVE_CPPFLAGS
 	catch_summary="system-wide; $catch_CFLAGS $catch_LIBS"
@@ -18727,6 +18957,64 @@ fi
 
 
 
+#
+# stdc++fs (for PEGTL >=3 below, e.g. with GCC 8)
+#
+STDCXX_FS_LINKTEST_FILENAME="${srcdir}"/src/test_filesystem.cpp
+STDCXX_FS_LINKTEST_SOURCE="$(cat "${STDCXX_FS_LINKTEST_FILENAME}")"
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we need to link to -lstdc++fs for PEGTL explicitly" >&5
+$as_echo_n "checking whether we need to link to -lstdc++fs for PEGTL explicitly... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+${STDCXX_FS_LINKTEST_SOURCE}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+    stdcxxfs_LIBS=''
+
+else
+
+    stdcxxfs_LIBS='-lstdc++fs'
+    SAVE_LIBS=${LIBS}
+    LIBS="${LIBS} ${stdcxxfs_LIBS}"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+${STDCXX_FS_LINKTEST_SOURCE}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+else
+
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: ERROR" >&5
+$as_echo "ERROR" >&6; }
+        as_fn_error $? "Link test failed both with and without ${stdcxxfs_LIBS}; something is broken, please check file config.log for details." "$LINENO" 5
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+    LIBS=${SAVE_LIBS}
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
 #
 # PEGTL C++ library
 #
@@ -18741,14 +19029,14 @@ fi
 if test "x$with_bundled_pegtl" = xyes; then
 	pegtl_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/PEGTL/include"
 	pegtl_AC_CFLAGS="-I$srcdir/src/ThirdParty/PEGTL/include"
-	pegtl_LIBS=""
+	pegtl_LIBS="${stdcxxfs_LIBS}"
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: Using bundled PEGTL library" >&5
 $as_echo "$as_me: Using bundled PEGTL library" >&6;}
 	pegtl_summary="bundled; $pegtl_CFLAGS $pegtl_LIBS"
 else
 	pegtl_CFLAGS=""
 	pegtl_AC_CFLAGS=""
-	pegtl_LIBS=""
+	pegtl_LIBS="${stdcxxfs_LIBS}"
 	pegtl_summary="system-wide; $pegtl_CFLAGS $pegtl_LIBS"
 fi
 
@@ -18756,7 +19044,7 @@ fi
 
 
 SAVE_CPPFLAGS=$CPPFLAGS
-CPPFLAGS="-std=c++11 $CPPFLAGS $pegtl_AC_CFLAGS"
+CPPFLAGS="-std=c++17 $CPPFLAGS $pegtl_AC_CFLAGS"
 ac_ext=cpp
 ac_cpp='$CXXCPP $CPPFLAGS'
 ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -18808,12 +19096,12 @@ if test -n "$dbus_CFLAGS"; then
     pkg_cv_dbus_CFLAGS="$dbus_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 gio-2.0\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "dbus-1 gio-2.0") 2>&5
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 gio-2.0 polkit-gobject-1\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "dbus-1 gio-2.0 polkit-gobject-1") 2>&5
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_dbus_CFLAGS=`$PKG_CONFIG --cflags "dbus-1 gio-2.0" 2>/dev/null`
+  pkg_cv_dbus_CFLAGS=`$PKG_CONFIG --cflags "dbus-1 gio-2.0 polkit-gobject-1" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -18825,12 +19113,12 @@ if test -n "$dbus_LIBS"; then
     pkg_cv_dbus_LIBS="$dbus_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 gio-2.0\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "dbus-1 gio-2.0") 2>&5
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 gio-2.0 polkit-gobject-1\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "dbus-1 gio-2.0 polkit-gobject-1") 2>&5
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_dbus_LIBS=`$PKG_CONFIG --libs "dbus-1 gio-2.0" 2>/dev/null`
+  pkg_cv_dbus_LIBS=`$PKG_CONFIG --libs "dbus-1 gio-2.0 polkit-gobject-1" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -18851,9 +19139,9 @@ else
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        dbus_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 gio-2.0" 2>&1`
+	        dbus_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 gio-2.0 polkit-gobject-1" 2>&1`
         else
-	        dbus_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 gio-2.0" 2>&1`
+	        dbus_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 gio-2.0 polkit-gobject-1" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$dbus_PKG_ERRORS" >&5
@@ -19259,7 +19547,6 @@ ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
 
-  ASAN_FLAGS="$ASAN_FLAGS -static-libasan"
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C++ compiler accepts -faddress-sanitizer" >&5
 $as_echo_n "checking whether C++ compiler accepts -faddress-sanitizer... " >&6; }
 if ${ax_cv_check_cxxflags___faddress_sanitizer+:} false; then :
@@ -19377,6 +19664,41 @@ else
   :
 fi
 
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C++ compiler accepts -static-libasan" >&5
+$as_echo_n "checking whether C++ compiler accepts -static-libasan... " >&6; }
+if ${ax_cv_check_cxxflags___static_libasan+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+  ax_check_save_flags=$CXXFLAGS
+  CXXFLAGS="$CXXFLAGS  -static-libasan"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ax_cv_check_cxxflags___static_libasan=yes
+else
+  ax_cv_check_cxxflags___static_libasan=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  CXXFLAGS=$ax_check_save_flags
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cxxflags___static_libasan" >&5
+$as_echo "$ax_cv_check_cxxflags___static_libasan" >&6; }
+if test "x$ax_cv_check_cxxflags___static_libasan" = xyes; then :
+  ASAN_FLAGS="$ASAN_FLAGS -static-libasan"
+else
+  :
+fi
+
   ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -20587,6 +20909,15 @@ fi
 
 
 
+# Check whether --with-bash-completion-dir was given.
+if test "${with_bash_completion_dir+set}" = set; then :
+  withval=$with_bash_completion_dir;
+else
+  with_bash_completion_dir=yes
+fi
+
+
+if test "x$with_bash_completion_dir" = "xyes"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for BASH_COMPLETION" >&5
@@ -20646,18 +20977,20 @@ fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$BASH_COMPLETION_PKG_ERRORS" >&5
 
-	bash_completion=no
+	BASH_COMPLETION_DIR="$datadir/bash-completion/completions"
 elif test $pkg_failed = untried; then
      	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
 $as_echo "no" >&6; }
-	bash_completion=no
+	BASH_COMPLETION_DIR="$datadir/bash-completion/completions"
 else
 	BASH_COMPLETION_CFLAGS=$pkg_cv_BASH_COMPLETION_CFLAGS
 	BASH_COMPLETION_LIBS=$pkg_cv_BASH_COMPLETION_LIBS
         { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 $as_echo "yes" >&6; }
-	bash_completion_dir="`$PKG_CONFIG --variable=completionsdir bash-completion`"
-   bash_completion=yes
+	BASH_COMPLETION_DIR=$($PKG_CONFIG --variable=completionsdir bash-completion)
+fi
+else
+	BASH_COMPLETION_DIR="$with_bash_completion_dir"
 fi
 
 if test "x$debug" = xyes; then
@@ -20712,19 +21045,14 @@ fi
 ANALYZE_CONFIGURE_ARGS=$ac_configure_args
 
 
-case "$bash_completion_dir" in
-  /usr/share/*|/usr/local/share/*)
-    bash_completion_dir=$(echo "$bash_completion_dir" | sed -r 's,^(/usr/share|/usr/local/share),${datadir},')
-    ;;
-  /usr/*|/usr/local/*)
-    bash_completion_dir=$(echo "$bash_completion_dir" | sed -r 's,^(/usr|/usr/local),${prefix},')
-    ;;
-  /*)
-    bash_completion_dir='${prefix}'"$bash_completion_dir"
-    ;;
-esac
 
-BASH_COMPLETION_DIR=$bash_completion_dir
+ if test "x$with_bash_completion_dir" != "xno"; then
+  ENABLE_BASH_COMPLETION_TRUE=
+  ENABLE_BASH_COMPLETION_FALSE='#'
+else
+  ENABLE_BASH_COMPLETION_TRUE='#'
+  ENABLE_BASH_COMPLETION_FALSE=
+fi
 
 
  if test "x$systemd" = xyes ; then
@@ -20993,6 +21321,10 @@ if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then
   as_fn_error $? "conditional \"am__fastdepCXX\" was never defined.
 Usually this means the macro was only invoked conditionally." "$LINENO" 5
 fi
+if test -z "${ENABLE_BASH_COMPLETION_TRUE}" && test -z "${ENABLE_BASH_COMPLETION_FALSE}"; then
+  as_fn_error $? "conditional \"ENABLE_BASH_COMPLETION\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
 if test -z "${SYSTEMD_SUPPORT_ENABLED_TRUE}" && test -z "${SYSTEMD_SUPPORT_ENABLED_FALSE}"; then
   as_fn_error $? "conditional \"SYSTEMD_SUPPORT_ENABLED\" was never defined.
 Usually this means the macro was only invoked conditionally." "$LINENO" 5
@@ -21418,7 +21750,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by usbguard $as_me 0.7.8, which was
+This file was extended by usbguard $as_me 1.1.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -21484,7 +21816,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-usbguard config.status 0.7.8
+usbguard config.status 1.1.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -22678,7 +23010,9 @@ $as_echo X/"$am_mf" |
     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "Something went wrong bootstrapping makefile fragments
-    for automatic dependency tracking.  Try re-running configure with the
+    for automatic dependency tracking.  If GNU make was not used, consider
+    re-running the configure script with MAKE=\"gmake\" (or whatever is
+    necessary).  You can also try re-running configure with the
     '--disable-dependency-tracking' option to at least be able to build
     the package (albeit without support for automatic dependency tracking).
 See \`config.log' for more details" "$LINENO" 5; }
@@ -23455,7 +23789,7 @@ echo " libseccomp: $libseccomp_summary"
 echo "  libcap-ng: $libcap_ng_summary"
 echo "   protobuf: $protobuf_summary"
 echo "      Catch: $catch_summary"
-echo "      PEGTL: $pegtl_summary; version <= 2.6.0: $have_pegtl_lte_260"
+echo "      PEGTL: $pegtl_summary"
 echo "      GDBus: $dbus_summary"
 echo "   umockdev: $umockdev_summary"
 echo
diff --git a/configure.ac b/configure.ac
index 5e319b6..0f3e79a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -20,27 +20,27 @@ EXTERNAL_CFLAGS="$CFLAGS"
 EXTERNAL_CPPFLAGS="$CPPFLAGS"
 
 COMMON_WARNING_FLAGS=" -pedantic"
-COMMON_WARNING_FLAGS+=" -Wno-unknown-pragmas"
-COMMON_WARNING_FLAGS+=" -Wall"
-COMMON_WARNING_FLAGS+=" -Wextra"
-COMMON_WARNING_FLAGS+=" -Wformat=2"
-COMMON_WARNING_FLAGS+=" -Wredundant-decls"
-COMMON_WARNING_FLAGS+=" -Wcast-align"
-COMMON_WARNING_FLAGS+=" -Wmissing-declarations"
-COMMON_WARNING_FLAGS+=" -Wmissing-include-dirs"
-COMMON_WARNING_FLAGS+=" -Wmissing-format-attribute"
-COMMON_WARNING_FLAGS+=" -Wswitch-enum"
-COMMON_WARNING_FLAGS+=" -Wswitch-default"
-COMMON_WARNING_FLAGS+=" -Winvalid-pch"
-COMMON_WARNING_FLAGS+=" -Wformat-nonliteral"
-COMMON_WARNING_FLAGS+=" -Wno-deprecated-register"
-#COMMON_WARNING_FLAGS+=" -flto -Wodr"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-unknown-pragmas"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wall"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wextra"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wformat=2"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wredundant-decls"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wcast-align"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-declarations"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-include-dirs"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wmissing-format-attribute"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wswitch-enum"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wswitch-default"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Winvalid-pch"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wformat-nonliteral"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-deprecated-register"
+#COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -flto -Wodr"
 
 #
 # Workaround for older compilers warning about { }
 # not initializing all struct fields.
 #
-COMMON_WARNING_FLAGS+=" -Wno-missing-field-initializers"
+COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-missing-field-initializers"
 
 #
 # Don't warn about implicit fallthrough
@@ -49,12 +49,12 @@ COMMON_WARNING_FLAGS+=" -Wno-missing-field-initializers"
 #       newer compiler versions.
 #
 AX_CHECK_COMPILE_FLAG([-Wno-implicit-fallthrough],
-                      [COMMON_WARNING_FLAGS+=" -Wno-implicit-fallthrough"], [], [])
+                      [COMMON_WARNING_FLAGS="${COMMON_WARNING_FLAGS} -Wno-implicit-fallthrough"], [], [])
 
 #
 # Final project CXXFLAGS are set after configure checks.
 #
-CXXFLAGS="-std=c++11 $EXTERNAL_CXXFLAGS"
+CXXFLAGS="-std=c++17 $EXTERNAL_CXXFLAGS"
 CFLAGS="-std=c99 $EXTERNAL_CFLAGS"
 CPPFLAGS="-DHAVE_BUILD_CONFIG_H $EXTERNAL_CPPFLAGS"
 
@@ -75,15 +75,29 @@ CXXFLAGS_DEBUG_DISABLED=""
 # Libtool Versioning
 # See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details
 
-## increment if the interface has additions, changes, removals.
-LT_CURRENT=0
+#
+# The most recent interface number.
+#
+# Increment if the interface has additions, changes or removals.
+#
+LT_CURRENT=1
 
-## increment any time the source changes; set 0 to if you increment CURRENT
-LT_REVISION=0
+#
+# The implementation number of the current interface.
+#
+# Increment if library source code changed at all.
+# Set to 0 if you increment CURRENT.
+#
+LT_REVISION=1
 
-## increment if any interfaces have been added; set to 0
-## if any interfaces have been changed or removed. removal has
-## precedence over adding, so set to 0 if both happened.
+#
+# The difference between the newest and oldest interfaces
+# that the library implements.
+#
+# Increment if any interfaces have been added.
+# Set to 0 if any interfaces have been changed or removed.
+# Removal has precedence over adding, so set to 0 if both happened.
+#
 LT_AGE=0
 
 AC_SUBST(LT_CURRENT)
@@ -91,7 +105,7 @@ AC_SUBST(LT_REVISION)
 AC_SUBST(LT_AGE)
 
 AC_ARG_ENABLE([coverage],
-     [AC_HELP_STRING([--enable-coverage], [enable instrumented compilation for code coverage testing (default=no)])],
+     [AS_HELP_STRING([--enable-coverage], [enable instrumented compilation for code coverage testing (default=no)])],
      [case "${enableval}" in
        yes) coverage=yes ;;
        no)  coverage=no ;;
@@ -104,7 +118,7 @@ if test "x$coverage" = xyes; then
 fi
 
 AC_ARG_ENABLE([werror],
-     [AC_HELP_STRING([--enable-werror], [treat compiler warnings as errors (default=no)])],
+     [AS_HELP_STRING([--enable-werror], [treat compiler warnings as errors (default=no)])],
      [case "${enableval}" in
        yes) werror=yes ;;
        no)  werror=no ;;
@@ -122,8 +136,7 @@ AC_PROG_CXX
 AC_PROG_CC_C99
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
-AM_PROG_LIBTOOL
-AC_PROG_LIBTOOL
+LT_INIT
 
 # Check if libatomic is available, might be required for emulating
 # atomic intrinsics on some platforms.
@@ -139,6 +152,51 @@ AC_CHECK_LIB([atomic], [__atomic_add_fetch_8], [
 ], [atomic_LIBS=""])
 AC_SUBST([atomic_LIBS])
 
+# GNU (or just POSIX) basename(3) function?
+AC_MSG_CHECKING([for basename function])
+AC_LANG_PUSH([C++])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+    #define _GNU_SOURCE
+    #include <cstring>
+    int main(int argc, char ** argv) {
+        ::basename(*argv);
+        return 0;
+    }
+])], [
+    have_gnu_basename=yes
+    AC_MSG_RESULT([GNU])
+], [
+    have_gnu_basename=no
+    AC_MSG_RESULT([POSIX])
+])
+AC_LANG_POP()
+AS_IF([test "x${have_gnu_basename}" = xyes], [
+  AC_DEFINE([HAVE_GNU_BASENAME], [1], [Wether the GNU version of function basename(3) is available (or just the POSIX one).])
+], [])
+
+# GNU or XSI strerror_r(3) function?
+AC_MSG_CHECKING([for strerror_r function])
+AC_LANG_PUSH([C++])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+    #undef _POSIX_C_SOURCE
+    #define _GNU_SOURCE
+    #include <cstring>
+    int main(int argc, char ** argv) {
+        char * res = ::strerror_r(0, 0, 0);
+        return 0;
+    }
+])], [
+    have_gnu_strerror_r=yes
+    AC_MSG_RESULT([GNU])
+], [
+    have_gnu_strerror_r=no
+    AC_MSG_RESULT([XSI])
+])
+AC_LANG_POP()
+AS_IF([test "x${have_gnu_strerror_r}" = xyes], [
+  AC_DEFINE([HAVE_GNU_STRERROR_R], [1], [Wether the GNU version of function strerror_r(3) is available (or just the XSI one).])
+], [])
+
 #
 # Checks for required libraries.
 #
@@ -178,6 +236,16 @@ libsodium_available=yes,
 []
 )
 
+#
+# libcrypto library (OpenSSL)
+#
+PKG_CHECK_MODULES([libcrypto], [libcrypto >= 1.0.0],
+[AC_DEFINE([HAVE_LIBCRYPTO], [1], [libcrypto API available])
+libcrypto_summary="system-wide; $libcrypto_CFLAGS $libcrypto_LIBS"]
+libcrypto_available=yes,
+[]
+)
+
 #
 # gcrypt library
 #
@@ -197,12 +265,23 @@ libgcrypt_available=yes],
 #
 #  sodium ... libsodium
 #  gcrypt ... libgcrypt
+#  openssl ... libcrypto
 #
 AC_ARG_WITH([crypto-library], AS_HELP_STRING([--with-crypto-library],
-            [Select crypto backend library. Supported values: sodium, gcrypt.]),
+            [Select crypto backend library. Supported values: sodium, gcrypt, openssl.]),
             [with_crypto_library=$withval], [with_crypto_library=sodium])
 
 case "$with_crypto_library" in
+  openssl)
+    if test "x$libcrypto_available" = xyes; then
+      crypto_CFLAGS="$libcrypto_CFLAGS"
+      crypto_LIBS="$libcrypto_LIBS"
+      crypto_summary="$libcrypto_summary"
+      AC_DEFINE([USBGUARD_USE_OPENSSL], [1], [Use openssl as crypto backend])
+     else
+      AC_MSG_ERROR([The selected crypto backend library is not available.])
+    fi
+  ;;
   sodium)
     if test "x$libsodium_available" = xyes; then
       crypto_CFLAGS="$sodium_CFLAGS"
@@ -224,7 +303,7 @@ case "$with_crypto_library" in
     fi
   ;;
   *)
-  AC_MSG_FAILURE([Invalid crypto library selector. Supported selectors: sodium, gcrypt])
+  AC_MSG_FAILURE([Invalid crypto library selector. Supported selectors: sodium, gcrypt, openssl])
 esac
 AC_SUBST([crypto_CFLAGS])
 AC_SUBST([crypto_LIBS])
@@ -279,7 +358,7 @@ else
 fi
 
 AC_ARG_ENABLE([umockdev],
-              [AS_HELP_STRING([--enable-umockdev], [Enables Seccomp support if available (default=yes)])],
+              [AS_HELP_STRING([--enable-umockdev], [Enables umockdev support if available (default=yes)])],
               [use_umockdev=$enableval], [use_umockdev=yes])
 
 if test "x$use_umockdev" = xyes; then
@@ -309,17 +388,17 @@ fi
 #
 AC_ARG_WITH([bundled-catch], AS_HELP_STRING([--with-bundled-catch], [Build using the bundled Catch library]), [with_bundled_catch=$withval], [with_bundled_catch=no])
 if test "x$with_bundled_catch" = xyes; then
-	catch_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/Catch/include"
+	catch_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/Catch/single_include/catch2"
 	catch_LIBS=""
 	AC_MSG_NOTICE([Using bundled Catch library])
 	catch_summary="bundled; $catch_CFLAGS $catch_LIBS"
 else
 	SAVE_CPPFLAGS=$CPPFLAGS
-	CPPFLAGS="-std=c++11 $CPPFLAGS -I/usr/include/catch"
+	CPPFLAGS="-std=c++17 $CPPFLAGS -I/usr/include/catch2"
 	AC_LANG_PUSH([C++])
 	AC_CHECK_HEADER([catch.hpp], [], [AC_MSG_FAILURE(catch.hpp not found or not usable. Re-run with --with-bundled-catch to use the bundled library.)])
 	AC_LANG_POP
-	catch_CFLAGS="-I/usr/include/catch"
+	catch_CFLAGS="-I/usr/include/catch2"
 	catch_LIBS=""
 	CPPFLAGS=$SAVE_CPPFLAGS
 	catch_summary="system-wide; $catch_CFLAGS $catch_LIBS"
@@ -327,6 +406,30 @@ fi
 AC_SUBST([catch_CFLAGS])
 AC_SUBST([catch_LIBS])
 
+#
+# stdc++fs (for PEGTL >=3 below, e.g. with GCC 8)
+#
+STDCXX_FS_LINKTEST_FILENAME="${srcdir}"/src/test_filesystem.cpp
+STDCXX_FS_LINKTEST_SOURCE="$(cat "${STDCXX_FS_LINKTEST_FILENAME}")"
+AC_LANG_PUSH([C++])
+AC_MSG_CHECKING([whether we need to link to -lstdc++fs for PEGTL explicitly])
+AC_LINK_IFELSE([AC_LANG_SOURCE([${STDCXX_FS_LINKTEST_SOURCE}])], [
+    AC_MSG_RESULT([no])
+    stdcxxfs_LIBS=''
+], [
+    stdcxxfs_LIBS='-lstdc++fs'
+    SAVE_LIBS=${LIBS}
+    LIBS="${LIBS} ${stdcxxfs_LIBS}"
+    AC_LINK_IFELSE([AC_LANG_SOURCE([${STDCXX_FS_LINKTEST_SOURCE}])], [
+        AC_MSG_RESULT([yes])
+    ], [
+        AC_MSG_RESULT([ERROR])
+        AC_MSG_ERROR([Link test failed both with and without ${stdcxxfs_LIBS}; something is broken, please check file config.log for details.])
+    ])
+    LIBS=${SAVE_LIBS}
+])
+AC_LANG_POP()
+
 #
 # PEGTL C++ library
 #
@@ -334,13 +437,13 @@ AC_ARG_WITH([bundled-pegtl], AS_HELP_STRING([--with-bundled-pegtl], [Build using
 if test "x$with_bundled_pegtl" = xyes; then
 	pegtl_CFLAGS="-I\$(top_srcdir)/src/ThirdParty/PEGTL/include"
 	pegtl_AC_CFLAGS="-I$srcdir/src/ThirdParty/PEGTL/include"
-	pegtl_LIBS=""
+	pegtl_LIBS="${stdcxxfs_LIBS}"
 	AC_MSG_NOTICE([Using bundled PEGTL library])
 	pegtl_summary="bundled; $pegtl_CFLAGS $pegtl_LIBS"
 else
 	pegtl_CFLAGS=""
 	pegtl_AC_CFLAGS=""
-	pegtl_LIBS=""
+	pegtl_LIBS="${stdcxxfs_LIBS}"
 	pegtl_summary="system-wide; $pegtl_CFLAGS $pegtl_LIBS"
 fi
 AC_SUBST([pegtl_CFLAGS])
@@ -348,7 +451,7 @@ AC_SUBST([pegtl_AC_CFLAGS])
 AC_SUBST([pegtl_LIBS])
 
 SAVE_CPPFLAGS=$CPPFLAGS
-CPPFLAGS="-std=c++11 $CPPFLAGS $pegtl_AC_CFLAGS"
+CPPFLAGS="-std=c++17 $CPPFLAGS $pegtl_AC_CFLAGS"
 AC_LANG_PUSH([C++])
 AC_CHECK_HEADER([tao/pegtl.hpp],
 		[AC_DEFINE([HAVE_TAO_PEGTL_HPP], [1], [PEGTL header file with .hpp extension is present])],
@@ -359,12 +462,12 @@ CPPFLAGS=$SAVE_CPPFLAGS
 #
 # GDBus
 #
-AC_ARG_WITH([dbus], AC_HELP_STRING([--with-dbus], [Build the DBus Bridge service]), [], [with_dbus=yes])
+AC_ARG_WITH([dbus], AS_HELP_STRING([--with-dbus], [Build the DBus Bridge service]), [], [with_dbus=yes])
 if test "x$with_dbus" = xyes; then
   #
   # Check for required D-Bus modules
   #
-  PKG_CHECK_MODULES([dbus], [dbus-1 gio-2.0],
+  PKG_CHECK_MODULES([dbus], [dbus-1 gio-2.0 polkit-gobject-1],
   [AC_DEFINE([HAVE_DBUS], [1], [Required GDBus API available])
   dbus_summary="system-wide; $dbus_CFLAGS $dbus_LIBS"],
   [AC_MSG_FAILURE([Required D-Bus modules (dbus-1, gio-2.0) not found!])]
@@ -431,7 +534,7 @@ fi
 #
 # PolicyKit
 #
-AC_ARG_WITH([polkit], AC_HELP_STRING([--with-polkit], [Install the PolicyKit configuration if D-Bus support is also enabled]), [], [with_polkit=yes])
+AC_ARG_WITH([polkit], AS_HELP_STRING([--with-polkit], [Install the PolicyKit configuration if D-Bus support is also enabled]), [], [with_polkit=yes])
 if test "x$with_polkit" = xyes; then
   #
   # Check for required PolicyKit modules
@@ -456,7 +559,7 @@ fi
 #
 # LDAP
 #
-AC_ARG_WITH([ldap], AC_HELP_STRING([--with-ldap], [Build USBGuard with ldap support]), [], [with_ldap=no])
+AC_ARG_WITH([ldap], AS_HELP_STRING([--with-ldap], [Build USBGuard with ldap support]), [], [with_ldap=no])
 if test "x$with_ldap" = xyes; then
   #
   # Check for LDAP ldap.h
@@ -497,7 +600,6 @@ AC_ARG_ENABLE([asan],
 
 if test "x$enable_asan" = xyes; then
   AC_LANG_PUSH([C++])
-  ASAN_FLAGS="$ASAN_FLAGS -static-libasan"
   AX_CHECK_COMPILE_FLAG([-faddress-sanitizer],
                         [ASAN_FLAGS="$ASAN_FLAGS -faddress-sanitizer"],
                         [enable_asan=no], [])
@@ -516,6 +618,9 @@ if test "x$enable_asan" = xyes; then
   AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer],
                         [ASAN_FLAGS="$ASAN_FLAGS -fno-omit-frame-pointer"],
                         [], [])
+  AX_CHECK_COMPILE_FLAG([-static-libasan],
+                        [ASAN_FLAGS="$ASAN_FLAGS -static-libasan"],
+                        [], [])
   AC_LANG_POP
 fi
 #
@@ -613,7 +718,7 @@ AC_LANG_POP
 
 # ./configure script options
 AC_ARG_ENABLE([debug-build],
-     [AC_HELP_STRING([--enable-debug-build], [enable debugging flags (default=no)])],
+     [AS_HELP_STRING([--enable-debug-build], [enable debugging flags (default=no)])],
      [case "${enableval}" in
        yes) debug=yes ;;
        no)  debug=no ;;
@@ -621,18 +726,25 @@ AC_ARG_ENABLE([debug-build],
      esac], [debug=no])
 
 AC_ARG_ENABLE([systemd],
-     [AC_HELP_STRING([--enable-systemd], [install the systemd service unit file (default=no)])],
+     [AS_HELP_STRING([--enable-systemd], [install the systemd service unit file (default=no)])],
      [case "${enableval}" in
        yes) systemd=yes ;;
        no)  systemd=no ;;
        *) AC_MSG_ERROR([bad value ${enableval} for --enable-systemd]) ;;
      esac], [systemd=no])
 
+AC_ARG_WITH([bash-completion-dir],
+	AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
+		[Enable bash auto-completion. Uses pkgconfig if no path given. @<:@default=yes@:>@]),
+	[], [with_bash_completion_dir=yes])
 
-PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
-  [bash_completion_dir="`$PKG_CONFIG --variable=completionsdir bash-completion`"
-   bash_completion=yes],
-  [bash_completion=no])
+if test "x$with_bash_completion_dir" = "xyes"; then
+	PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
+		[BASH_COMPLETION_DIR=$($PKG_CONFIG --variable=completionsdir bash-completion)],
+		[BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])
+else
+	BASH_COMPLETION_DIR="$with_bash_completion_dir"
+fi
 
 if test "x$debug" = xyes; then
    CXXFLAGS="$CXXFLAGS $CXXFLAGS_DEBUG_ENABLED"
@@ -671,19 +783,8 @@ fi
 
 AC_SUBST([ANALYZE_CONFIGURE_ARGS], $ac_configure_args)
 
-case "$bash_completion_dir" in
-  /usr/share/*|/usr/local/share/*)
-    bash_completion_dir=$(echo "$bash_completion_dir" | sed -r 's,^(/usr/share|/usr/local/share),${datadir},')
-    ;;
-  /usr/*|/usr/local/*)
-    bash_completion_dir=$(echo "$bash_completion_dir" | sed -r 's,^(/usr|/usr/local),${prefix},')
-    ;;
-  /*)
-    bash_completion_dir='${prefix}'"$bash_completion_dir"
-    ;;
-esac
-
-AC_SUBST([BASH_COMPLETION_DIR], $bash_completion_dir)
+AC_SUBST([BASH_COMPLETION_DIR])
+AM_CONDITIONAL([ENABLE_BASH_COMPLETION], [test "x$with_bash_completion_dir" != "xno"])
 
 AM_CONDITIONAL([SYSTEMD_SUPPORT_ENABLED], [test "x$systemd" = xyes ])
 AM_CONDITIONAL([DBUS_ENABLED], [test "x$with_dbus" = xyes ])
@@ -733,7 +834,7 @@ echo " libseccomp: $libseccomp_summary"
 echo "  libcap-ng: $libcap_ng_summary"
 echo "   protobuf: $protobuf_summary"
 echo "      Catch: $catch_summary"
-echo "      PEGTL: $pegtl_summary; version <= 2.6.0: $have_pegtl_lte_260"
+echo "      PEGTL: $pegtl_summary"
 echo "      GDBus: $dbus_summary"
 echo "   umockdev: $umockdev_summary"
 echo
diff --git a/doc/man/usbguard-daemon.conf.5.adoc b/doc/man/usbguard-daemon.conf.5.adoc
index 3458896..17e7b5c 100644
--- a/doc/man/usbguard-daemon.conf.5.adoc
+++ b/doc/man/usbguard-daemon.conf.5.adoc
@@ -18,6 +18,7 @@ It may be overridden using the *-c* command-line option, see *usbguard-daemon*(8
 *RuleFile*='path'::
     The USBGuard daemon will use this file to load the policy rule set from it
     and to write new rules received via the IPC interface.
+    Default: %sysconfdir%/usbguard/rules.conf
 
 *RuleFolder*='path'::
     The USBGuard daemon will use this folder to load the policy rule set from
@@ -32,35 +33,40 @@ It may be overridden using the *-c* command-line option, see *usbguard-daemon*(8
     How to treat USB devices that don't match any rule in the policy. Target
     should be one of `allow`, `block` or `reject` (logically remove the device
     node from the system).
+    Default: block
 
 *PresentDevicePolicy*='policy'::
     How to treat USB devices that are already connected when the daemon starts.
     Policy should be one of `allow`, `block`, `reject`, `keep` (keep whatever
     state the device is currently in) or `apply-policy` (evaluate the rule set
     for every present device).
+    Default: apply-policy
 
 *PresentControllerPolicy*='policy'::
     How to treat USB *controller* devices that are already connected when the
     daemon starts. One of `allow`, `block`, `reject`, `keep` or `apply-policy`.
+    Default: keep
 
 *InsertedDevicePolicy*='policy'::
     How to treat USB devices that are already connected _after_ the daemon
     starts. One of `block`, `reject`, `apply-policy`.
+    Default: apply-policy
 
 *AuthorizedDefault*='authorizedDefault'::
     The USBGuard daemon modifies some of the default authorization state
     attributes of controller devices. This setting, enables you to define what
     value the default authorization is set to. Authorized default should be one
-    of `keep` (do not change autorization state), `wired` (new wired USB
-    devices start out authorized, wireless do not), `none` (every new device
+    of `keep` (do not change authorization state), `none` (every new device
     starts out deauthorized), `all` (every new device starts out authorized) or
     `internal` (internal devices start out authorized, external do not).
+    Default: none
 
 *RestoreControllerDeviceState*='boolean'::
     The USBGuard daemon modifies some attributes of controller devices like the
     default authorization state of new child device instances. Using this
     setting, you can control whether the daemon will try to restore the
     attribute values to the state before modification on shutdown.
+    Default: false
 
 *DeviceManagerBackend*='backend'::
     Which device manager backend implementation to use. Backend should be one
@@ -69,10 +75,12 @@ It may be overridden using the *-c* command-line option, see *usbguard-daemon*(8
     and an uevent socket for receiving USB device related events. UMockDev
     based device manager is capable of simulating devices based on
     umockdev-record files.
+    Default: uevent
 
 *IPCAllowedUsers*='username' ['username' ...]::
     A space delimited list of usernames that the daemon will accept IPC
     connections from.
+    Default: root
 
 *IPCAllowedGroups*='groupname' ['groupname' ...]::
     A space delimited list of groupnames that the daemon will accept IPC
@@ -85,18 +93,22 @@ It may be overridden using the *-c* command-line option, see *usbguard-daemon*(8
 
 *DeviceRulesWithPort*='boolean'::
     Generate device specific rules including the "via-port" attribute.
+    Default: false
 
 *AuditBackend*='backend'::
     USBGuard audit events log backend. The 'backend' value should be one of
     `FileAudit` or `LinuxAudit`.
+    Default: FileAudit
 
 *AuditFilePath*='filepath'::
     USBGuard audit events log file path. Required if AuditBackend is set to
     `FileAudit`.
+    Default: %localstatedir%/log/usbguard/usbguard-audit.log
 
 *HidePII*='boolean'::
     Hides personally identifiable information such as device serial numbers and
     hashes of descriptors (which include the serial number) from audit entries.
+    Default: false
 
 
 == SECURITY CONSIDERATIONS
@@ -118,14 +130,14 @@ Access to the USBGuard IPC interface can be limited per user or group.
 Furthermore, by using the IPC Access Control files, it is possible to limit the access down to the level of Sections and Privileges as explained below.
 
 === *Recommended*: *IPCAccessControlFiles*
-When you set *IPCAccessControlFiles* option, the daemon will look for IPC access control files in the directory specified by the setting value.
+When you set *IPCAccessControlFiles* option, the daemon will look for IPC access control files in the directory specified by the set value.
 Each file in the directory is processed as follows:
 
- 1. The basename of the file is interpreted as an username, UID, groupname or GID.
-    If the names starts with `:` (colon), it is assumed that the rest of the name represents a group identifier (groupname or GID in case of a numeric-only string).
-    Otherwise, it is interpreted as an user identifier (username or UID in case of numeric-only string).
+ 1. The basename of the file is interpreted as a username, UID, groupname or GID.
+    If the name starts with `:` (colon), it is assumed that the rest of the name represents a group identifier (groupname or GID in case of a numeric-only string).
+    Otherwise, it is interpreted as a user identifier (username or UID in case of numeric-only string).
 
- 2. The contents of the file are parsed as `Section=privilege [privilege ...]` formatted lines which specify the section privileges.
+ 2. The contents of the file are parsed as `Section=[privilege1][,privilege2] ...` formatted lines which specify the section privileges.
     If a section is omitted, it is assumed that no privileges are given for that section.
 
 Available sections and privileges:
@@ -150,17 +162,22 @@ Available sections and privileges:
 
  ** list: Get values of run-time parameters.
 
+ ** listen: Listen to property parameter changes.
+
 The following is a generally usable and reasonably safe example of an access control file.
-It allows to modify USB device authorization state (`Devices=modify`), list USB devices (`Devices=list`), listen to USB device related events (`Devices=listen`), list USB authorization policy rules (`Policy=list`) and listen to exception events (`Exceptions=listen`):
+It allows one to modify USB device authorization state (`Devices=modify`), list USB devices (`Devices=list`), listen to USB device related events (`Devices=listen`), list USB authorization policy rules (`Policy=list`) and listen to exception events (`Exceptions=listen`):
 
 ....
-Devices=modify list listen
+Devices=modify,list,listen
 Policy=list
 Exceptions=listen
 ....
 
-Instead of creating the access control files by yourself, you can use the `usbguard add-user` or `usbguard remove-user` CLI commands.
+You can create or remove the IPC access control files using `usbguard add-user` and `usbguard
+remove-user` CLI commands.
 See usbguard(1) for more details.
+If you want to create the IPC access control files manually, you need to set the files permissions
+to `0600`.
 
 
 === Legacy: *IPCAllowedUsers* and *IPCAllowedGroups*
diff --git a/doc/man/usbguard-ldap.conf.5.adoc b/doc/man/usbguard-ldap.conf.5.adoc
index 1a1daed..3ac35b3 100644
--- a/doc/man/usbguard-ldap.conf.5.adoc
+++ b/doc/man/usbguard-ldap.conf.5.adoc
@@ -19,7 +19,7 @@ Keys are case insensitive but values are not.
 == OPTIONS
 *URI* 'address'::
     The USBGuard Daemon will use this address to connect to this LDAP server.
-    
+
     `URI ldap://127.0.0.1/`
 
 *ROOTDN* 'root domain name'::
@@ -41,7 +41,7 @@ Keys are case insensitive but values are not.
     This is supposed to be domain name of the USBGuard organizational unit.
     This option helps to the daemon found the right sub-tree with rules.
     If not set the daemon will use the default (ou=USBGuard,dc=example,dc=com).
-    
+
     `USBGUARDBASE ou=USBGuard,dc=example,dc=com`
 
 *RULEQUERY* 'query string'::
@@ -51,7 +51,7 @@ Keys are case insensitive but values are not.
     If the USBGuard host is asterisk it will match but this same rule must not contain negation of matched host.
 
     `RULEQUERY (&(cn=Rule*)(|(&(USBGuardHost=host)(!(USBGuardHost=!host)))(&(USBGuardHost=\*)(!(USBGuardHost=!host)))))`
-    
+
 *UPDATEINTERVAL* 'interval'::
     This is an interval that defines periodicity of update.
     The default is one hour.
diff --git a/doc/man/usbguard-rules.conf.5.adoc b/doc/man/usbguard-rules.conf.5.adoc
index f4a31b1..4366f69 100644
--- a/doc/man/usbguard-rules.conf.5.adoc
+++ b/doc/man/usbguard-rules.conf.5.adoc
@@ -54,7 +54,7 @@ Three types of target are recognized:
 === Device Specification
 Except the target, all the other fields of a rule are optional.
 A rule where only the `target` is specified will match any device.
-That allows the policy administator to write an explicit default target.
+That allows the policy administrator to write an explicit default target.
 If no rule from the policy is applicable to the device, an implicit target configured in usbguard-daemon.conf(5) will be used.
 However, if one wants to narrow the applicability of a rule to a set of devices or one device only, it's possible to do so with device attributes and rule conditions.
 
@@ -152,6 +152,11 @@ If the operator is not specified it is set to *equals*.
 *with-connect-type* [operator] { "connect-type" ... }::
     Match a set of USB port/connect_type device attributes.
 
+*label* "label"::
+    Associates arbitrary string with a rule. Label is useful for storing
+    some contextual information about rule or for filtering rules by label.
+    This attribute is not used when testing if a rule applies to a device.
+
 The 'usb-device-id' is a colon delimited pair in the form 'vendor_id:product_id'.
 All USB devices have this ID assigned by the manufacturer and it should uniquely identify a USB product type.
 Both 'vendor_id' and 'product_id' are 16-bit numbers represented in hexadecimal base.
@@ -236,6 +241,11 @@ List of conditions:
     Evaluates always to false.
 
 
+=== Partial rule
+Partial rule is a rule without a rule target.
+Partial rules may by used by some commands of *usbguard* CLI tool.
+
+
 == Initial policy
 Using the *usbguard* CLI tool and its *generate-policy* subcommand, you can generate an initial policy for your system instead of writing one from scratch.
 The tool generates an *allow* policy for all devices connected to the system at the time of execution.
@@ -292,7 +302,7 @@ The following set of rules forms a policy that allows USB flash disks and explic
 ....
 +
 The policy rejects all USB flash disk devices with an interface from the HID/Keyboard, Communications and Wireless classes.
-Note that blacklisting is the wrong approach and you shouldn't just blacklist a set of devices and allow the rest.
+Note that default allow is the wrong approach and you shouldn't just reject a set of devices and allow the rest.
 The policy above assumes that blocking is the default.
 Rejecting a set of devices considered as "bad" is a good approach how to limit the exposure of the OS to such devices as much as possible.
 
diff --git a/doc/man/usbguard.1.adoc b/doc/man/usbguard.1.adoc
index 3c55c87..8984a1c 100644
--- a/doc/man/usbguard.1.adoc
+++ b/doc/man/usbguard.1.adoc
@@ -17,11 +17,11 @@ usbguard set-parameter 'name' 'value'
 
 usbguard list-devices
 
-usbguard allow-device 'id' | 'rule'
+usbguard allow-device 'id' | 'rule' | 'partial-rule'
 
-usbguard block-device 'id' | 'rule'
+usbguard block-device 'id' | 'rule' | 'partial-rule'
 
-usbguard reject-device 'id' | 'rule'
+usbguard reject-device 'id' | 'rule' | 'partial-rule'
 
 usbguard list-rules
 
@@ -81,12 +81,18 @@ Available options:
 *-b, --blocked*::
     List blocked devices.
 
+*-t, --tree*::
+    List devices in a tree format.
+
 *-h, --help*::
     Show help.
 
 
-=== *allow-device* ['OPTIONS'] < 'id' | 'rule' >
-Authorize a device identified by either the device 'id' or a specific 'rule' to interact with the system. A rule might apply to multiple devices. Note that the device 'id' refers to the very first number of the list-devices command output.
+=== *allow-device* ['OPTIONS'] < 'id' | 'rule' | 'partial-rule' >
+Authorize a device to interact with the system.
+The device can be identified by either a device 'id', 'rule' or 'partial-rule' (rule without target).
+Both 'rule' and 'partial-rule' can be used to allow multiple devices at once.
+Note that 'id' refers to the internal device-rule ID (the very first number of the list-devices command output) rather than the device's ID attribute.
 
 Available options:
 
@@ -98,8 +104,11 @@ Available options:
     Show help.
 
 
-=== *block-device* ['OPTIONS'] < 'id' | 'rule' >
-Deauthorize a device identified by either the device 'id' or a specific 'rule'. A rule might apply to multiple devices. Note that the device 'id' refers to the very first number of the list-devices command output.
+=== *block-device* ['OPTIONS'] < 'id' | 'rule' | 'partial-rule' >
+Deauthorize a device.
+The device can be identified by either a device 'id', 'rule' or 'partial-rule' (rule without target).
+Both 'rule' and 'partial-rule' can be used to block multiple devices at once.
+Note that 'id' refers to the internal device-rule ID (the very first number of the list-devices command output) rather than the device's ID attribute.
 
 Available options:
 
@@ -111,8 +120,11 @@ Available options:
     Show help.
 
 
-=== *reject-device* ['OPTIONS'] < 'id' | 'rule' >
-Deauthorize and remove a device identified by either the device 'id' or a specific 'rule'. A rule might apply to multiple devices. Note that the device 'id' refers to the very first number of the list-devices command output.
+=== *reject-device* ['OPTIONS'] < 'id' | 'rule' | 'partial-rule' >
+Deauthorize and remove a device.
+The device can be identified by either a device 'id', 'rule' or 'partial-rule' (rule without target).
+Both 'rule' and 'partial-rule' can be used to reject multiple devices at once.
+Note that 'id' refers to the internal device-rule ID (the very first number of the list-devices command output) rather than the device's ID attribute.
 
 Available options:
 
@@ -261,9 +273,6 @@ Available options:
 *-P, --parameters* 'privileges'::
     Run-time parameter related privileges.
 
-*-N, --no-root-check*::
-    Disable root privileges checking.
-
 *-h, --help*::
     Show help.
 
@@ -276,6 +285,7 @@ The 'privileges' are expected to be in the form of a list separated by a colon:
 ....
 
 Consult the usbguard-daemon.conf(5) man-page for a detailed list of available privileges in each section.
+You can also use 'ALL' instead of 'privileges' to automatically assign all relevant privileges to a given section.
 
 
 === *remove-user* 'name' ['OPTIONS']
@@ -290,9 +300,6 @@ Available options:
 *-g, --group*::
     The specified 'name' represents a groupname or GID.
 
-*-N, --no-root-check*::
-    Disable root privileges checking.
-
 *-h, --help*::
     Show help.
 
diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4
index 6cf482f..9a29eb5 100644
--- a/m4/libgcrypt.m4
+++ b/m4/libgcrypt.m4
@@ -23,7 +23,7 @@ dnl
 AC_DEFUN([AM_PATH_LIBGCRYPT],
 [ AC_REQUIRE([AC_CANONICAL_HOST])
   AC_ARG_WITH(libgcrypt-prefix,
-            AC_HELP_STRING([--with-libgcrypt-prefix=PFX],
+            AS_HELP_STRING([--with-libgcrypt-prefix=PFX],
                            [prefix where LIBGCRYPT is installed (optional)]),
      libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="")
   if test x$libgcrypt_config_prefix != x ; then
diff --git a/scripts/astyle.sh b/scripts/astyle.sh
index ee2c1b9..83254ee 100755
--- a/scripts/astyle.sh
+++ b/scripts/astyle.sh
@@ -20,4 +20,4 @@
 PROJECT_ROOT="$(dirname "$0")/../"
 ASTYLERC_PATH="${PROJECT_ROOT}/src/astylerc"
 
-exec astyle $(< "${ASTYLERC_PATH}") $@
+exec astyle $(< "${ASTYLERC_PATH}") "$@"
diff --git a/scripts/bash_completion/usbguard b/scripts/bash_completion/usbguard
old mode 100755
new mode 100644
index 3b5ca67..312e912
--- a/scripts/bash_completion/usbguard
+++ b/scripts/bash_completion/usbguard
@@ -1,62 +1,387 @@
-# Completion for usbguard
-get_ids()
-{
-    local id_column=4
-    usbguard list-devices 2>/dev/null | cut -d " " -f "${id_column}"
-}
-get_rules()
-{
-    local id_column=1
-    usbguard list-rules 2>/dev/null | cut -d ":" -f "${id_column}"
-}
-_usbguard()
-{
-    local cur prev opts
-    COMPREPLY=()
-    cur="${COMP_WORDS[COMP_CWORD]}"
-    prev="${COMP_WORDS[COMP_CWORD-1]}"
-    opts="list-devices allow-device block-device reject-device list-rules append-rule remove-rule generate-policy watch read-descriptor"
-    case "${prev}" in
-        allow-device|block-device|reject-device)
-        local ids
-        ids=$( get_ids)
-	    # Maybe user running this is not allowed to list devices
-	    if [ $? -ne 0 ]; then
-            COMPREPLY=""
-            return 0
-	    fi
-            COMPREPLY=( $(compgen -W "${ids}"  "${cur}") )
-            return 0
-            ;;
+_usbguard_get_ids() {
+    usbguard list-devices 2>/dev/null | cut -d ":" -f1
+}
 
-        remove-rule)
-        local rules
-        rules=$( get_rules )
-	    # Maybe user running this is not allowed to list rules
-	    if [ $? -ne 0 ]; then
-            COMPREPLY=""
-            return 0
-	    fi
-            COMPREPLY=( $(compgen -W "${rules}"  "${cur}") )
-            return 0
-            ;;
+_usbguard_get_rules() {
+    usbguard list-rules 2>/dev/null | cut -d ":" -f1
+}
 
-        generate-policy)
-            opts=" --with-ports --no-ports-sn --target --no-hashes --hash-only --help"
-            COMPREPLY=( $(compgen -W "${opts}" "${cur}") )
-            return 0
-            ;;
+_usbguard_in_option() {
+    # Determines whether the cursor is currently on an option value (word that
+    # starts with a dash). If it is the case, return true, false otherwise.
+    #
+    # Parameters
+    #   None
+    #
+    # Returns
+    #   retval          (int)           True / False
+    #
+    if [[ "$cur" == -* ]]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+_usbguard_contains() {
+    # Copied from https://github.com/qtc-de/completion-helpers
+    #
+    # Takes two strings containing space separated words and checks if one of the
+    # words in the second list matches one in the first.
+    # E.g.: `_usbguard_contains "test test2" "no nope test"` returns true.
+    #
+    # Parameters
+    #   list_lookup     (string)        Space separated words to search in
+    #   list_search     (string)        Space separated words to search
+    #
+    # Returns
+    #   retval          (int)           Error / Success
+    #
+    # Side effects
+    #   None
+    #
+    for word in $2; do
 
-        list-device)
-            opts=" --allowed --blocked --help"
-            COMPREPLY=( $(compgen -W "${opts}" "${cur}") )
+        if [[ $1 =~ (^|[[:space:]])${word}($|[[:space:]]) ]]; then
             return 0
+        fi
+
+    done
+
+    return 1
+}
+
+_usbguard_short_to_long() {
+    # Copied from https://github.com/qtc-de/completion-helpers
+    #
+    # Takes a short option name and a a list that represents the current option list.
+    # The functions assumes that the long option for a certain short option directly
+    # follows the short option inside the argument list. The corresponding long
+    # option is then returned inside the $long variable.
+    #
+    # Parameters
+    #   short            (string)        Name of the short option (with - prefix)
+    #   opts             (list)          Space separated words (option list)
+    #
+    # Returns
+    #   retval           (int)           Error / Success
+    #
+    # Side effects
+    #   stores the corresponding long option inside the $long variable
+    #
+    local short ret
+
+    short=$1
+    shift
+
+    ret=0
+    long=
+
+    while (( "$#" )); do
+
+        if [[ "$1" == "$short" ]]; then
+            shift
+            if [[ $1 =~ ^--[^[:space:]]+$ ]]; then
+                long=$1
+                return 0
+            fi
+            return 1
+        fi
+
+        shift
+    done
+
+    return 1
+}
+
+_usbguard_filter() {
+    # Copied from https://github.com/qtc-de/completion-helpers
+    #
+    # Takes the name of a variable that contains the current option list as a string.
+    # Iterates over the current command line and removes all options that are already
+    # present.
+    #
+    # A second string of space separated words can be passed
+    # that are excluded from filtering. This is useful, when certain options are allowed
+    # to appear multiple times.
+    #
+    # By default, the function will try to remove long options if their corresponding short
+    # options are used on the command line. To work correctly, this requires that each short
+    # option has a long option that directly follows the short option in the options list. If
+    # this assumption is incorrect, you should set a third parameter for _usbguard_filter to 'false'.
+    #
+    # Parameters
+    #   opts            (string)        Space separated words to filter (call by ref)
+    #   exclude         (string)        Space separated words to exclude
+    #   remove_longs    (string)        Decides whether short-to-long translation is attempted (true|false)
+    #
+    # Returns
+    #   retval           (int)           Error / Success
+    #
+    # Side effects
+    #   filtered option list is stored in first variable (passed by ref)
+    #
+    local Opts=$1 filter exclude cur long remove_longs
+
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    filter=" ${!Opts} "
+    exclude=$2
+    remove_longs=$3
+
+    # iterate over each word inside the current command line
+    for var in ${COMP_LINE}; do
+
+        # exclude the current word to allow full specified options to be space completed
+        if [[ "$var" == "$cur" ]]; then
+            continue
+
+        # exclude words from the exclusion list
+        elif _usbguard_contains "$exclude" $var; then
+            continue
+
+        # otherwise remove the word from the filter list
+        else
+            # if short option, remove long option
+            if [[ "$remove_longs" != "false" && $var =~ ^-[a-zA-Z0-9]+$ ]]; then
+                _usbguard_short_to_long $var $filter && \
+                filter=( "${filter//[[:space:]]${long}[[:space:]]/ }" )
+            fi
+
+            # remove actual option
+            filter=( "${filter//[[:space:]]${var}[[:space:]]/ }" )
+        fi
+
+    done
+
+    _upvars -v $Opts "$filter"
+}
+
+_usbguard_filter_shorts() {
+    # Copied from https://github.com/qtc-de/completion-helpers
+    #
+    # Takes a string of space separated words and removes all short options from it.
+    # Completion of short options is a matter of taste. The maintainers of bash-completion
+    # do not recommend it. Instead, completions should only handle completion for arguments
+    # that are required by short options, but to not complete short options themselves.
+    #
+    # Parameters
+    #   opts            (string)        Space separated option list (call by ref)
+    #
+    # Returns
+    #   retval           (int)           Error / Success
+    #
+    # Side effects
+    #   filtered option list is stored in first variable (passed by ref)
+    #
+    local Opts=$1 filter
+
+    filter=" ${!Opts} "
+
+    for word in $filter; do
+
+        if [[ $word =~ ^-[a-zA-Z0-9]+$ ]]; then
+            filter=( "${filter//[[:space:]]${word}[[:space:]]/ }" )
+        fi
+
+    done
+
+    _upvars -v $Opts "$filter"
+}
+
+
+_usbguard_comp_list() {
+    # This function is used for list completion (comma separated words). If an option
+    # expects a comma separated list of arguments, you can call this function with the
+    # desired set of available list arguments. The function does already populate the
+    # COMPREPLY variable and you normally want to return from the completion script
+    # after calling it.
+    #
+    # Additionally to the array of available options, it is possible to specify an
+    # integer as second argument that specifies the maximum amount of selectable
+    # options. After this amount of options was specified, space completion is enabled.
+    #
+    # Parameters
+    #   options         (string)        Space separated list of available list options
+    #   max             (int)           Maximum number of list items
+    #
+    # Returns
+    #   retval          (int)           Error / Success
+    #
+    local cur prev prev_arr options=" $1 " count=1
+
+    compopt -o nospace
+    cur="${COMP_WORDS[COMP_CWORD]}"
+
+    if [[ "$cur" != ?*,* ]]; then
+        mapfile -t COMPREPLY < <(compgen -W "${options}" -- "${cur}")
+        return 0
+
+    else
+        prev="${cur%,*}"
+        cur="${cur##*,}"
+        prev_arr=${prev//,/ }
+
+        for word in $prev_arr; do
+
+            count=$((count+1))
+            if _usbguard_contains "$options" $word; then
+                options=( "${options//[[:space:]]${word}[[:space:]]/ }" )
+                continue
+            fi
+
+        done
+
+        if [[ $count -ge $2 ]]; then
+            compopt +o nospace
+        fi
+
+        mapfile -t COMPREPLY < <(compgen -P "$prev," -W "${options}" -- "${cur}")
+        return 0
+    fi
+}
+
+_usbguard() {
+    local args cur prev words cword value_options
+
+    _init_completion || return
+    _count_args
+
+    COMPREPLY=()
+
+    # If there was no positional argument provided yet, complete commands
+    if [[ $args -eq 1 ]]; then
+        opts="get-parameter set-parameter list-devices allow-device block-device reject-device list-rules append-rule"
+        opts="${opts} remove-rule generate-policy watch read-descriptor add-user remove-user"
+
+    else
+        opts='-h --help'
+        case "${words[1]}" in
+
+            get-parameter)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    opts="InsertedDevicePolicy ImplicitPolicyTarget"
+                fi
+                ;;
+
+            set-parameter)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    opts="InsertedDevicePolicy ImplicitPolicyTarget"
+
+                elif ! _usbguard_in_option && [[ $args -eq 3 ]]; then
+                    return 0
+
+                else
+                    opts="$opts -v --verbose"
+                fi
+                ;;
+
+            list-devices)
+                opts="$opts -a --allowed -b --blocked -t --tree"
+                ;;
+
+            list-rules)
+                if _usbguard_contains "-l --label" $prev; then
+                    return 0
+                fi
+
+                opts="$opts -d --show-devices -l --label"
+                ;;
+
+            allow-device|block-device|reject-device)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    opts=$(_usbguard_get_ids || :)
+                else
+                    opts="$opts -p --permanent"
+                fi
+                ;;
+
+            append-rule)
+                if _usbguard_contains "-a --after" $prev; then
+                    opts=$(_usbguard_get_rules || :)
+
+                elif ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    return 0
+
+                else
+                    opts="$opts -a --after -t --temporary"
+                fi
+                ;;
+
+            remove-rule)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    opts=$(_usbguard_get_rules || :)
+                fi
+                ;;
+
+            generate-policy)
+                if _usbguard_contains "-d --devpath" $prev; then
+                    _filedir
+                    return 0
+
+                elif _usbguard_contains "-t --target" $prev; then
+                    opts="allow block reject"
+
+                elif _usbguard_contains "-b --usbguardbase -o --objectclass -n --name-prefix" $prev; then
+                    return 0
+
+                else
+                    opts="$opts -p --with-ports -P --no-ports-sn -d --devpath -t --target -X --no-hashes"
+                    opts="$opts -H --hash-only -L --ldif -b --usbguardbase -o --objectclass -n --name-prefix"
+                fi
+                ;;
+
+            watch)
+                if _usbguard_contains "-e --exec" $prev; then
+                    _filedir
+                    return 0
+                fi
+
+                opts="$opts -w --wait -o --once -e --exec"
+                ;;
+
+            read-descriptor)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    _filedir
+                    return 0
+                fi
+                ;;
+
+            add-user)
+                value_options="-p --policy -d --devices -e --exceptions -P --parameters"
+                _count_args "" "@(${value_options// /|})"
+
+                if _usbguard_contains "$value_options" $prev; then
+                    _usbguard_comp_list "modify list listen" 3
+                    return 0
+
+                elif ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    _usergroup
+                    return 0
+
+                else
+                    opts="$opts -u --user -g --group $value_options"
+                fi
+                ;;
+
+            remove-user)
+                if ! _usbguard_in_option && [[ $args -eq 2 ]]; then
+                    _usergroup
+                    return 0
+                fi
+
+                opts="$opts -u --user -g --group"
+                ;;
+
+            *)
             ;;
-        *)
-        ;;
-    esac
+        esac
+    fi
 
-    COMPREPLY=( $(compgen -W "${opts}" "${cur}") )
+    _usbguard_filter "opts"
+    _usbguard_filter_shorts "opts"
+
+    mapfile -t COMPREPLY < <(compgen -W "${opts}" -- "${cur}")
     return 0
 }
+
 complete -F _usbguard usbguard
diff --git a/scripts/reformat-sources.sh b/scripts/reformat-sources.sh
index a476d51..e4e41d3 100755
--- a/scripts/reformat-sources.sh
+++ b/scripts/reformat-sources.sh
@@ -32,5 +32,6 @@ set -ex
 
 find "${PROJECT_ROOT}/src" \
 	-type f -not -path '*ThirdParty/*' \
+	-not \( -name \*.pb.h -o -name build-config.h \) \
 	\( -name '*.cpp' -or -name '*.hpp' -or -name '*.c' -or -name '*.h' \) \
 	-exec "${ASTYLE}" --preserve-date --suffix=none --lineend=linux --formatted "{}" \;
diff --git a/src/CLI/IPCSignalWatcher.cpp b/src/CLI/IPCSignalWatcher.cpp
index b658f91..88013bd 100644
--- a/src/CLI/IPCSignalWatcher.cpp
+++ b/src/CLI/IPCSignalWatcher.cpp
@@ -127,6 +127,28 @@ namespace usbguard
     }
   }
 
+  void IPCSignalWatcher::DevicePolicyApplied(uint32_t id,
+    Rule::Target target_new,
+    const std::string& device_rule,
+    uint32_t rule_id)
+  {
+    std::cout << "[device] PolicyApplied: id=" << id << std::endl;
+    std::cout << " target_new=" << Rule::targetToString(target_new) << std::endl;
+    std::cout << " device_rule=" << device_rule << std::endl;
+    std::cout << " rule_id=" << rule_id << std::endl;
+
+    if (hasOpenExecutable()) {
+      const std::map<std::string, std::string> env = {
+        { "USBGUARD_IPC_SIGNAL", "Device.PolicyApplied" },
+        { "USBGUARD_DEVICE_ID", std::to_string(id) },
+        { "USBGUARD_DEVICE_TARGET_NEW", Rule::targetToString(target_new) },
+        { "USBGUARD_DEVICE_RULE", device_rule },
+        { "USBGUARD_DEVICE_RULE_ID", std::to_string(rule_id) }
+      };
+      runExecutable(env);
+    }
+  }
+
   void IPCSignalWatcher::PropertyParameterChanged(const std::string& name,
     const std::string& value_old,
     const std::string& value_new)
diff --git a/src/CLI/IPCSignalWatcher.hpp b/src/CLI/IPCSignalWatcher.hpp
index 00dcedd..94e4a01 100644
--- a/src/CLI/IPCSignalWatcher.hpp
+++ b/src/CLI/IPCSignalWatcher.hpp
@@ -46,6 +46,11 @@ namespace usbguard
       const std::string& device_rule,
       uint32_t rule_id) override;
 
+    void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id) override;
+
     void PropertyParameterChanged(const std::string& name,
       const std::string& value_old,
       const std::string& value_new) override;
diff --git a/src/CLI/usbguard-add-user.cpp b/src/CLI/usbguard-add-user.cpp
index 67f9b3c..2412ad6 100644
--- a/src/CLI/usbguard-add-user.cpp
+++ b/src/CLI/usbguard-add-user.cpp
@@ -27,13 +27,12 @@
 #include "usbguard/IPCServer.hpp"
 
 #include <iostream>
-
 #include <unistd.h>
 #include <sys/stat.h>
 
 namespace usbguard
 {
-  static const char* options_short = "hugp:d:e:P:N";
+  static const char* options_short = "hugp:d:e:P:";
 
   static const struct ::option options_long[] = {
     { "help", no_argument, nullptr, 'h' },
@@ -43,7 +42,6 @@ namespace usbguard
     { "devices", required_argument, nullptr, 'd' },
     { "exceptions", required_argument, nullptr, 'e' },
     { "parameters", required_argument, nullptr, 'P' },
-    { "no-root-check", no_argument, nullptr, 'N' },
     { nullptr, 0, nullptr, 0 }
   };
 
@@ -58,7 +56,6 @@ namespace usbguard
     stream << "  -d, --devices <privileges>     Device related privileges." << std::endl;
     stream << "  -e, --exceptions <privileges>  Exceptions related privileges." << std::endl;
     stream << "  -P, --parameters <privileges>  Run-time parameter related privileges." << std::endl;
-    stream << "  -N, --no-root-check            Disable root privileges checking." << std::endl;
     stream << "  -h, --help                     Show this help." << std::endl;
     stream << std::endl;
   }
@@ -88,7 +85,6 @@ namespace usbguard
   {
     int opt = 0;
     bool opt_is_group = false;
-    bool opt_no_root_check = false;
     IPCServer::AccessControl access_control;
 
     while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
@@ -121,10 +117,6 @@ namespace usbguard
         access_control.merge(std::string("Parameters=").append(optarg));
         break;
 
-      case 'N':
-        opt_no_root_check = true;
-        break;
-
       case '?':
         showHelp(std::cerr);
 
@@ -141,11 +133,9 @@ namespace usbguard
       return EXIT_FAILURE;
     }
 
-    if (!opt_no_root_check) {
-      if (!(getuid() == 0 && geteuid() == 0)) {
-        USBGUARD_LOG(Error) << "This subcommand requires root privileges. Please retry as root.";
-        return EXIT_FAILURE;
-      }
+    if (!(getuid() == 0 && geteuid() == 0)) {
+      USBGUARD_LOG(Error) << "This subcommand requires root privileges. Please retry as root.";
+      return EXIT_FAILURE;
     }
 
     const std::string name(argv[0]);
diff --git a/src/CLI/usbguard-allow-device.cpp b/src/CLI/usbguard-allow-device.cpp
index b5ff3a7..c66e3fb 100644
--- a/src/CLI/usbguard-allow-device.cpp
+++ b/src/CLI/usbguard-allow-device.cpp
@@ -15,6 +15,7 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
+//          Attila Lakatos <alakatos@redhat.com>
 //
 #ifdef HAVE_BUILD_CONFIG_H
   #include <build-config.h>
@@ -22,94 +23,15 @@
 
 #include "usbguard.hpp"
 #include "usbguard-allow-device.hpp"
-#include "usbguard/RuleParser.hpp"
-#include "Common/Utility.hpp"
-
-#include "usbguard/IPCClient.hpp"
+#include "usbguard-apply-device-policy.hpp"
 
 #include <iostream>
 
 namespace usbguard
 {
-  static const char* options_short = "hp";
-
-  static const struct ::option options_long[] = {
-    { "help", no_argument, nullptr, 'h' },
-    { "permanent", no_argument, nullptr, 'p' },
-    { nullptr, 0, nullptr, 0 }
-  };
-
-  static void showHelp(std::ostream& stream)
-  {
-    stream << " Usage: " << usbguard_arg0 << " allow-device [OPTIONS] (<device-id> | <rule>)" << std::endl;
-    stream << std::endl;
-    stream << " Options:" << std::endl;
-    stream << "  -p, --permanent  Make the decision permanent. A device specific allow" << std::endl;
-    stream << "                   rule will be appended to or updated in the current policy." << std::endl;
-    stream << "  -h, --help       Show this help." << std::endl;
-    stream << std::endl;
-  }
-
   int usbguard_allow_device(int argc, char* argv[])
   {
-    uint32_t id = 0;
-    bool permanent = false;
-    int opt = 0;
-
-    while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
-      switch (opt) {
-      case 'h':
-        showHelp(std::cout);
-        return EXIT_SUCCESS;
-
-      case 'p':
-        permanent = true;
-        break;
-
-      case '?':
-        showHelp(std::cerr);
-
-      default:
-        return EXIT_FAILURE;
-      }
-    }
-
-    argc -= optind;
-    argv += optind;
-    usbguard::IPCClient ipc(/*connected=*/true);
-
-    if (argc == 0) {
-      showHelp(std::cerr);
-      return EXIT_FAILURE;
-    }
-    else if (argc == 1) { /* Allow device by ID */
-      id = std::stoul(argv[0]);
-      ipc.applyDevicePolicy(id, Rule::Target::Allow, permanent);
-    }
-    else { /* Allow device by Rule */
-      //Create a string containing each argument(rule)
-      std::vector<std::string> arguments(argv, argv + argc);
-      std::string rule_string = joinElements(arguments.begin(), arguments.end());
-      usbguard::Rule rule;
-
-      try {
-        rule = Rule::fromString(rule_string);
-      }
-      catch (const usbguard::RuleParserError& ex) {
-        std::cerr << "ERROR: " << ex.what() << std::endl;
-        showHelp(std::cerr);
-        return EXIT_FAILURE;
-      }
-
-      for (auto rule_device : ipc.listDevices(argv[0])) {
-        if (rule.appliesTo(rule_device)) {
-          id = rule_device.getRuleID();
-          ipc.applyDevicePolicy(id, Rule::Target::Allow, permanent);
-        }
-      }
-    }
-
-    return EXIT_SUCCESS;
+    return usbguard_apply_device_policy(argc, argv, Rule::Target::Allow);
   }
 } /* namespace usbguard */
 
diff --git a/src/CLI/usbguard-apply-device-policy.cpp b/src/CLI/usbguard-apply-device-policy.cpp
new file mode 100644
index 0000000..64cbd7d
--- /dev/null
+++ b/src/CLI/usbguard-apply-device-policy.cpp
@@ -0,0 +1,151 @@
+//
+// Copyright (C) 2020 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Attila Lakatos <alakatos@redhat.com>, Zoltan Fridrich <zfridric@redhat.com>
+//
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "usbguard.hpp"
+#include "usbguard-apply-device-policy.hpp"
+#include "Common/Utility.hpp"
+
+#include "usbguard/IPCClient.hpp"
+
+#include <iostream>
+#include <list>
+
+namespace usbguard
+{
+  static const char* options_short = "hp";
+
+  static const struct ::option options_long[] = {
+    { "help", no_argument, nullptr, 'h' },
+    { "permanent", no_argument, nullptr, 'p' },
+    { nullptr, 0, nullptr, 0 }
+  };
+
+  static void showHelp(std::ostream& stream, Rule::Target target)
+  {
+    std::string target_string = Rule::targetToString(target);
+    stream << " Usage: " << usbguard_arg0 << " " << target_string
+      << "-device [OPTIONS] (<id> | <rule> | <partial-rule>)" << std::endl;
+    stream << std::endl;
+    stream << " Options:" << std::endl;
+    stream << "  -p, --permanent  Make the decision permanent. A device specific " << target_string << std::endl;
+    stream << "                   rule will be appended to or updated in the current policy." << std::endl;
+    stream << "  -h, --help       Show this help." << std::endl;
+    stream << std::endl;
+  }
+
+  static bool isNumeric(const std::string& s)
+  {
+    return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) {
+      return !std::isdigit(c);
+    }) == s.end();
+  }
+
+  int usbguard_apply_device_policy(int argc, char** argv, Rule::Target target)
+  {
+    bool permanent = false;
+    int opt = 0;
+
+    while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
+      switch (opt) {
+      case 'h':
+        showHelp(std::cout, target);
+        return EXIT_SUCCESS;
+
+      case 'p':
+        permanent = true;
+        break;
+
+      case '?':
+        showHelp(std::cerr, target);
+
+      default:
+        return EXIT_FAILURE;
+      }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc == 0) {
+      showHelp(std::cerr, target);
+      return EXIT_FAILURE;
+    }
+
+    usbguard::IPCClient ipc(/*connected=*/true);
+
+    /* If a single numeric argument is supplied interpret it as a rule ID */
+    if (argc == 1 && isNumeric(std::string(argv[0]))) {
+      uint32_t id = std::stoul(argv[0]);
+      ipc.applyDevicePolicy(id, target, permanent);
+      return EXIT_SUCCESS;
+    }
+
+    /*
+     * Interpret arguments as a rule/partial-rule
+     */
+    std::string rule_string;
+
+    if (argc == 1) {
+      rule_string = argv[0];
+    }
+    else {
+      std::vector<std::string> arguments(argv, argv + argc);
+      rule_string = joinElements(arguments.begin(), arguments.end());
+    }
+
+    try { /* Check whether rule target has been supplied */
+      std::string rule_target = rule_string.substr(0, rule_string.find(" "));
+      Rule::targetFromString(rule_target);
+    }
+    catch (const std::runtime_error& ex) {
+      /*
+       * The rule contains no target => partial rule
+       * Supply a default match target
+       */
+      rule_string.insert(0, Rule::targetToString(Rule::Target::Match) + " ");
+    }
+
+    for (auto device_rule : ipc.listDevices(rule_string)) {
+      if (target != device_rule.getTarget()) {
+        uint32_t id = device_rule.getRuleID();
+
+        try {
+          ipc.applyDevicePolicy(id, target, permanent);
+        }
+        catch (const usbguard::Exception& ex) {
+          /*
+           * When a parent device is blocked/rejected, all its child
+           * devices are removed from the device map. If we try to apply
+           * device policy to a device whose parent has been
+           * blocked/rejected, therefore this device is not present in
+           * the device map anymore, we will receive an exception.
+           * We ignore such exceptions.
+           */
+        }
+      }
+    }
+
+    return EXIT_SUCCESS;
+  }
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/CLI/usbguard-apply-device-policy.hpp b/src/CLI/usbguard-apply-device-policy.hpp
new file mode 100644
index 0000000..6b5c2a7
--- /dev/null
+++ b/src/CLI/usbguard-apply-device-policy.hpp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2020 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Attila Lakatos <alakatos@redhat.com>
+//
+#pragma once
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "usbguard/RuleParser.hpp"
+
+namespace usbguard
+{
+  int usbguard_apply_device_policy(int argc, char** argv, Rule::Target target);
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/CLI/usbguard-block-device.cpp b/src/CLI/usbguard-block-device.cpp
index 2a1ca05..f3c85f2 100644
--- a/src/CLI/usbguard-block-device.cpp
+++ b/src/CLI/usbguard-block-device.cpp
@@ -15,6 +15,7 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
+//          Attila Lakatos <alakatos@redhat.com>
 //
 #ifdef HAVE_BUILD_CONFIG_H
   #include <build-config.h>
@@ -22,94 +23,15 @@
 
 #include "usbguard.hpp"
 #include "usbguard-block-device.hpp"
-#include "usbguard/RuleParser.hpp"
-#include "Common/Utility.hpp"
-
-#include "usbguard/IPCClient.hpp"
+#include "usbguard-apply-device-policy.hpp"
 
 #include <iostream>
 
 namespace usbguard
 {
-  static const char* options_short = "hp";
-
-  static const struct ::option options_long[] = {
-    { "help", no_argument, nullptr, 'h' },
-    { "permanent", no_argument, nullptr, 'p' },
-    { nullptr, 0, nullptr, 0 }
-  };
-
-  static void showHelp(std::ostream& stream)
-  {
-    stream << " Usage: " << usbguard_arg0 << " block-device [OPTIONS] (<device-id> | <rule>)" << std::endl;
-    stream << std::endl;
-    stream << " Options:" << std::endl;
-    stream << "  -p, --permanent  Make the decision permanent. A device specific block" << std::endl;
-    stream << "                   rule will be appended to or updated in the current policy." << std::endl;
-    stream << "  -h, --help       Show this help." << std::endl;
-    stream << std::endl;
-  }
-
   int usbguard_block_device(int argc, char* argv[])
   {
-    uint32_t id = 0;
-    bool permanent = false;
-    int opt = 0;
-
-    while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
-      switch (opt) {
-      case 'h':
-        showHelp(std::cout);
-        return EXIT_SUCCESS;
-
-      case 'p':
-        permanent = true;
-        break;
-
-      case '?':
-        showHelp(std::cerr);
-
-      default:
-        return EXIT_FAILURE;
-      }
-    }
-
-    argc -= optind;
-    argv += optind;
-    usbguard::IPCClient ipc(/*connected=*/true);
-
-    if (argc == 0) {
-      showHelp(std::cerr);
-      return EXIT_FAILURE;
-    }
-    else if (argc == 1) { /* Block device by ID */
-      id = std::stoul(argv[0]);
-      ipc.applyDevicePolicy(id, Rule::Target::Block, permanent);
-    }
-    else { /* Block device by Rule */
-      //Create a string containing each argument(rule)
-      std::vector<std::string> arguments(argv, argv + argc);
-      std::string rule_string = joinElements(arguments.begin(), arguments.end());
-      usbguard::Rule rule;
-
-      try {
-        rule = Rule::fromString(rule_string);
-      }
-      catch (const usbguard::RuleParserError& ex) {
-        std::cerr << "ERROR: " << ex.what() << std::endl;
-        showHelp(std::cerr);
-        return EXIT_FAILURE;
-      }
-
-      for (auto rule_device : ipc.listDevices(argv[0])) {
-        if (rule.appliesTo(rule_device)) {
-          id = rule_device.getRuleID();
-          ipc.applyDevicePolicy(id, Rule::Target::Block, permanent);
-        }
-      }
-    }
-
-    return EXIT_SUCCESS;
+    return usbguard_apply_device_policy(argc, argv, Rule::Target::Block);
   }
 } /* namespace usbguard */
 
diff --git a/src/CLI/usbguard-list-devices.cpp b/src/CLI/usbguard-list-devices.cpp
index 54eb0d5..0125dd6 100644
--- a/src/CLI/usbguard-list-devices.cpp
+++ b/src/CLI/usbguard-list-devices.cpp
@@ -26,15 +26,18 @@
 #include "usbguard/IPCClient.hpp"
 
 #include <iostream>
+#include <map>
+#include <vector>
 
 namespace usbguard
 {
-  static const char* options_short = "hab";
+  static const char* options_short = "habt";
 
   static const struct ::option options_long[] = {
     { "help", no_argument, nullptr, 'h' },
-    { "blocked", no_argument, nullptr, 'b' },
     { "allowed", no_argument, nullptr, 'a' },
+    { "blocked", no_argument, nullptr, 'b' },
+    { "tree", no_argument, nullptr, 't' },
     { nullptr, 0, nullptr, 0 }
   };
 
@@ -45,14 +48,133 @@ namespace usbguard
     stream << " Options:" << std::endl;
     stream << "  -a, --allowed  List allowed devices." << std::endl;
     stream << "  -b, --blocked  List blocked devices." << std::endl;
+    stream << "  -t, --tree     List devices in a tree format." << std::endl;
     stream << "  -h, --help     Show this help." << std::endl;
     stream << std::endl;
   }
 
+  /**
+   * @brief Prints list of devices in a classic format
+   *
+   * @param rules Device rules to print
+   */
+  static void classicFormat(const std::vector<Rule>& rules)
+  {
+    for (const auto& rule : rules) {
+      std::cout << rule.getRuleID() << ": " << rule.toString() << std::endl;
+    }
+  }
+
+  /**
+   * @brief Recursively prints the tree nodes
+   *
+   * @param tree Rules organized into a tree structure
+   * @param node Node of a tree
+   * @param prefix Helper string used for prefixing the output
+   */
+  static void printNode(
+    const std::map<std::string, std::pair<Rule, std::vector<std::string>>>& tree,
+    const std::pair<Rule, std::vector<std::string>>& node,
+    const std::string& prefix)
+  {
+    const auto& rule = node.first;
+    const auto& children = node.second;
+
+    if (rule) {
+      std::cout << rule.getRuleID() << ": "
+        << Rule::targetToString(rule.getTarget()) << " "
+        << rule.getName() << std::endl;
+    }
+
+    if (children.empty()) {
+      return;
+    }
+
+    for (unsigned i = 0; i < children.size() - 1; ++i) {
+      std::cout << prefix << "├── ";
+      printNode(tree, tree.at(children[i]), prefix + "│   ");
+    }
+
+    std::cout << prefix << "└── ";
+    printNode(tree, tree.at(children.back()), prefix + "    ");
+  }
+
+  /**
+   * @brief Recursively prints the rule tree
+   *
+   * @param tree Rules organized into a tree structure
+   */
+  static void printTree(const std::map<std::string, std::pair<Rule, std::vector<std::string>>>& tree)
+  {
+    std::cout << "." << std::endl;
+    std::vector<std::pair<Rule, std::vector<std::string>>> roots;
+
+    for (const auto& it : tree) {
+      if (!it.second.first) {
+        roots.push_back(it.second);
+      }
+    }
+
+    for (const auto& root : roots) {
+      auto children = root.second;
+
+      for (const auto& child : children) {
+        if (root == roots.back() && child == children.back()) {
+          std::cout << "└── ";
+          printNode(tree, tree.at(child), "    ");
+        }
+        else {
+          std::cout << "├── ";
+          printNode(tree, tree.at(child), "│   ");
+        }
+      }
+    }
+  }
+
+  /**
+   * @brief Prints list of devices in a tree format
+   *
+   * Complexity O(n*log(n)), where n = rules.size
+   *
+   * @param rules Device rules to print
+   */
+  static void treeFormat(const std::vector<Rule>& rules)
+  {
+    /*
+     * key: hash
+     * value: (rule, children_hashes)
+     */
+    std::map<std::string, std::pair<Rule, std::vector<std::string>>> tree;
+
+    for (const auto& rule : rules) {
+      auto hash = rule.getHash();
+      auto p_hash = rule.getParentHash();
+      auto hash_it = tree.find(hash);
+      auto p_hash_it = tree.find(p_hash);
+
+      if (p_hash_it == tree.end()) {
+        tree.insert({p_hash, {{}, {hash}}});
+      }
+      else {
+        p_hash_it->second.second.push_back(hash);
+      }
+
+      if (hash_it == tree.end()) {
+        tree.insert({hash, {rule, {}}});
+      }
+      else {
+        hash_it->second.first = rule;
+      }
+    }
+
+    printTree(tree);
+  }
+
   int usbguard_list_devices(int argc, char* argv[])
   {
     bool list_blocked = false;
     bool list_allowed = false;
+    bool tree_format = false;
     int opt = 0;
 
     while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
@@ -69,6 +191,10 @@ namespace usbguard
         list_blocked = true;
         break;
 
+      case 't':
+        tree_format = true;
+        break;
+
       case '?':
         showHelp(std::cerr);
 
@@ -77,22 +203,20 @@ namespace usbguard
       }
     }
 
-    const bool list_everything = (list_blocked == list_allowed);
     std::string query = "match";
 
-    if (!list_everything) {
-      if (list_allowed) {
-        query = "allow";
-      }
-      else {
-        query = "block";
-      }
+    if (list_blocked != list_allowed) {
+      query = list_allowed ? "allow" : "block";
     }
 
     usbguard::IPCClient ipc(/*connected=*/true);
+    auto device_rules = ipc.listDevices(query);
 
-    for (auto device_rule : ipc.listDevices(query)) {
-      std::cout << device_rule.getRuleID() << ": " << device_rule.toString() << std::endl;
+    if (tree_format) {
+      treeFormat(device_rules);
+    }
+    else {
+      classicFormat(device_rules);
     }
 
     return EXIT_SUCCESS;
diff --git a/src/CLI/usbguard-reject-device.cpp b/src/CLI/usbguard-reject-device.cpp
index 5bd7dde..5975ba5 100644
--- a/src/CLI/usbguard-reject-device.cpp
+++ b/src/CLI/usbguard-reject-device.cpp
@@ -15,6 +15,7 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
+//          Attila Lakatos <alakatos@redhat.com>
 //
 #ifdef HAVE_BUILD_CONFIG_H
   #include <build-config.h>
@@ -22,94 +23,15 @@
 
 #include "usbguard.hpp"
 #include "usbguard-reject-device.hpp"
-#include "usbguard/RuleParser.hpp"
-#include "Common/Utility.hpp"
-
-#include "usbguard/IPCClient.hpp"
+#include "usbguard-apply-device-policy.hpp"
 
 #include <iostream>
 
 namespace usbguard
 {
-  static const char* options_short = "hp";
-
-  static const struct ::option options_long[] = {
-    { "help", no_argument, nullptr, 'h' },
-    { "permanent", no_argument, nullptr, 'p' },
-    { nullptr, 0, nullptr, 0 }
-  };
-
-  static void showHelp(std::ostream& stream)
-  {
-    stream << " Usage: " << usbguard_arg0 << " reject-device [OPTIONS] (<device-id> | <rule>)" << std::endl;
-    stream << std::endl;
-    stream << " Options:" << std::endl;
-    stream << "  -p, --permanent  Make the decision permanent. A device specific reject" << std::endl;
-    stream << "                   rule will be appended to or updated in the current policy." << std::endl;
-    stream << "  -h, --help       Show this help." << std::endl;
-    stream << std::endl;
-  }
-
   int usbguard_reject_device(int argc, char* argv[])
   {
-    uint32_t id = 0;
-    bool permanent = false;
-    int opt = 0;
-
-    while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
-      switch (opt) {
-      case 'h':
-        showHelp(std::cout);
-        return EXIT_SUCCESS;
-
-      case 'p':
-        permanent = true;
-        break;
-
-      case '?':
-        showHelp(std::cerr);
-
-      default:
-        return EXIT_FAILURE;
-      }
-    }
-
-    argc -= optind;
-    argv += optind;
-    usbguard::IPCClient ipc(/*connected=*/true);
-
-    if (argc == 0) {
-      showHelp(std::cerr);
-      return EXIT_FAILURE;
-    }
-    else if (argc == 1) { /* Reject device by ID */
-      id = std::stoul(argv[0]);
-      ipc.applyDevicePolicy(id, Rule::Target::Reject, permanent);
-    }
-    else { /* Reject device by Rule */
-      //Create a string containing each argument(rule)
-      std::vector<std::string> arguments(argv, argv + argc);
-      std::string rule_string = joinElements(arguments.begin(), arguments.end());
-      usbguard::Rule rule;
-
-      try {
-        rule = Rule::fromString(rule_string);
-      }
-      catch (const usbguard::RuleParserError& ex) {
-        std::cerr << "ERROR: " << ex.what() << std::endl;
-        showHelp(std::cerr);
-        return EXIT_FAILURE;
-      }
-
-      for (auto rule_device : ipc.listDevices(argv[0])) {
-        if (rule.appliesTo(rule_device)) {
-          id = rule_device.getRuleID();
-          ipc.applyDevicePolicy(id, Rule::Target::Reject, permanent);
-        }
-      }
-    }
-
-    return EXIT_SUCCESS;
+    return usbguard_apply_device_policy(argc, argv, Rule::Target::Reject);
   }
 } /* namespace usbguard */
 
diff --git a/src/CLI/usbguard-remove-user.cpp b/src/CLI/usbguard-remove-user.cpp
index 01a16f2..67307a5 100644
--- a/src/CLI/usbguard-remove-user.cpp
+++ b/src/CLI/usbguard-remove-user.cpp
@@ -27,18 +27,16 @@
 #include "usbguard/IPCServer.hpp"
 
 #include <iostream>
-
 #include <unistd.h>
 
 namespace usbguard
 {
-  static const char* options_short = "hugN";
+  static const char* options_short = "hug";
 
   static const struct ::option options_long[] = {
     { "help", no_argument, nullptr, 'h' },
     { "user", no_argument, nullptr, 'u' },
     { "group", no_argument, nullptr, 'g' },
-    { "no-root-check", no_argument, nullptr, 'N' },
     { nullptr, 0, nullptr, 0  }
   };
 
@@ -49,7 +47,6 @@ namespace usbguard
     stream << " Options:" << std::endl;
     stream << "  -u, --user           The specified name represents a username or UID (default)." << std::endl;
     stream << "  -g, --group          The specified name represents a groupname or GID." << std::endl;
-    stream << "  -N, --no-root-check  Disable root privileges checking." << std::endl;
     stream << "  -h, --help           Show this help." << std::endl;
     stream << std::endl;
   }
@@ -70,7 +67,6 @@ namespace usbguard
   {
     int opt = 0;
     bool opt_is_group = false;
-    bool opt_no_root_check = false;
 
     while ((opt = getopt_long(argc, argv, options_short, options_long, nullptr)) != -1) {
       switch (opt) {
@@ -86,10 +82,6 @@ namespace usbguard
         showHelp(std::cout);
         return EXIT_SUCCESS;
 
-      case 'N':
-        opt_no_root_check = true;
-        break;
-
       case '?':
         showHelp(std::cerr);
 
@@ -106,11 +98,9 @@ namespace usbguard
       return EXIT_FAILURE;
     }
 
-    if (!opt_no_root_check) {
-      if (!(getuid() == 0 && geteuid() == 0)) {
-        USBGUARD_LOG(Error) << "This subcommand requires root privileges. Please retry as root.";
-        return EXIT_FAILURE;
-      }
+    if (!(getuid() == 0 && geteuid() == 0)) {
+      USBGUARD_LOG(Error) << "This subcommand requires root privileges. Please retry as root.";
+      return EXIT_FAILURE;
     }
 
     const std::string name(argv[0]);
diff --git a/src/CLI/usbguard-rule-parser.cpp b/src/CLI/usbguard-rule-parser.cpp
index dcaacca..7b1f00a 100644
--- a/src/CLI/usbguard-rule-parser.cpp
+++ b/src/CLI/usbguard-rule-parser.cpp
@@ -20,14 +20,24 @@
   #include <build-config.h>
 #endif
 
+#include "usbguard/Exception.hpp"
 #include "usbguard/Rule.hpp"
 #include "usbguard/RuleParser.hpp"
 
-#include <iostream>
-#ifndef _GNU_SOURCE
-  #define _GNU_SOURCE
+#ifdef HAVE_GNU_BASENAME
+  /* GNU version of basename(3) */
+  #ifndef _GNU_SOURCE
+    #define _GNU_SOURCE
+  #endif
+  #include <cstring>
+#else
+  /* POSIX version of basename(3) */
+  #include <libgen.h>
 #endif
-#include <cstring>
+
+#include <cstdlib>  // for free(3)
+#include <cstring>  // for strdup(3)
+#include <iostream>
 #include <fstream>
 
 #include <getopt.h>
@@ -43,14 +53,16 @@ static const struct ::option options_long[] = {
 
 static void showHelp(std::ostream& stream, const char* usbguard_arg0)
 {
-  stream << " Usage: " << ::basename(usbguard_arg0) << " [OPTIONS] <rule_spec>" << std::endl;
-  stream << " Usage: " << ::basename(usbguard_arg0) << " [OPTIONS] -f <file>" << std::endl;
+  char* const writeable_arg0 = ::strdup(usbguard_arg0);
+  stream << " Usage: " << ::basename(writeable_arg0) << " [OPTIONS] <rule_spec>" << std::endl;
+  stream << " Usage: " << ::basename(writeable_arg0) << " [OPTIONS] -f <file>" << std::endl;
   stream << std::endl;
   stream << " Options:" << std::endl;
   stream << "  -f, --file       Interpret the argument as a path to a file that should be parsed." << std::endl;
   stream << "  -t, --trace      Enable parser tracing." << std::endl;
   stream << "  -h, --help       Show this help." << std::endl;
   stream << std::endl;
+  ::free(writeable_arg0);
 }
 
 int main(int argc, char** argv)
@@ -93,8 +105,14 @@ int main(int argc, char** argv)
 
   try {
     if (from_file) {
-      const std::string rule_file(argv[0]);
+      const char* const rule_file_filename = argv[0];
+      const std::string rule_file(rule_file_filename);
       std::ifstream stream(rule_file);
+
+      if (! stream.is_open()) {
+        throw usbguard::ErrnoException("Rules file could not be opened", rule_file_filename, errno);
+      }
+
       size_t line = 0;
 
       while (stream.good()) {
@@ -129,6 +147,9 @@ int main(int argc, char** argv)
     std::cerr << "^-- " << ex.hint() << std::endl;
     std::cerr.width(1);
   }
+  catch (const usbguard::Exception& ex) {
+    std::cerr << "! ERROR: " << ex.message() << std::endl;
+  }
   catch (const std::exception& ex) {
     std::cerr << "! EXCEPTION: " << ex.what() << std::endl;
   }
diff --git a/src/CLI/usbguard.cpp b/src/CLI/usbguard.cpp
index d26cddd..08da644 100644
--- a/src/CLI/usbguard.cpp
+++ b/src/CLI/usbguard.cpp
@@ -26,10 +26,16 @@
 #include <map>
 #include <iostream>
 
-#ifndef _GNU_SOURCE
-  #define _GNU_SOURCE
+#ifdef HAVE_GNU_BASENAME
+  /* GNU version of basename(3) */
+  #ifndef _GNU_SOURCE
+    #define _GNU_SOURCE
+  #endif
+  #include <cstring>
+#else
+  /* POSIX version of basename(3) */
+  #include <libgen.h>
 #endif
-#include <cstring> /* GNU version of basename(3) */
 
 #include "usbguard.hpp"
 #include "usbguard-get-parameter.hpp"
@@ -79,9 +85,9 @@ namespace usbguard
     stream << "  get-parameter <name>           Get the value of a runtime parameter." << std::endl;
     stream << "  set-parameter <name> <value>   Set the value of a runtime parameter." << std::endl;
     stream << "  list-devices                   List all USB devices recognized by the USBGuard daemon." << std::endl;
-    stream << "  allow-device <id>              Authorize a device to interact with the system." << std::endl;
-    stream << "  block-device <id>              Deauthorize a device." << std::endl;
-    stream << "  reject-device <id>             Deauthorize and remove a device from the system." << std::endl;
+    stream << "  allow-device <id|rule|p-rule>  Authorize a device to interact with the system." << std::endl;
+    stream << "  block-device <id|rule|p-rule>  Deauthorize a device." << std::endl;
+    stream << "  reject-device <id|rule|p-rule> Deauthorize and remove a device from the system." << std::endl;
     stream << std::endl;
     stream << "  list-rules                     List the rule set (policy) used by the USBGuard daemon." << std::endl;
     stream << "  append-rule <rule>             Append a rule to the rule set." << std::endl;
diff --git a/src/Common/FDInputStream.hpp b/src/Common/FDInputStream.hpp
index 077d85b..8528e84 100644
--- a/src/Common/FDInputStream.hpp
+++ b/src/Common/FDInputStream.hpp
@@ -29,6 +29,7 @@
 #endif /* !HAVE_EXT_STDIO_FILEBUF_H */
 #include <fstream>
 #include <memory>
+#include <unistd.h>
 
 namespace usbguard
 {
diff --git a/src/Common/LDAPUtil.cpp b/src/Common/LDAPUtil.cpp
index e64a49e..336418a 100644
--- a/src/Common/LDAPUtil.cpp
+++ b/src/Common/LDAPUtil.cpp
@@ -33,6 +33,7 @@ namespace usbguard
     "USBGuardRuleOrder",
     "USBID",
     "USBSerial",
+    "USBWithConnectType",
     "USBName",
     "USBHash",
     "USBParentHash",
@@ -47,6 +48,7 @@ namespace usbguard
     "USBGuardOrder", /* just for indexing */
     "id",
     "serial",
+    "with-connect-type",
     "name",
     "hash",
     "parent-hash",
diff --git a/src/Common/LDAPUtil.hpp b/src/Common/LDAPUtil.hpp
index 86d0a1b..05c8f09 100644
--- a/src/Common/LDAPUtil.hpp
+++ b/src/Common/LDAPUtil.hpp
@@ -38,6 +38,7 @@ namespace usbguard
       USBGuardRuleOrder,
       USBID,
       USBSerial,
+      USBWithConnectType,
       USBName,
       USBHash,
       USBParentHash,
diff --git a/src/Common/Thread.hpp b/src/Common/Thread.hpp
index cee75b0..d1a597d 100644
--- a/src/Common/Thread.hpp
+++ b/src/Common/Thread.hpp
@@ -45,7 +45,7 @@ namespace usbguard
       _method_class_ptr = thread._method_class_ptr;
       _method = thread._method;
       std::swap(_thread, thread._thread);
-      _stop_request = thread._stop_request;
+      _stop_request = thread._stop_request.load();
     }
 
     Thread& operator=(Thread& thread)
@@ -53,7 +53,7 @@ namespace usbguard
       _method_class_ptr = thread._method_class_ptr;
       _method = thread._method;
       std::swap(_thread, thread._thread);
-      _stop_request = thread._stop_request;
+      _stop_request = thread._stop_request.load();
       return *this;
     }
 
diff --git a/src/Common/Utility.cpp b/src/Common/Utility.cpp
index d9fc26a..aee50ce 100644
--- a/src/Common/Utility.cpp
+++ b/src/Common/Utility.cpp
@@ -524,7 +524,7 @@ namespace usbguard
     std::string file_name;
 
     if (!dir_fd) {
-      throw Exception("getConfigsFromDir", "opendir: " + path , strerror(errno));
+      throw Exception("getConfigsFromDir", "opendir: " + path, strerror(errno));
     }
 
     while ((dp = readdir(dir_fd)) != NULL) { // iterate over directory for file entries
@@ -539,8 +539,35 @@ namespace usbguard
 
     // cleanup
     closedir(dir_fd);
+    std::sort(rulefile_list.begin(), rulefile_list.end());
     return rulefile_list;
   }
+
+  bool isValidName(const std::string& name)
+  {
+    const char* s = name.data();
+
+    if (('\0' == *s) ||
+      !((('a' <= *s) && ('z' >= *s)) ||
+        (('A' <= *s) && ('Z' >= *s)) ||
+        ('_' == *s))) {
+      return false;
+    }
+
+    while ('\0' != *++s) {
+      if (!((('a' <= *s) && ('z' >= *s)) ||
+          (('A' <= *s) && ('Z' >= *s)) ||
+          (('0' <= *s) && ('9' >= *s)) ||
+          ('_' == *s) ||
+          ('-' == *s) ||
+          (('$' == *s) && ('\0' == *(s + 1))))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
 } /* namespace usbguard */
 
 /* vim: set ts=2 sw=2 et */
diff --git a/src/Common/Utility.hpp b/src/Common/Utility.hpp
index df1afcd..d49e24d 100644
--- a/src/Common/Utility.hpp
+++ b/src/Common/Utility.hpp
@@ -161,7 +161,7 @@ namespace usbguard
   std::string parentPath(const std::string& path);
 
   /**
-   * Retrun the number of path components.
+   * Return the number of path components.
    */
   std::size_t countPathComponents(const std::string& path);
 
@@ -192,7 +192,7 @@ namespace usbguard
   [](const std::pair<std::string, std::string>& a, const std::pair<std::string, std::string>& b) -> bool {
     return a.first < b.first;
   },
-  bool directory_required = false);
+  bool directory_required = true);
 
   /**
    * Remove prefix from string.
@@ -316,6 +316,15 @@ namespace usbguard
     return ss.str();
   }
 
+  /**
+   * @brief Checks whether a given name is a valid group/user name
+   *
+   * User/group names must match [A-Za-z_][A-Za-z0-9_-]*[$]
+   *
+   * @param name Name to check
+   * @return True if given name is valid, false otherwise
+   */
+  bool isValidName(const std::string& name);
 
 } /* namespace usbguard */
 
diff --git a/src/DBus/DBusBridge.cpp b/src/DBus/DBusBridge.cpp
index 76471e7..891ed09 100644
--- a/src/DBus/DBusBridge.cpp
+++ b/src/DBus/DBusBridge.cpp
@@ -15,12 +15,15 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
+// Authors: Sebastian Pipping <sebastian@pipping.org>
 //
 #ifdef HAVE_BUILD_CONFIG_H
   #include <build-config.h>
 #endif
 
 #include "DBusBridge.hpp"
+#include <polkit/polkit.h>
+
 namespace usbguard
 {
   DBusBridge::DBusBridge(GDBusConnection* const gdbus_connection,
@@ -77,6 +80,10 @@ namespace usbguard
   void DBusBridge::handleRootMethodCall(const std::string& method_name, GVariant* parameters, GDBusMethodInvocation* invocation)
   {
     if (method_name == "getParameter") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       const char* name_cstr = nullptr;
       g_variant_get(parameters, "(&s)", &name_cstr);
       std::string name(name_cstr);
@@ -86,6 +93,10 @@ namespace usbguard
     }
 
     if (method_name == "setParameter") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       const char* name_cstr = nullptr;
       const char* value_cstr = nullptr;
       g_variant_get(parameters, "(&s&s)", &name_cstr, &value_cstr);
@@ -104,6 +115,10 @@ namespace usbguard
   void DBusBridge::handlePolicyMethodCall(const std::string& method_name, GVariant* parameters, GDBusMethodInvocation* invocation)
   {
     if (method_name == "listRules") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       const char* label_cstr = nullptr;
       g_variant_get(parameters, "(&s)", &label_cstr);
       std::string label(label_cstr);
@@ -136,6 +151,10 @@ namespace usbguard
     }
 
     if (method_name == "appendRule") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       const char* rule_spec_cstr = nullptr;
       uint32_t parent_id = 0;
       gboolean temporary = false;
@@ -147,6 +166,10 @@ namespace usbguard
     }
 
     if (method_name == "removeRule") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       uint32_t rule_id = 0;
       g_variant_get(parameters, "(u)", &rule_id);
       removeRule(rule_id);
@@ -162,7 +185,13 @@ namespace usbguard
   void DBusBridge::handleDevicesMethodCall(const std::string& method_name, GVariant* parameters,
     GDBusMethodInvocation* invocation)
   {
+    USBGUARD_LOG(Debug) << "dbus devices method call: " << method_name;
+
     if (method_name == "listDevices") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       const char* query_cstr = nullptr;
       g_variant_get(parameters, "(&s)", &query_cstr);
       std::string query(query_cstr);
@@ -195,10 +224,16 @@ namespace usbguard
     }
 
     if (method_name == "applyDevicePolicy") {
+      if (! isAuthorizedByPolkit(invocation)) {
+        return;
+      }
+
       uint32_t device_id = 0;
       uint32_t target_integer = 0;
       gboolean permanent = false;
       g_variant_get(parameters, "(uub)", &device_id, &target_integer, &permanent);
+      USBGUARD_LOG(Debug) << "DBus: applyDevicePolicy: Parsed device_id: " << device_id << " target_integer: " << target_integer <<
+        " and permanent: " << permanent;
       const Rule::Target target = Rule::targetFromInteger(target_integer);
       const uint32_t rule_id = applyDevicePolicy(device_id, target, permanent);
       g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", rule_id));
@@ -270,6 +305,27 @@ namespace usbguard
     }
   }
 
+  void DBusBridge::DevicePolicyApplied(uint32_t id,
+    Rule::Target target_new,
+    const std::string& device_rule,
+    uint32_t rule_id)
+  {
+    GVariantBuilder* gv_builder_attributes = deviceRuleToAttributes(device_rule);
+    g_dbus_connection_emit_signal(p_gdbus_connection, nullptr,
+      DBUS_DEVICES_PATH, DBUS_DEVICES_INTERFACE, "DevicePolicyApplied",
+      g_variant_new("(uusua{ss})",
+        id,
+        Rule::targetToInteger(target_new),
+        device_rule.c_str(),
+        rule_id,
+        gv_builder_attributes),
+      nullptr);
+
+    if (gv_builder_attributes != nullptr) {
+      g_variant_builder_unref(gv_builder_attributes);
+    }
+  }
+
   void DBusBridge::PropertyParameterChanged(const std::string& name,
     const std::string& value_old,
     const std::string& value_new)
@@ -337,13 +393,118 @@ namespace usbguard
     g_variant_builder_add(builder, "{ss}",
       "with-interface",
       with_interface_string.c_str());
-
     g_variant_builder_add(builder, "{ss}",
       "with-connect-type",
       device_rule.getWithConnectType().c_str());
-
     return builder;
   }
+
+  std::string DBusBridge::formatGError(GError* error)
+  {
+    if (error) {
+      std::stringstream formatGError;
+      formatGError << error->message << " (code " << error->code << ")";
+      return formatGError.str();
+    }
+    else {
+      return "unknown error";
+    }
+  }
+
+  bool DBusBridge::isAuthorizedByPolkit(GDBusMethodInvocation* invocation)
+  {
+    GError* error = NULL;
+    USBGUARD_LOG(Trace) << "Extracting bus name...";
+    const gchar* const /*no-free!*/ bus_name = g_dbus_method_invocation_get_sender (invocation);
+
+    if (! bus_name) {
+      USBGUARD_LOG(Trace) << "Failed to extract bus name.";
+      return false;
+    }
+
+    USBGUARD_LOG(Trace) << "Extracted bus name \"" << bus_name << "\".";
+    USBGUARD_LOG(Trace) << "Extracting interface name...";
+    const gchar* const /*no-free!*/ interfaceName = g_dbus_method_invocation_get_interface_name(invocation);
+
+    if (! interfaceName) {
+      USBGUARD_LOG(Trace) << "Failed to extract interface name.";
+      return false;
+    }
+
+    USBGUARD_LOG(Trace) << "Extracted interface name \"" << interfaceName << "\".";
+    USBGUARD_LOG(Trace) << "Extracting method name...";
+    const gchar* const /*no-free!*/ methodName = g_dbus_method_invocation_get_method_name(invocation);
+
+    if (! methodName) {
+      USBGUARD_LOG(Trace) << "Failed to extract method name.";
+      return false;
+    }
+
+    std::stringstream action_id;
+    action_id << interfaceName << "." << methodName;
+    USBGUARD_LOG(Trace) << "Extracted method name \"" << methodName << "\".";
+    USBGUARD_LOG(Trace) << "Creating a system bus Polkit subject...";
+    PolkitSubject* const subject = polkit_system_bus_name_new(bus_name);
+
+    if (! subject) {
+      USBGUARD_LOG(Trace) << "Failed to create Polkit subject.";
+      return false;
+    }
+
+    USBGUARD_LOG(Trace) << "Created.";
+    USBGUARD_LOG(Trace) << "Connecting with Polkit authority...";
+    PolkitAuthority* const authority = polkit_authority_get_sync(/*cancellable=*/ NULL, &error);
+
+    if (! authority || error) {
+      USBGUARD_LOG(Trace) << "Failed to connect to Polkit authority: " << formatGError(error) << ".";
+      g_error_free(error);
+      g_object_unref(authority);
+      g_object_unref(subject);
+      return false;
+    }
+
+    USBGUARD_LOG(Trace) << "Connected.";
+    USBGUARD_LOG(Trace) << "Customizing Polkit authentication dialog...";
+    PolkitDetails* const details = polkit_details_new();
+
+    if (! details) {
+      USBGUARD_LOG(Trace) << "Failed to customize the Polkit authentication dialog.";
+      g_object_unref(authority);
+      g_object_unref(subject);
+      return false;
+    }
+
+    polkit_details_insert (details, "polkit.message", "This USBGuard action needs authorization");
+    USBGUARD_LOG(Trace) << "Customized.";
+    USBGUARD_LOG(Trace) << "Checking authorization of action \"" << action_id.str() << "\" with Polkit ...";
+    const PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION;
+    PolkitAuthorizationResult* const result = polkit_authority_check_authorization_sync
+      (authority,
+        subject,
+        action_id.str().c_str(),
+        details,
+        flags,
+        /*cancellable=*/ NULL,
+        &error);
+
+    if (! result || error) {
+      USBGUARD_LOG(Trace) << "Failed to check back with Polkit for authoriation: " << formatGError(error) << ".";
+      g_error_free(error);
+      g_object_unref(result);
+      g_object_unref(details);
+      g_object_unref(authority);
+      g_object_unref(subject);
+      return false;
+    }
+
+    gboolean isAuthorized = polkit_authorization_result_get_is_authorized(result);
+    USBGUARD_LOG(Trace) << (isAuthorized ? "Authorized" : "Not authorized") << ".";
+    g_object_unref(result);
+    g_object_unref(details);
+    g_object_unref(authority);
+    g_object_unref(subject);
+    return isAuthorized;
+  }
 } /* namespace usbguard */
 
 /* vim: set ts=2 sw=2 et */
diff --git a/src/DBus/DBusBridge.hpp b/src/DBus/DBusBridge.hpp
index cd370ba..e1eb472 100644
--- a/src/DBus/DBusBridge.hpp
+++ b/src/DBus/DBusBridge.hpp
@@ -63,6 +63,11 @@ namespace usbguard
       const std::string& device_rule,
       uint32_t rule_id) override;
 
+    void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id) override;
+
     void PropertyParameterChanged(const std::string& name,
       const std::string& value_old,
       const std::string& value_new) override;
@@ -83,6 +88,8 @@ namespace usbguard
       bool rule_match,
       uint32_t rule_id);
 
+    static std::string formatGError(GError* error);
+    static bool isAuthorizedByPolkit(GDBusMethodInvocation* invocation);
 
     GDBusConnection* const p_gdbus_connection;
     void(*p_ipc_callback)(bool);
diff --git a/src/DBus/DBusInterface.xml b/src/DBus/DBusInterface.xml
index 3b78fe9..96999bd 100644
--- a/src/DBus/DBusInterface.xml
+++ b/src/DBus/DBusInterface.xml
@@ -152,8 +152,8 @@
       If the permanent flag is set to True, a rule will be appended to the policy or an exiting device
       rule will be modified in order to permanently store the authorization decision.
 
-      Sucessfull exection of this method will cause the DevicePolicyChanged signal to be broadcasted if
-      the device authorization target was different than the applied target.
+      Successful execution of this method will cause the DevicePolicyChanged signal to be broadcasted if
+      the device authorization target was different from the applied target.
       -->
     <method name="applyDevicePolicy">
       <arg name="id" direction="in" type="u"/>
@@ -205,6 +205,7 @@
       DevicePolicyChanged:
        @id: Device id of the device
        @target_old: Previous authorization target in numerical form.
+                    0 = Allow. 1 = Block. 2 = Reject.
        @target_new: Current authorization target in numerical form.
        @device_rule: Device specific rule.
        @rule_id: A rule id of the matched rule. Otherwise a reserved rule id value is used.
@@ -234,6 +235,41 @@
       <arg name="rule_id" direction="out" type="u"/>
       <arg name="attributes" direction="out" type="a{ss}"/>
     </signal>
+
+    <!--
+      DevicePolicyApplied:
+       @id: Device id of the device
+       @target_new: Current authorization target in numerical form.
+                    0 = Allow. 1 = Block. 2 = Reject.
+       @device_rule: Device specific rule.
+       @rule_id: A rule id of the matched rule. Otherwise a reserved rule id value is used.
+                 Reserved values are:
+                     4294967294 (UINT32_MAX - 1) for an implicit rule, e.g. 
+                     ImplicitPolicyTarget or InsertedDevicePolicy.
+       @attributes: A dictionary of device attributes and their values.
+
+      Notify about a change of a USB device.
+      This is a superset of DevicePolicyChanged and will always be thrown
+      when a device is inserted, authorised, or rejected.
+
+      The device attribute dictionary contains the following attributes:
+        - id (the USB device ID in the form VID:PID)
+        - name
+        - serial
+        - via-port
+        - hash
+        - parent-hash
+        - with-interface
+        - with-connect-type (either "hardwired", "hotplug", or the empty string for unknown)
+
+     -->
+    <signal name="DevicePolicyApplied">
+      <arg name="id" direction="out" type="u"/>
+      <arg name="target_new" direction="out" type="u"/>
+      <arg name="device_rule" direction="out" type="s"/>
+      <arg name="rule_id" direction="out" type="u"/>
+      <arg name="attributes" direction="out" type="a{ss}"/>
+    </signal>
   </interface>
 </node>
 
diff --git a/src/DBus/gdbus-server.cpp b/src/DBus/gdbus-server.cpp
index ccdf62a..33620ba 100644
--- a/src/DBus/gdbus-server.cpp
+++ b/src/DBus/gdbus-server.cpp
@@ -20,7 +20,19 @@
   #include <build-config.h>
 #endif
 
-#include <stdlib.h>
+#ifdef HAVE_GNU_BASENAME
+  /* GNU version of basename(3) */
+  #ifndef _GNU_SOURCE
+    #define _GNU_SOURCE
+  #endif
+  #include <cstring>
+#else
+  /* POSIX version of basename(3) */
+  #include <libgen.h>
+#endif
+
+#include <cstdlib>  // e.g. for free(3)
+#include <cstring>  // e.g. for strdup(3)
 #include <iostream>
 #include <getopt.h>
 #include "DBusBridge.hpp"
@@ -208,13 +220,15 @@ static const struct ::option options_long[] = {
 
 static void showHelp(std::ostream& stream)
 {
-  stream << " Usage: " << ::basename(usbguard_arg0) << " [OPTIONS]" << std::endl;
+  char* const writeable_arg0 = ::strdup(usbguard_arg0);
+  stream << " Usage: " << ::basename(writeable_arg0) << " [OPTIONS]" << std::endl;
   stream << std::endl;
   stream << " Options:" << std::endl;
   stream << "  -s, --system   Listen on the system bus." << std::endl;
   stream << "  -S, --session  Listen on the session bus." << std::endl;
   stream << "  -h, --help     Show this help." << std::endl;
   stream << std::endl;
+  ::free(writeable_arg0);
 }
 
 int
diff --git a/src/DBus/org.usbguard1.policy b/src/DBus/org.usbguard1.policy
index ce84239..2b0cebf 100644
--- a/src/DBus/org.usbguard1.policy
+++ b/src/DBus/org.usbguard1.policy
@@ -1,23 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
         "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
-        
+
 <policyconfig>
   <vendor>The USBGuard Project</vendor>
   <vendor_url>https://github.org/USBGuard/usbguard</vendor_url>
 
   <action id="org.usbguard.Policy1.listRules">
     <description>List the rule set (policy) used by the USBGuard daemon</description>
-    <message>Prevents from listing the USBGuard policy</message>
+    <message>Prevents listing the USBGuard policy</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_self_keep_session</allow_active>
+      <allow_active>auth_self_keep</allow_active>
     </defaults>
   </action>
 
   <action id="org.usbguard.Policy1.appendRule">
     <description>Append a new rule to the policy</description>
-    <message>Prevents from appending rules to the USBGuard policy</message>
+    <message>Prevents appending rules to the USBGuard policy</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
       <allow_active>auth_admin</allow_active>
@@ -33,36 +33,36 @@
     </defaults>
   </action>
 
-  <action id="org.usbguard.Devices1.listDevices">
-    <description>List all USB devices recognized by the USBGuard daemon</description>
-    <message>Prevents from listing USB devices recognized by the USBGuard daemon</message>
+  <action id="org.usbguard.Devices1.applyDevicePolicy">
+    <description>Apply a policy to a device in USBGuard</description>
+    <message>Prevents applying a policy to a device in USBGuard</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_self_keep_session</allow_active>
+      <allow_active>auth_admin</allow_active>
     </defaults>
   </action>
 
-  <action id="org.usbguard.Devices1.allowDevice">
-    <description>Authorize a USB device via the USBGuard daemon to interact with the system</description>
-    <message>Prevents from authorizing USB devices via the USBGuard daemon</message>
+  <action id="org.usbguard.Devices1.listDevices">
+    <description>List all USB devices recognized by the USBGuard daemon</description>
+    <message>Prevents listing USB devices recognized by the USBGuard daemon</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_admin</allow_active>
+      <allow_active>auth_self_keep</allow_active>
     </defaults>
   </action>
 
-  <action id="org.usbguard.Devices1.blockDevice">
-    <description>Deauthorize a USB device via the USBGuard daemon</description>
-    <message>Prevents from deauthorizing USB devices via the USBGuard daemon</message>
+  <action id="org.usbguard1.getParameter">
+    <description>Get the value of a runtime parameter</description>
+    <message>Prevents getting values of runtime USBGuard parameters</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
-      <allow_active>auth_admin</allow_active>
+      <allow_active>auth_self_keep</allow_active>
     </defaults>
   </action>
 
-  <action id="org.usbguard.Devices1.rejectDevice">
-    <description>Remove a USB device via the USBGuard daemon</description>
-    <message>Prevents from removing USB devices via the USBGuard daemon</message>
+  <action id="org.usbguard1.setParameter">
+    <description>Set the value of a runtime parameter</description>
+    <message>Prevents setting values of runtime USBGuard parameters</message>
     <defaults>
       <allow_inactive>no</allow_inactive>
       <allow_active>auth_admin</allow_active>
diff --git a/src/Daemon/Daemon.cpp b/src/Daemon/Daemon.cpp
index acc148f..4ec2d93 100644
--- a/src/Daemon/Daemon.cpp
+++ b/src/Daemon/Daemon.cpp
@@ -327,8 +327,13 @@ namespace usbguard
 
     /* IPCAccessControlFiles */
     if (_config.hasSettingValue("IPCAccessControlFiles")) {
-      const std::string value = _config.getSettingValue("IPCAccessControlFiles");
-      loadIPCAccessControlFiles(value);
+      const std::string ipc_dir = _config.getSettingValue("IPCAccessControlFiles");
+
+      if (check_permissions) {
+        checkFolderPermissions(ipc_dir, (S_IRUSR | S_IWUSR));
+      }
+
+      loadIPCAccessControlFiles(ipc_dir);
     }
 
     /* AuditBackend */
@@ -406,6 +411,9 @@ namespace usbguard
     catch (const RuleParserError& ex) {
       throw Exception("Rules", _nss.getSourceInfo(), ex.hint());
     }
+    catch (const Exception& ex) {
+      throw ex;
+    }
     catch (const std::exception& ex) {
       throw Exception("Rules", _nss.getSourceInfo(), ex.what());
     }
@@ -438,12 +446,25 @@ namespace usbguard
   void Daemon::parseIPCAccessControlFilename(const std::string& basename, std::string* const ptr_user,
     std::string* const ptr_group)
   {
+    // There are five supported forms:
+    // - "<user>:<group>"
+    // - "<user>:"
+    // - "<user>"
+    // - ":<group>"
+    // - ":"
     const auto ug_separator = basename.find_first_of(":");
     const bool has_group = ug_separator != std::string::npos;
     const std::string user = basename.substr(0, ug_separator);
     const std::string group = has_group ? basename.substr(ug_separator + 1) : std::string();
-    checkIPCAccessControlName(user);
-    checkIPCAccessControlName(group);
+
+    if (! user.empty()) {
+      checkIPCAccessControlName(user);
+    }
+
+    if (! group.empty()) {
+      checkIPCAccessControlName(group);
+    }
+
     *ptr_user = user;
     *ptr_group = group;
   }
@@ -760,7 +781,7 @@ namespace usbguard
 
     for (auto ruleset : _policy.getRuleSet()) {
       for (auto const& rule : ruleset->getRules()) {
-        if (label.empty() || rule->getLabel() == label) {
+        if (label.empty() || (!rule->attributeLabel().empty() && rule->getLabel() == label)) {
           rules.push_back(*rule);
         }
       }
@@ -844,6 +865,9 @@ namespace usbguard
       _dm->applyDevicePolicy(device->getID(),
         matched_rule->getTarget());
     const bool target_changed = target_old != device_post->getTarget();
+    std::shared_ptr<const Rule> device_rule = \
+      device_post->getDeviceRule(/*with_port=*/true,
+        /*with_parent_hash=*/true);
 
     if (target_changed || matched_rule->getRuleID() == Rule::ImplicitID) {
       if (target_changed) {
@@ -855,9 +879,6 @@ namespace usbguard
         USBGUARD_LOG(Debug) << "Implicit rule matched";
       }
 
-      std::shared_ptr<const Rule> device_rule = \
-        device_post->getDeviceRule(/*with_port=*/true,
-          /*with_parent_hash=*/true);
       DevicePolicyChanged(device->getID(),
         target_old,
         device_post->getTarget(),
@@ -865,6 +886,10 @@ namespace usbguard
         matched_rule->getRuleID());
     }
 
+    DevicePolicyApplied(device->getID(),
+      device_post->getTarget(),
+      device_rule->toString(),
+      matched_rule->getRuleID());
     matched_rule->updateMetaDataCounters(/*applied=*/true);
     audit_event.success();
   }
@@ -1030,8 +1055,8 @@ namespace usbguard
 
     /* Generate a match rule for upsert */
     std::shared_ptr<Rule> match_rule = device->getDeviceRule(/*with-port=*/false,
-							     /*with-parent-hash=*/false,
-							     /*match_rule=*/true);
+        /*with-parent-hash=*/false,
+        /*match_rule=*/true);
     const std::string match_spec = match_rule->toString();
     USBGUARD_LOG(Debug) << "match_spec=" << match_spec;
     /* Generate new device rule */
diff --git a/src/Daemon/LDAPHandler.cpp b/src/Daemon/LDAPHandler.cpp
index c200c81..4ee5f36 100644
--- a/src/Daemon/LDAPHandler.cpp
+++ b/src/Daemon/LDAPHandler.cpp
@@ -173,6 +173,7 @@ namespace usbguard
 
           case LDAPUtil::LDAP_KEY_INDEX::USBID:
           case LDAPUtil::LDAP_KEY_INDEX::USBSerial:
+          case LDAPUtil::LDAP_KEY_INDEX::USBWithConnectType:
           case LDAPUtil::LDAP_KEY_INDEX::USBName:
           case LDAPUtil::LDAP_KEY_INDEX::USBHash:
           case LDAPUtil::LDAP_KEY_INDEX::USBParentHash:
diff --git a/src/Daemon/NSHandler.cpp b/src/Daemon/NSHandler.cpp
index 9cd0845..92783e9 100644
--- a/src/Daemon/NSHandler.cpp
+++ b/src/Daemon/NSHandler.cpp
@@ -79,7 +79,7 @@ namespace usbguard
         ret = "SourceLOCAL::" + _rulesPath;
       }
       else {
-        ret = "SourceLOCAL::uknown";
+        ret = "SourceLOCAL::unknown";
       }
 
       break;
@@ -154,7 +154,10 @@ namespace usbguard
     std::ifstream nss(_nsswitch_path);
 
     if (!nss.is_open()) {
-      throw ErrnoException("NSSwitch parsing", _nsswitch_path, errno);
+      USBGUARD_LOG(Info) << "Error when opening nsswitch file: " << _nsswitch_path << ": " << ErrnoException::reasonFromErrno(errno);
+      USBGUARD_LOG(Info) << "Using default value FILES";
+      _source = SourceType::LOCAL;
+      return;
     }
 
     _parser.parseStream(nss);
diff --git a/src/Daemon/RuleSetFactory.cpp b/src/Daemon/RuleSetFactory.cpp
index 16f3cd6..8d96417 100644
--- a/src/Daemon/RuleSetFactory.cpp
+++ b/src/Daemon/RuleSetFactory.cpp
@@ -74,7 +74,8 @@ namespace usbguard
           ruleSet.push_back(rs);
         }
       }
-      else if (ns.getRulesPath().empty()){
+
+      if (ruleSet.empty()) {
         USBGUARD_LOG(Warning) << "RuleFile not set; Modification of the permanent policy won't be possible.";
         ruleSet = generateDefaultRuleSet();
       }
diff --git a/src/Daemon/main.cpp b/src/Daemon/main.cpp
index aec5c01..616af0d 100644
--- a/src/Daemon/main.cpp
+++ b/src/Daemon/main.cpp
@@ -62,7 +62,7 @@ static void printUsage(std::ostream& stream, const char* arg0)
   stream << "  -P         Disable permissions check on conf and policy files" << std::endl;
   stream << "             (default: /etc/usbguard/usbguard-daemon.conf)" << std::endl;
   stream << "  -C         Drop capabilities to limit privileges of the process." << std::endl;
-  stream << "  -W         Use a seccomp whitelist to limit available syscalls to the process." << std::endl;
+  stream << "  -W         Use a seccomp allowlist to limit available syscalls to the process." << std::endl;
   stream << "  -h         Show this usage screen." << std::endl;
   stream << std::endl;
 }
@@ -74,7 +74,7 @@ int main(int argc, char* argv[])
   bool log_syslog = false;
   bool log_console = true;
   bool log_file = false;
-  bool use_seccomp_whitelist = false;
+  bool use_seccomp_allowlist = false;
   bool drop_capabilities = false;
   bool check_permissions = true;
   bool daemonize = false;
@@ -124,7 +124,7 @@ int main(int argc, char* argv[])
       break;
 
     case 'W':
-      use_seccomp_whitelist = true;
+      use_seccomp_allowlist = true;
       break;
 
     case 'C':
@@ -159,17 +159,26 @@ int main(int argc, char* argv[])
     return EXIT_FAILURE;
   }
 
-  /* Setup seccomp whitelist & drop capabilities */
-  if (use_seccomp_whitelist) {
+  /* Setup seccomp allowlist & drop capabilities */
+  if (use_seccomp_allowlist) {
+#if defined(HAVE_SECCOMP)
+
     if (!setupSeccompWhitelist()) {
+      USBGUARD_LOG(Error) << "Unable to setup seccomp";
       return EXIT_FAILURE;
     }
+
+#else
+    USBGUARD_LOG(Error) << "USBGuard was not compiled with libseccomp support";
+    return EXIT_FAILURE;
+#endif
   }
 
   if (drop_capabilities) {
 #if defined(HAVE_LIBCAPNG)
     setupCapabilities();
 #else
+    USBGUARD_LOG(Error) << "USBGuard was not compiled with libcap-ng support";
     return EXIT_FAILURE;
 #endif
   }
@@ -203,7 +212,7 @@ int main(int argc, char* argv[])
     USBGUARD_LOG(Error) << ex.what();
   }
   catch (...) {
-    USBGUARD_LOG(Error) << "Unknown excepton caught while starting the process";
+    USBGUARD_LOG(Error) << "Unknown exception caught while starting the process";
   }
 
   return ret;
diff --git a/src/Library/DeviceBase.cpp b/src/Library/DeviceBase.cpp
new file mode 100644
index 0000000..ddf7917
--- /dev/null
+++ b/src/Library/DeviceBase.cpp
@@ -0,0 +1,260 @@
+//
+// Copyright (C) 2015 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Daniel Kopecek <dkopecek@redhat.com>
+//
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "DeviceBase.hpp"
+#include "DeviceManagerBase.hpp"
+#include "SysFSDevice.hpp"
+#include "Common/FDInputStream.hpp"
+#include "Common/Utility.hpp"
+
+#include "usbguard/Exception.hpp"
+#include "usbguard/Logger.hpp"
+#include "usbguard/USB.hpp"
+
+namespace usbguard
+{
+
+  DeviceBase::DeviceBase(DeviceManagerBase& device_manager, SysFSDevice& sysfs_device)
+    : Device(device_manager)
+  {
+    /*
+     * Look for the parent USB device and set the parent id if we find one.
+     */
+    const std::string sysfs_parent_path(sysfs_device.getParentPath());
+    const SysFSDevice sysfs_parent_device(sysfs_parent_path);
+
+    if (sysfs_parent_device.getUEvent().getAttribute("DEVTYPE") == "usb_device") {
+      setParentID(device_manager.getIDFromSysfsPath(sysfs_parent_path));
+    }
+    else {
+      setParentID(Rule::RootID);
+      setParentHash(hashString(sysfs_parent_path));
+    }
+
+    /*
+     * Set name
+     */
+    setName(sysfs_device.readAttribute("product", /*strip_last_null=*/true, /*optional=*/true));
+    /*
+     * Set USB ID
+     */
+    const std::string id_vendor(sysfs_device.readAttribute("idVendor", /*strip_last_null=*/true));
+    const std::string id_product(sysfs_device.readAttribute("idProduct", /*strip_last_null=*/true));
+    const USBDeviceID device_id(id_vendor, id_product);
+    setDeviceID(device_id);
+    /*
+     * Set serial number
+     */
+    setSerial(sysfs_device.readAttribute("serial", /*strip_last_null=*/true, /*optional=*/true));
+    /*
+     * Set USB port
+     */
+    setPort(sysfs_device.getName());
+    /*
+     * Sync authorization target
+     */
+    const std::string authorized_value(sysfs_device.readAttribute("authorized", /*strip_last_null=*/true));
+
+    if (authorized_value == "0") {
+      setTarget(Rule::Target::Block);
+    }
+    else if (authorized_value == "1") {
+      setTarget(Rule::Target::Allow);
+    }
+    else {
+      /*
+       * Block the device if we get an unexpected value
+       */
+      setTarget(Rule::Target::Block);
+    }
+
+    /*
+     * Set connect type
+     */
+    int retry = 0;
+    std::string connect_type;
+    /*
+     * Host controllers are directly connected to SoC/PCI bus
+     * hence do not have port/connect_type in sysfs.
+     * Filter out host controllers to avoid unnecessary wait in the loop.
+     */
+    const auto before = std::chrono::steady_clock::now();
+
+    if (!hasPrefix(getPort(), "usb")) {
+      while (retry < 30) {
+        connect_type = sysfs_device.readAttribute("port/connect_type", /*strip_last_null=*/true, /*optional=*/true);
+
+        if (connect_type != "") {
+          break;
+        }
+
+        USBGUARD_LOG(Trace) << "connect_type is empty, will attempt again";
+        usleep(1000);
+        retry++;
+      }
+
+      const auto after = std::chrono::steady_clock::now();
+      const auto duration_nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(after - before).count();
+      USBGUARD_LOG(Info) << "***Setting connect type=" << connect_type <<" after "<< retry << " retries (took " <<
+        duration_nanoseconds << "ns)";
+
+      if (connect_type == "") {
+        USBGUARD_LOG(Warning) << "port/connect_type is empty for non host controller device " << getPort() << " after waiting for " <<
+          duration_nanoseconds << "ns";
+      }
+    }
+
+    setConnectType(connect_type);
+    /*
+     * Process USB descriptor data.
+     *
+     * FDInputStream (stdio_filebuf) is responsible for closing the file
+     * descriptor returned by sysfs_device.openAttribute().
+     *
+     */
+    FDInputStream descriptor_stream(sysfs_device.openAttribute("descriptors"));
+
+    if (!descriptor_stream.good()) {
+      throw ErrnoException("DeviceBase", sysfs_device.getPath(), errno);
+    }
+
+    initializeHash();
+    USBDescriptorParser parser(*this);
+
+    if (parser.parse(descriptor_stream) < sizeof(USBDeviceDescriptor)) {
+      throw Exception("DeviceBase", sysfs_device.getPath(),
+        "USB descriptor parser processed less data than the size of a USB device descriptor");
+    }
+
+    finalizeHash();
+    /*
+     * From now on we take ownership of the SysFSDevice instance.
+     */
+    _sysfs_device = std::move(sysfs_device);
+  }
+
+  SysFSDevice& DeviceBase::sysfsDevice()
+  {
+    return _sysfs_device;
+  }
+
+  const std::string& DeviceBase::getSysPath() const
+  {
+    return _sysfs_device.getPath();
+  }
+
+  bool DeviceBase::isController() const
+  {
+    if (getPort().substr(0, 3) != "usb" || getInterfaceTypes().size() != 1) {
+      return false;
+    }
+
+    const USBInterfaceType hub_interface("09:00:*");
+    return hub_interface.appliesTo(getInterfaceTypes()[0]);
+  }
+
+  std::string DeviceBase::getSystemName() const
+  {
+    return getSysPath();
+  }
+
+  void DeviceBase::parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
+    USBDescriptor* descriptor_out)
+  {
+    USBGUARD_LOG(Trace);
+    USBDescriptorParserHooks::parseUSBDescriptor(parser, descriptor_raw, descriptor_out);
+
+    if (isLinuxRootHubDeviceDescriptor(descriptor_out)) {
+      updateHashLinuxRootHubDeviceDescriptor(descriptor_raw);
+    }
+    else {
+      updateHash(descriptor_raw, static_cast<size_t>(descriptor_raw->bHeader.bLength));
+    }
+  }
+
+  void DeviceBase::loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor)
+  {
+    const auto type = static_cast<USBDescriptorType>(descriptor->bHeader.bDescriptorType);
+
+    switch (type) {
+    case USBDescriptorType::Device:
+      loadDeviceDescriptor(parser, descriptor);
+      break;
+
+    case USBDescriptorType::Configuration:
+      loadConfigurationDescriptor(parser, descriptor);
+      break;
+
+    case USBDescriptorType::Interface:
+      loadInterfaceDescriptor(parser, descriptor);
+      break;
+
+    case USBDescriptorType::Endpoint:
+      loadEndpointDescriptor(parser, descriptor);
+      break;
+
+    case USBDescriptorType::AssociationInterface:
+    case USBDescriptorType::Unknown:
+    case USBDescriptorType::String:
+    default:
+      USBGUARD_LOG(Debug) << "Ignoring descriptor: type=" << (int)type
+        << " size=" << descriptor->bHeader.bLength;
+    }
+  }
+
+  bool DeviceBase::isLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
+  {
+    USBGUARD_LOG(Trace);
+
+    if (descriptor->bHeader.bDescriptorType != USB_DESCRIPTOR_TYPE_DEVICE) {
+      return false;
+    }
+
+    const USBDeviceDescriptor* const device_descriptor = \
+      reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
+
+    if (device_descriptor->idVendor == 0x1d6b /* Linux Foundation */) {
+      switch (device_descriptor->idProduct) {
+      case 0x0001: /* 1.1 root hub */
+      case 0x0002: /* 2.0 root hub */
+      case 0x0003: /* 3.0 root hub */
+        return true;
+
+      default:
+        return false;
+      }
+    }
+
+    return false;
+  }
+
+  void DeviceBase::updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
+  {
+    USBGUARD_LOG(Trace);
+    USBDeviceDescriptor descriptor_modified = *reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
+    descriptor_modified.bcdDevice = 0;
+    updateHash(&descriptor_modified, sizeof descriptor_modified);
+  }
+
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/Library/DeviceBase.hpp b/src/Library/DeviceBase.hpp
new file mode 100644
index 0000000..e501f39
--- /dev/null
+++ b/src/Library/DeviceBase.hpp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2016 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Daniel Kopecek <dkopecek@redhat.com>
+//
+#pragma once
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "SysFSDevice.hpp"
+
+#include "usbguard/Device.hpp"
+#include "usbguard/USB.hpp"
+
+namespace usbguard
+{
+  class DeviceManagerBase;
+
+  class DeviceBase : public Device, public USBDescriptorParserHooks
+  {
+  public:
+    DeviceBase(DeviceManagerBase& device_manager, SysFSDevice& sysfs_device);
+
+    SysFSDevice& sysfsDevice();
+    const std::string& getSysPath() const;
+    bool isController() const override;
+    std::string getSystemName() const override;
+
+  private:
+    void parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
+      USBDescriptor* descriptor_out) override;
+    void loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor) override;
+    bool isLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
+    void updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
+
+    SysFSDevice _sysfs_device;
+  };
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/Library/DeviceManagerBase.cpp b/src/Library/DeviceManagerBase.cpp
new file mode 100644
index 0000000..b2bcc94
--- /dev/null
+++ b/src/Library/DeviceManagerBase.cpp
@@ -0,0 +1,403 @@
+//
+// Copyright (C) 2015 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Daniel Kopecek <dkopecek@redhat.com>
+//
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "Common/Utility.hpp"
+#include "DeviceBase.hpp"
+#include "DeviceManagerBase.hpp"
+#include "SysFSDevice.hpp"
+
+#include "usbguard/Logger.hpp"
+#include "usbguard/Exception.hpp"
+
+#include <stdexcept>
+#include <fstream>
+#include <chrono>
+
+#include <limits.h>
+#include <linux/netlink.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+namespace usbguard
+{
+
+  DeviceManagerBase::DeviceManagerBase(DeviceManagerHooks& hooks)
+    : DeviceManager(hooks),
+      _uevent_fd(-1),
+      _wakeup_fd(-1),
+      _enumeration(false)
+  {
+    setEnumerationOnlyMode(/*state=*/false);
+  }
+
+  DeviceManagerBase::~DeviceManagerBase() = default;
+
+  void DeviceManagerBase::setEnumerationOnlyMode(bool state)
+  {
+    _enumeration_only_mode = state;
+  }
+
+  std::shared_ptr<Device> DeviceManagerBase::applyDevicePolicy(uint32_t id, Rule::Target target)
+  {
+    USBGUARD_LOG(Trace) << "id=" << id << " target=" << Rule::targetToString(target);
+    std::shared_ptr<DeviceBase> device = std::static_pointer_cast<DeviceBase>(getDevice(id));
+    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
+    sysfsApplyTarget(device->sysfsDevice(), target);
+    device->setTarget(target);
+    return device;
+  }
+
+  void DeviceManagerBase::insertDevice(std::shared_ptr<DeviceBase> device)
+  {
+    DeviceManager::insertDevice(std::static_pointer_cast<Device>(device));
+    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
+    learnSysfsPath(device->getSysPath(), device->getID());
+  }
+
+  std::shared_ptr<Device> DeviceManagerBase::removeDevice(const std::string& syspath)
+  {
+    /*
+     * FIXME: device map locking
+     */
+    if (!knownSysfsPath(syspath)) {
+      throw Exception("removeDevice", syspath, "unknown syspath, cannot remove device");
+    }
+
+    std::shared_ptr<Device> device = DeviceManager::removeDevice(getIDFromSysfsPath(syspath));
+    forgetSysfsPath(syspath);
+    return device;
+  }
+
+  uint32_t DeviceManagerBase::getIDFromSysfsPath(const std::string& sysfs_path) const
+  {
+    uint32_t id = 0;
+
+    if (knownSysfsPath(sysfs_path, &id)) {
+      return id;
+    }
+
+    throw Exception("DeviceManagerBase", sysfs_path, "unknown sysfs path");
+  }
+
+  std::string DeviceManagerBase::ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry)
+  {
+#if defined(_DIRENT_HAVE_D_TYPE)
+
+    if (direntry->d_type != DT_UNKNOWN) {
+      switch (direntry->d_type) {
+      case DT_LNK:
+        return symlinkPath(filepath);
+
+      case DT_DIR:
+        return filepath;
+
+      default:
+        return std::string();
+      }
+    }
+    else {
+      /*
+       * Unknown type. We have to call lstat.
+       */
+#endif
+      struct stat st = {};
+
+      if (lstat(filepath.c_str(), &st) != 0) {
+        /*
+         * Cannot stat, skip this entry.
+         */
+        USBGUARD_LOG(Warning) << "lstat(" << filepath << "): errno=" << errno;
+        return std::string();
+      }
+
+      if (S_ISLNK(st.st_mode)) {
+        return symlinkPath(filepath, &st);
+      }
+      else if (S_ISDIR(st.st_mode)) {
+        return filepath;
+      }
+      else {
+        return std::string();
+      }
+
+#if defined(_DIRENT_HAVE_D_TYPE)
+    }
+
+#endif
+  }
+
+  int DeviceManagerBase::ueventOpen()
+  {
+    int socket_fd = -1;
+    USBGUARD_SYSCALL_THROW("UEvent device manager",
+      (socket_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0);
+
+    try {
+      const int optval = 1;
+      USBGUARD_SYSCALL_THROW("UEvent device manager",
+        setsockopt(socket_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof optval) != 0);
+      /*
+       * Set a 1MiB receive buffer on the netlink socket to avoid ENOBUFS error
+       * in recvmsg.
+       */
+      const size_t rcvbuf_max = 1024 * 1024;
+      USBGUARD_SYSCALL_THROW("UEvent device manager",
+        setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_max, sizeof rcvbuf_max) != 0);
+      struct sockaddr_nl sa = {};
+      sa.nl_family = AF_NETLINK;
+      sa.nl_pid = getpid();
+      sa.nl_groups = -1;
+      USBGUARD_SYSCALL_THROW("UEvent device manager",
+        bind(socket_fd, reinterpret_cast<const sockaddr*>(&sa), sizeof sa) != 0);
+    }
+    catch (...) {
+      (void)close(socket_fd);
+      throw;
+    }
+
+    return socket_fd;
+  }
+
+  void DeviceManagerBase::setDeviceAuthorizedDefault(SysFSDevice* device, DeviceManager::AuthorizedDefaultType auth_default)
+  {
+    if (auth_default == DeviceManager::AuthorizedDefaultType::Keep) {
+      return;
+    }
+
+    std::string auth_default_str = std::to_string(DeviceManager::authorizedDefaultTypeToInteger(auth_default));
+    device->setAttribute("authorized_default", auth_default_str);
+
+    if (device->readAttribute("authorized_default", /*strip_last_null=*/true) != auth_default_str) {
+      if (auth_default == DeviceManager::AuthorizedDefaultType::Internal) {
+        USBGUARD_LOG(Warning) << "No kernel support for authorized_default = 2, falling back to 0";
+        setDeviceAuthorizedDefault(device, DeviceManager::AuthorizedDefaultType::None);
+      }
+      else {
+        throw Exception("DeviceBase", device->getPath(), "Failed to set authorized_default to \"" + auth_default_str + "\"");
+      }
+    }
+  }
+
+  void DeviceManagerBase::sysfsAuthorizeDevice(SysFSDevice& sysfs_device)
+  {
+    sysfs_device.setAttribute("authorized", "1");
+  }
+
+  void DeviceManagerBase::sysfsDeauthorizeDevice(SysFSDevice& sysfs_device)
+  {
+    sysfs_device.setAttribute("authorized", "0");
+  }
+
+  void DeviceManagerBase::sysfsRemoveDevice(SysFSDevice& sysfs_device)
+  {
+    sysfs_device.setAttribute("remove", "1");
+  }
+
+  void DeviceManagerBase::sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target)
+  {
+    switch (target) {
+    case Rule::Target::Allow:
+      sysfsAuthorizeDevice(sysfs_device);
+      break;
+
+    case Rule::Target::Block:
+      sysfsDeauthorizeDevice(sysfs_device);
+      break;
+
+    case Rule::Target::Reject:
+      sysfsRemoveDevice(sysfs_device);
+      break;
+
+    case Rule::Target::Match:
+    case Rule::Target::Device:
+    case Rule::Target::Unknown:
+    case Rule::Target::Empty:
+    case Rule::Target::Invalid:
+    default:
+      throw std::runtime_error("Unknown rule target in applyDevicePolicy");
+    }
+  }
+
+  void DeviceManagerBase::processDevicePresence(const uint32_t id)
+  {
+    USBGUARD_LOG(Trace) << "id=" << id;
+
+    try {
+      std::shared_ptr<DeviceBase> device = \
+        std::static_pointer_cast<DeviceBase>(DeviceManager::getDevice(id));
+      device->sysfsDevice().reload();
+      /*
+       * TODO: Check attribute state
+       *  - authorized_default (in case of controller)
+       */
+      DeviceEvent(DeviceManager::EventType::Present, device);
+      return;
+    }
+    catch (const Exception& ex) {
+      USBGUARD_LOG(Error) << "Present device exception: " << ex.message();
+      DeviceException(ex.message());
+    }
+    catch (const std::exception& ex) {
+      USBGUARD_LOG(Error) << "Present device exception: " << ex.what();
+      DeviceException(ex.what());
+    }
+    catch (...) {
+      USBGUARD_LOG(Error) << "BUG: Unknown device exception.";
+      DeviceException("BUG: Unknown device exception.");
+    }
+
+    /*
+     * We don't reject the device here (as is done in processDeviceInsertion)
+     * because the device was already connected to the system when USBGuard
+     * started. Therefore, if the device is malicious, it already had a chance
+     * to interact with the system.
+     */
+  }
+
+  void DeviceManagerBase::processDeviceInsertion(SysFSDevice& sysfs_device, const bool signal_present)
+  {
+    USBGUARD_LOG(Trace) << "sysfs_device=" << sysfs_device.getPath();
+
+    try {
+      std::shared_ptr<DeviceBase> device = std::make_shared<DeviceBase>(*this, sysfs_device);
+      DeviceManager::AuthorizedDefaultType auth_default = getAuthorizedDefault();
+
+      if (device->isController() && !_enumeration_only_mode) {
+        USBGUARD_LOG(Debug) << "Setting default blocked state for controller device to " <<
+          DeviceManager::authorizedDefaultTypeToString(auth_default);
+        setDeviceAuthorizedDefault(&device->sysfsDevice(), auth_default);
+      }
+
+      insertDevice(device);
+
+      /*
+       * Signal insertions as presence if device enumeration hasn't
+       * completed yet.
+       */
+      if (signal_present) {
+        DeviceEvent(DeviceManager::EventType::Present, device);
+      }
+      else {
+        DeviceEvent(DeviceManager::EventType::Insert, device);
+      }
+
+      return;
+    }
+    catch (const Exception& ex) {
+      USBGUARD_LOG(Error) << "Device insert exception: " << ex.message();
+      DeviceException(ex.message());
+    }
+    catch (const std::exception& ex) {
+      USBGUARD_LOG(Error) << "Device insert exception: " << ex.what();
+      DeviceException(ex.what());
+    }
+    catch (...) {
+      USBGUARD_LOG(Error) << "BUG: Unknown device insert exception.";
+      DeviceException("BUG: Unknown device insert exception.");
+    }
+
+    /*
+     * Skip device reject when in enumeration only mode.
+     */
+    if (_enumeration_only_mode) {
+      return;
+    }
+
+    /*
+     * Something went wrong and an exception was generated.
+     * Either the device is malicious or the system lacks some
+     * resources to successfully process the device. In either
+     * case, we take the safe route and fallback to rejecting
+     * the device.
+     */
+    USBGUARD_LOG(Warning) << "Rejecting device at syspath=" << sysfs_device.getPath();
+    sysfsApplyTarget(sysfs_device, Rule::Target::Reject);
+  }
+
+  void DeviceManagerBase::processDeviceRemoval(const std::string& sysfs_devpath)
+  {
+    USBGUARD_LOG(Trace) << "sysfs_devpath=" << sysfs_devpath;
+
+    try {
+      std::shared_ptr<Device> device = removeDevice(sysfs_devpath);
+      DeviceEvent(DeviceManager::EventType::Remove, device);
+    }
+    catch (...) {
+      /* Ignore for now */
+      USBGUARD_LOG(Debug) << "Removal of an unknown device ignored.";
+      return;
+    }
+  }
+
+  bool DeviceManagerBase::isPresentSysfsPath(const std::string& sysfs_path) const
+  {
+    uint32_t id = 0;
+
+    if (knownSysfsPath(sysfs_path, &id)) {
+      return 0 == id;
+    }
+
+    return false;
+  }
+
+  bool DeviceManagerBase::knownSysfsPath(const std::string& sysfs_path, uint32_t* id_ptr) const
+  {
+    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id_ptr=" << (void*)id_ptr;
+    auto it = _sysfs_path_to_id_map.find(sysfs_path);
+    uint32_t known_id = 0;
+    bool known = false;
+
+    if (it != _sysfs_path_to_id_map.end()) {
+      known = true;
+      known_id = it->second;
+    }
+
+    if (id_ptr != nullptr) {
+      *id_ptr = known_id;
+    }
+
+    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " id_ptr=" << (void*)id_ptr << " known=" << known << " known_id="
+      << known_id;
+    return known;
+  }
+
+  void DeviceManagerBase::learnSysfsPath(const std::string& sysfs_path, uint32_t id)
+  {
+    USBGUARD_LOG(Trace) << "Learn sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id=" << id;
+    _sysfs_path_to_id_map[sysfs_path] = id;
+  }
+
+  void DeviceManagerBase::forgetSysfsPath(const std::string& sysfs_path)
+  {
+    USBGUARD_LOG(Trace) << "Forget sysfs_path=" << sysfs_path;
+    _sysfs_path_to_id_map.erase(sysfs_path);
+  }
+
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/Library/DeviceManagerBase.hpp b/src/Library/DeviceManagerBase.hpp
new file mode 100644
index 0000000..3207684
--- /dev/null
+++ b/src/Library/DeviceManagerBase.hpp
@@ -0,0 +1,80 @@
+//
+// Copyright (C) 2016 Red Hat, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+// Authors: Daniel Kopecek <dkopecek@redhat.com>
+//
+#pragma once
+#ifdef HAVE_BUILD_CONFIG_H
+  #include <build-config.h>
+#endif
+
+#include "SysFSDevice.hpp"
+
+#include "usbguard/DeviceManager.hpp"
+#include "usbguard/Device.hpp"
+#include "usbguard/Rule.hpp"
+
+#include <dirent.h>
+
+namespace usbguard
+{
+  class DeviceBase;
+
+  class DeviceManagerBase : public DeviceManager
+  {
+    using DeviceManager::insertDevice;
+
+  public:
+    DeviceManagerBase(DeviceManagerHooks& hooks);
+    virtual ~DeviceManagerBase();
+
+    void setEnumerationOnlyMode(bool state) override;
+
+    std::shared_ptr<Device> applyDevicePolicy(uint32_t id, Rule::Target target) override;
+    void insertDevice(std::shared_ptr<DeviceBase> device);
+    std::shared_ptr<Device> removeDevice(const std::string& syspath);
+
+    uint32_t getIDFromSysfsPath(const std::string& syspath) const;
+
+  protected:
+    static std::string ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry);
+
+    int ueventOpen();
+    void setDeviceAuthorizedDefault(SysFSDevice* device, DeviceManager::AuthorizedDefaultType auth_default);
+
+    virtual void sysfsAuthorizeDevice(SysFSDevice& sysfs_device);
+    virtual void sysfsDeauthorizeDevice(SysFSDevice& sysfs_device);
+    virtual void sysfsRemoveDevice(SysFSDevice& sysfs_device);
+    void sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target);
+
+    void processDevicePresence(uint32_t id);
+    void processDeviceInsertion(SysFSDevice& sysfs_device, bool signal_present);
+    void processDeviceRemoval(const std::string& sysfs_devpath);
+
+    bool isPresentSysfsPath(const std::string& sysfs_path) const;
+    bool knownSysfsPath(const std::string& sysfs_path, uint32_t* id = nullptr) const;
+    void learnSysfsPath(const std::string& sysfs_path, uint32_t id = 0);
+    void forgetSysfsPath(const std::string& sysfs_path);
+
+    int _uevent_fd;
+    int _wakeup_fd;
+    std::map<std::string, uint32_t> _sysfs_path_to_id_map;
+    bool _enumeration_only_mode;
+    std::atomic<bool> _enumeration;
+  };
+} /* namespace usbguard */
+
+/* vim: set ts=2 sw=2 et */
diff --git a/src/Library/DeviceManagerPrivate.cpp b/src/Library/DeviceManagerPrivate.cpp
index 308df7a..73f84fd 100644
--- a/src/Library/DeviceManagerPrivate.cpp
+++ b/src/Library/DeviceManagerPrivate.cpp
@@ -117,6 +117,7 @@ namespace usbguard
       return _device_map.at(id);
     }
     catch (...) {
+      USBGUARD_LOG(Debug) << "Lookup error: " << id;
       throw Exception("Device lookup", "device id", "id doesn't exist");
     }
   }
diff --git a/src/Library/Hash.cpp b/src/Library/Hash.cpp
index 700c758..7d26bac 100644
--- a/src/Library/Hash.cpp
+++ b/src/Library/Hash.cpp
@@ -38,6 +38,26 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
     crypto_hash_sha256_init(&_state);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+
+    if ((_state = EVP_MD_CTX_new()) == nullptr) {
+      throw std::runtime_error("Dynamic memory allocation of message digest context failed.");
+    }
+
+#else
+
+    if ((_state = EVP_MD_CTX_create()) == nullptr) {
+      throw std::runtime_error("Dynamic memory allocation of message digest context failed.");
+    }
+
+#endif
+
+    if (!EVP_DigestInit_ex(_state, EVP_sha256(), nullptr)) {
+      throw std::runtime_error("Context initialization of message digest context failed.");
+    }
+
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     gcry_md_open(&_state, GCRY_MD_SHA256, 0);
 #endif
@@ -49,6 +69,9 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
     _state = rhs._state;
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    _state = rhs._state;
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     gcry_md_copy(&_state, rhs._state);
 #endif
@@ -61,6 +84,10 @@ namespace usbguard
     _state = rhs._state;
     memset(&rhs._state, 0, sizeof _state);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    _state = rhs._state;
+    memset(&rhs._state, 0, sizeof _state);
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     _state = rhs._state;
     rhs._state = nullptr;
@@ -74,6 +101,10 @@ namespace usbguard
     _state = rhs._state;
     memset(&rhs._state, 0, sizeof _state);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    _state = rhs._state;
+    memset(&rhs._state, 0, sizeof _state);
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     _state = rhs._state;
     rhs._state = nullptr;
@@ -83,6 +114,13 @@ namespace usbguard
 
   Hash::~Hash()
   {
+#if defined(USBGUARD_USE_OPENSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+    EVP_MD_CTX_free(_state);
+#else
+    EVP_MD_CTX_destroy(_state);
+#endif
+#endif
     release();
   }
 
@@ -91,6 +129,9 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
     memset(&_state, 0, sizeof _state);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    memset(&_state, 0, sizeof _state);
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
 
     if (_state != nullptr) {
@@ -110,6 +151,13 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
     crypto_hash_sha256_update(&_state, reinterpret_cast<const uint8_t*>(ptr), size);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+
+    if (!EVP_DigestUpdate(_state, reinterpret_cast<const uint8_t*>(ptr), size)) {
+      throw std::runtime_error("Hashing data into message digest context failed.");
+    }
+
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     gcry_md_write(_state, ptr, size);
 #endif
@@ -130,6 +178,13 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
         crypto_hash_sha256_update(&_state, buffer, buflen);
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+
+        if (!EVP_DigestUpdate(_state, buffer, buflen)) {
+          throw std::runtime_error("Hashing data into message digest context failed.");
+        }
+
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
         gcry_md_write(_state, buffer, buflen);
 #endif
@@ -148,6 +203,17 @@ namespace usbguard
     const uint8_t* const hash_buffer = hash_binary;
     const size_t hash_buflen = sizeof hash_binary;
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    uint8_t hash_binary[EVP_MAX_MD_SIZE];
+    unsigned int hash_len;
+
+    if (!EVP_DigestFinal_ex(_state, hash_binary, &hash_len)) {
+      throw std::runtime_error("Digest value retrieval failed.");
+    }
+
+    const uint8_t* const hash_buffer = hash_binary;
+    const size_t hash_buflen = hash_len;
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     gcry_md_final(_state);
     const size_t hash_buflen = gcry_md_get_algo_dlen(GCRY_MD_SHA256);
diff --git a/src/Library/Hash.hpp b/src/Library/Hash.hpp
index bf445bf..06dfd94 100644
--- a/src/Library/Hash.hpp
+++ b/src/Library/Hash.hpp
@@ -30,6 +30,8 @@
 
 #if defined(USBGUARD_USE_LIBSODIUM)
   #include <sodium.h>
+#elif defined(USBGUARD_USE_OPENSSL)
+  #include <openssl/evp.h>
 #elif defined(USBGUARD_USE_LIBGCRYPT)
   #include <gcrypt.h>
 #else
@@ -56,6 +58,9 @@ namespace usbguard
 #if defined(USBGUARD_USE_LIBSODIUM)
     crypto_hash_sha256_state _state;
 #endif
+#if defined(USBGUARD_USE_OPENSSL)
+    EVP_MD_CTX* _state;
+#endif
 #if defined(USBGUARD_USE_LIBGCRYPT)
     gcry_md_hd_t _state {nullptr};
 #endif
diff --git a/src/Library/IPC/Devices.proto b/src/Library/IPC/Devices.proto
index ba7ea1f..8cfb2a0 100644
--- a/src/Library/IPC/Devices.proto
+++ b/src/Library/IPC/Devices.proto
@@ -51,6 +51,13 @@ message DevicePolicyChangedSignal {
   required uint32 rule_id = 5;
 }
 
+message DevicePolicyAppliedSignal {
+  required uint32 id = 1;
+  required uint32 target_new = 2;
+  required string device_rule = 3;
+  required uint32 rule_id = 4;
+}
+
 message PropertyParameterChangedSignal {
   required string name = 1;
   required string value_old = 2;
diff --git a/src/Library/IPC/Policy.proto b/src/Library/IPC/Policy.proto
index 399b246..46fe977 100644
--- a/src/Library/IPC/Policy.proto
+++ b/src/Library/IPC/Policy.proto
@@ -40,7 +40,7 @@ message removeRuleRequest {
 }
 
 message removeRuleResponse {
-  required uint32 id = 1;  
+  required uint32 id = 1;
 }
 
 message removeRule {
@@ -48,3 +48,21 @@ message removeRule {
   required removeRuleRequest request = 2;
   optional removeRuleResponse response = 3;
 }
+
+
+message checkIPCPermissionsRequest {
+  required uint32 uid = 1;
+  required uint32 gid = 2;
+  required string section = 3;
+  required string privilege = 4;
+}
+
+message checkIPCPermissionsResponse {
+  required bool permit = 1;
+}
+
+message checkIPCPermissions {
+  required MessageHeader header = 1;
+  required checkIPCPermissionsRequest request = 2;
+  optional checkIPCPermissionsResponse response = 3;
+}
diff --git a/src/Library/IPCClientPrivate.cpp b/src/Library/IPCClientPrivate.cpp
index 8db341a..6aa5201 100644
--- a/src/Library/IPCClientPrivate.cpp
+++ b/src/Library/IPCClientPrivate.cpp
@@ -32,6 +32,7 @@
 #include <unistd.h>
 #include <sys/poll.h>
 #include <sys/eventfd.h>
+#include <sys/types.h>
 
 namespace usbguard
 {
@@ -80,9 +81,11 @@ namespace usbguard
     registerHandler<IPC::removeRule>(&IPCClientPrivate::handleMethodResponse);
     registerHandler<IPC::applyDevicePolicy>(&IPCClientPrivate::handleMethodResponse);
     registerHandler<IPC::listDevices>(&IPCClientPrivate::handleMethodResponse);
+    registerHandler<IPC::checkIPCPermissions>(&IPCClientPrivate::handleMethodResponse);
     registerHandler<IPC::Exception>(&IPCClientPrivate::handleException);
     registerHandler<IPC::DevicePresenceChangedSignal>(&IPCClientPrivate::handleDevicePresenceChangedSignal);
     registerHandler<IPC::DevicePolicyChangedSignal>(&IPCClientPrivate::handleDevicePolicyChangedSignal);
+    registerHandler<IPC::DevicePolicyAppliedSignal>(&IPCClientPrivate::handleDevicePolicyAppliedSignal);
     registerHandler<IPC::PropertyParameterChangedSignal>(&IPCClientPrivate::handlePropertyParameterChangedSignal);
 
     if (connected) {
@@ -141,17 +144,20 @@ namespace usbguard
       << " do_wait=" << do_wait;
     USBGUARD_LOG(Trace) << "_qb_conn=" << _qb_conn
       << " _qb_fd=" << _qb_fd;
+    std::unique_lock<std::mutex> disconnect_lock(_disconnect_mutex);
 
     if (_qb_conn != nullptr && _qb_fd >= 0) {
       qb_loop_poll_del(_qb_loop, _qb_fd);
       qb_ipcc_disconnect(_qb_conn);
       _qb_conn = nullptr;
       _qb_fd = -1;
+      disconnect_lock.unlock();
       stop(do_wait);
       USBGUARD_LOG(Trace) << "Signaling IPCDisconnected";
       _p_instance.IPCDisconnected(/*exception_initiated=*/true, exception);
     }
     else if (_thread.running()) {
+      disconnect_lock.unlock();
       stop(do_wait);
     }
   }
@@ -439,6 +445,22 @@ namespace usbguard
     return devices;
   }
 
+  bool IPCClientPrivate::checkIPCPermissions(const IPCServer::AccessControl::Section& section,
+    const IPCServer::AccessControl::Privilege& privilege)
+  {
+    IPC::checkIPCPermissions message_out;
+    message_out.mutable_request()->set_uid(getuid());
+    message_out.mutable_request()->set_gid(getgid());
+    message_out.mutable_request()->set_section(
+      IPCServer::AccessControl::sectionToString(section)
+    );
+    message_out.mutable_request()->set_privilege(
+      IPCServer::AccessControl::privilegeToString(privilege)
+    );
+    auto message_in = qbIPCSendRecvMessage(message_out);
+    return message_in->response().permit();
+  }
+
   void IPCClientPrivate::handleMethodResponse(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out)
   {
     (void)message_out;
@@ -501,6 +523,17 @@ namespace usbguard
       signal->rule_id());
   }
 
+  void IPCClientPrivate::handleDevicePolicyAppliedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out)
+  {
+    (void)message_out;
+    const IPC::DevicePolicyAppliedSignal* const signal = \
+      reinterpret_cast<const IPC::DevicePolicyAppliedSignal*>(message_in.get());
+    _p_instance.DevicePolicyApplied(signal->id(),
+      Rule::targetFromInteger(signal->target_new()),
+      signal->device_rule(),
+      signal->rule_id());
+  }
+
   void IPCClientPrivate::handlePropertyParameterChangedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out)
   {
     (void)message_out;
diff --git a/src/Library/IPCClientPrivate.hpp b/src/Library/IPCClientPrivate.hpp
index ff7ad65..d92a1d4 100644
--- a/src/Library/IPCClientPrivate.hpp
+++ b/src/Library/IPCClientPrivate.hpp
@@ -65,6 +65,9 @@ namespace usbguard
     uint32_t applyDevicePolicy(uint32_t id, Rule::Target target, bool permanent);
     const std::vector<Rule> listDevices(const std::string& query);
 
+    bool checkIPCPermissions(const IPCServer::AccessControl::Section& section,
+      const IPCServer::AccessControl::Privilege& privilege);
+
     void processReceiveEvent();
 
   private:
@@ -107,6 +110,7 @@ namespace usbguard
     void handleException(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out);
     void handleDevicePresenceChangedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out);
     void handleDevicePolicyChangedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out);
+    void handleDevicePolicyAppliedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out);
     void handlePropertyParameterChangedSignal(IPC::MessagePointer& message_in, IPC::MessagePointer& message_out);
 
     IPCClient& _p_instance;
@@ -117,7 +121,7 @@ namespace usbguard
 
     int _wakeup_fd;
 
-    std::mutex _return_mutex;
+    std::mutex _return_mutex, _disconnect_mutex;
     std::map<uint64_t, std::promise<IPC::MessagePointer>> _return_map;
 
     Thread<IPCClientPrivate> _thread;
diff --git a/src/Library/IPCPrivate.cpp b/src/Library/IPCPrivate.cpp
index 7c12f9e..1b8f23f 100644
--- a/src/Library/IPCPrivate.cpp
+++ b/src/Library/IPCPrivate.cpp
@@ -38,13 +38,15 @@ namespace usbguard
     { 0x02, "usbguard.IPC.applyDevicePolicy" },
     { 0x03, "usbguard.IPC.DevicePresenceChangedSignal" },
     { 0x04, "usbguard.IPC.DevicePolicyChangedSignal" },
-    { 0x05, "usbguard.IPC.PropertyParameterChangedSignal" },
-    { 0x06, "usbguard.IPC.listRules" },
-    { 0x07, "usbguard.IPC.appendRule" },
-    { 0x08, "usbguard.IPC.removeRule" },
-    { 0x09, "usbguard.IPC.Exception" },
-    { 0x0a, "usbguard.IPC.getParameter" },
-    { 0x0b, "usbguard.IPC.setParameter" }
+    { 0x05, "usbguard.IPC.DevicePolicyAppliedSignal" },
+    { 0x06, "usbguard.IPC.PropertyParameterChangedSignal" },
+    { 0x07, "usbguard.IPC.listRules" },
+    { 0x08, "usbguard.IPC.appendRule" },
+    { 0x09, "usbguard.IPC.removeRule" },
+    { 0x0a, "usbguard.IPC.Exception" },
+    { 0x0b, "usbguard.IPC.getParameter" },
+    { 0x0c, "usbguard.IPC.setParameter" },
+    { 0x0d, "usbguard.IPC.checkIPCPermissions" }
   };
 
   uint32_t IPC::messageTypeNameToNumber(const std::string& name)
diff --git a/src/Library/IPCServerPrivate.cpp b/src/Library/IPCServerPrivate.cpp
index 949fc01..548a726 100644
--- a/src/Library/IPCServerPrivate.cpp
+++ b/src/Library/IPCServerPrivate.cpp
@@ -84,6 +84,8 @@ namespace usbguard
       IPCServer::AccessControl::Privilege::MODIFY);
     registerHandler<IPC::getParameter>(&IPCServerPrivate::handleGetParameter, IPCServer::AccessControl::Section::PARAMETERS,
       IPCServer::AccessControl::Privilege::LIST);
+    registerHandler<IPC::checkIPCPermissions>(&IPCServerPrivate::handleCheckIPCPermissions, IPCServer::AccessControl::Section::ALL,
+      IPCServer::AccessControl::Privilege::NONE);
   }
 
   void IPCServerPrivate::initIPC()
@@ -533,6 +535,10 @@ namespace usbguard
       return IPCServer::AccessControl::Section::DEVICES;
     }
 
+    if (name == "usbguard.IPC.DevicePolicyAppliedSignal") {
+      return IPCServer::AccessControl::Section::DEVICES;
+    }
+
     if (name == "usbguard.IPC.PropertyParameterChangedSignal") {
       return IPCServer::AccessControl::Section::PARAMETERS;
     }
@@ -565,10 +571,10 @@ namespace usbguard
   bool IPCServerPrivate::authenticateIPCConnectionDAC(uid_t uid, gid_t gid, IPCServer::AccessControl* const ac_ptr) const
   {
     USBGUARD_LOG(Trace) << "uid=" << uid << " gid=" << gid << " ac_ptr=" << ac_ptr;
-    return \
-      matchACLByUID(uid, ac_ptr) || \
-      matchACLByGID(gid, ac_ptr) || \
-      matchACLByName(uid, gid, ac_ptr);
+    bool matched_uid = matchACLByUID(uid, ac_ptr);
+    bool matched_gid = matchACLByGID(gid, ac_ptr);
+    bool matched_name = matchACLByName(uid, gid, ac_ptr);
+    return matched_uid || matched_gid || matched_name;
   }
 
   bool IPCServerPrivate::matchACLByUID(uid_t uid, IPCServer::AccessControl* const ac_ptr) const
@@ -990,6 +996,23 @@ namespace usbguard
     response.reset(message_out);
   }
 
+  void IPCServerPrivate::handleCheckIPCPermissions(IPC::MessagePointer& request, IPC::MessagePointer& response)
+  {
+    const IPC::checkIPCPermissions* const message_in = static_cast<const IPC::checkIPCPermissions*>(request.get());
+    uid_t uid = message_in->request().uid();
+    gid_t gid = message_in->request().gid();
+    IPCServer::AccessControl access_control = IPCServer::AccessControl();
+    const bool auth = qbIPCConnectionAllowed(uid, gid, &access_control);
+    IPCServer::AccessControl::Section section = IPCServer::AccessControl::sectionFromString(message_in->request().section());
+    IPCServer::AccessControl::Privilege privilege = IPCServer::AccessControl::privilegeFromString(
+        message_in->request().privilege());
+    const bool permit = auth && access_control.hasPrivilege(section, privilege);
+    IPC::checkIPCPermissions* const message_out = message_in->New();
+    message_out->MergeFrom(*message_in);
+    message_out->mutable_response()->set_permit(permit);
+    response.reset(message_out);
+  }
+
   void IPCServerPrivate::DevicePresenceChanged(uint32_t id,
     DeviceManager::EventType event,
     Rule::Target target,
@@ -1018,6 +1041,19 @@ namespace usbguard
     qbIPCBroadcastMessage(&signal);
   }
 
+  void IPCServerPrivate::DevicePolicyApplied(uint32_t id,
+    Rule::Target target_new,
+    const std::string& device_rule,
+    uint32_t rule_id)
+  {
+    IPC::DevicePolicyAppliedSignal signal;
+    signal.set_id(id);
+    signal.set_target_new(Rule::targetToInteger(target_new));
+    signal.set_device_rule(device_rule);
+    signal.set_rule_id(rule_id);
+    qbIPCBroadcastMessage(&signal);
+  }
+
   void IPCServerPrivate::PropertyParameterChanged(const std::string& name,
     const std::string& value_old,
     const std::string& value_new)
diff --git a/src/Library/IPCServerPrivate.hpp b/src/Library/IPCServerPrivate.hpp
index 53c9c33..25f9ac3 100644
--- a/src/Library/IPCServerPrivate.hpp
+++ b/src/Library/IPCServerPrivate.hpp
@@ -63,6 +63,11 @@ namespace usbguard
       const std::string& device_rule,
       uint32_t rule_id);
 
+    void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id);
+
     void PropertyParameterChanged(const std::string& name,
       const std::string& value_old,
       const std::string& value_new);
@@ -143,6 +148,8 @@ namespace usbguard
     void handleSetParameter(IPC::MessagePointer& request, IPC::MessagePointer& response);
     void handleGetParameter(IPC::MessagePointer& request, IPC::MessagePointer& response);
 
+    void handleCheckIPCPermissions(IPC::MessagePointer& request, IPC::MessagePointer& response);
+
     IPCServer& _p_instance;
 
     qb_loop_t* _qb_loop;
diff --git a/src/Library/RuleParser/Grammar.hpp b/src/Library/RuleParser/Grammar.hpp
index c80eb2d..aebb727 100644
--- a/src/Library/RuleParser/Grammar.hpp
+++ b/src/Library/RuleParser/Grammar.hpp
@@ -34,29 +34,29 @@ namespace usbguard
     /*
      * Rule language keywords
      */
-    struct str_allow : TAOCPP_PEGTL_STRING("allow") {};
-    struct str_block : TAOCPP_PEGTL_STRING("block") {};
-    struct str_reject : TAOCPP_PEGTL_STRING("reject") {};
-    struct str_match : TAOCPP_PEGTL_STRING("match") {};
-    struct str_device : TAOCPP_PEGTL_STRING("device") {};
-
-    struct str_name : TAOCPP_PEGTL_STRING("name") {};
-    struct str_hash : TAOCPP_PEGTL_STRING("hash") {};
-    struct str_parent_hash : TAOCPP_PEGTL_STRING("parent-hash") {};
-    struct str_via_port : TAOCPP_PEGTL_STRING("via-port") {};
-    struct str_with_interface : TAOCPP_PEGTL_STRING("with-interface") {};
-    struct str_with_connect_type : TAOCPP_PEGTL_STRING("with-connect-type") {};
-    struct str_serial : TAOCPP_PEGTL_STRING("serial") {};
-    struct str_if : TAOCPP_PEGTL_STRING("if") {};
-    struct str_id : TAOCPP_PEGTL_STRING("id") {};
-    struct str_label : TAOCPP_PEGTL_STRING("label") {};
-
-    struct str_all_of : TAOCPP_PEGTL_STRING("all-of") {};
-    struct str_one_of : TAOCPP_PEGTL_STRING("one-of") {};
-    struct str_none_of : TAOCPP_PEGTL_STRING("none-of") {};
-    struct str_equals : TAOCPP_PEGTL_STRING("equals") {};
-    struct str_equals_ordered : TAOCPP_PEGTL_STRING("equals-ordered") {};
-    struct str_match_all: TAOCPP_PEGTL_STRING("match-all") {};
+    struct str_allow : TAO_PEGTL_STRING("allow") {};
+    struct str_block : TAO_PEGTL_STRING("block") {};
+    struct str_reject : TAO_PEGTL_STRING("reject") {};
+    struct str_match : TAO_PEGTL_STRING("match") {};
+    struct str_device : TAO_PEGTL_STRING("device") {};
+
+    struct str_name : TAO_PEGTL_STRING("name") {};
+    struct str_hash : TAO_PEGTL_STRING("hash") {};
+    struct str_parent_hash : TAO_PEGTL_STRING("parent-hash") {};
+    struct str_via_port : TAO_PEGTL_STRING("via-port") {};
+    struct str_with_interface : TAO_PEGTL_STRING("with-interface") {};
+    struct str_with_connect_type : TAO_PEGTL_STRING("with-connect-type") {};
+    struct str_serial : TAO_PEGTL_STRING("serial") {};
+    struct str_if : TAO_PEGTL_STRING("if") {};
+    struct str_id : TAO_PEGTL_STRING("id") {};
+    struct str_label : TAO_PEGTL_STRING("label") {};
+
+    struct str_all_of : TAO_PEGTL_STRING("all-of") {};
+    struct str_one_of : TAO_PEGTL_STRING("one-of") {};
+    struct str_none_of : TAO_PEGTL_STRING("none-of") {};
+    struct str_equals : TAO_PEGTL_STRING("equals") {};
+    struct str_equals_ordered : TAO_PEGTL_STRING("equals-ordered") {};
+    struct str_match_all: TAO_PEGTL_STRING("match-all") {};
 
     /*
      * Generic rule attribute
diff --git a/src/Library/UEventDeviceManager.cpp b/src/Library/UEventDeviceManager.cpp
index 11d664b..8d1fb79 100644
--- a/src/Library/UEventDeviceManager.cpp
+++ b/src/Library/UEventDeviceManager.cpp
@@ -30,7 +30,6 @@
 
 #include "usbguard/Logger.hpp"
 #include "usbguard/Exception.hpp"
-#include "usbguard/USB.hpp"
 
 #include <stdexcept>
 #include <fstream>
@@ -48,234 +47,11 @@
 
 namespace usbguard
 {
-  namespace
-  {
-    void setDeviceAuthorizedDefault(SysFSDevice* device, DeviceManager::AuthorizedDefaultType auth_default)
-    {
-      if (auth_default == DeviceManager::AuthorizedDefaultType::Keep) {
-        return;
-      }
-
-      std::string auth_default_str = std::to_string(DeviceManager::authorizedDefaultTypeToInteger(auth_default));
-      device->setAttribute("authorized_default", auth_default_str);
-
-      if (device->readAttribute("authorized_default", /*strip_last_null=*/true) != auth_default_str) {
-        if (auth_default == DeviceManager::AuthorizedDefaultType::Internal) {
-          USBGUARD_LOG(Warning) << "No kernel support for authorized_default = 2, falling back to 0";
-          setDeviceAuthorizedDefault(device, DeviceManager::AuthorizedDefaultType::None);
-        }
-        else {
-          throw Exception("UEventDevice", device->getPath(), "Failed to set authorized_default to \"" + auth_default_str + "\"");
-        }
-      }
-    }
-  }  /* namespace */
-
-  UEventDevice::UEventDevice(UEventDeviceManager& device_manager, SysFSDevice& sysfs_device)
-    : Device(device_manager)
-  {
-    /*
-     * Look for the parent USB device and set the parent id
-     * if we find one.
-     */
-    const std::string sysfs_parent_path(sysfs_device.getParentPath());
-    const SysFSDevice sysfs_parent_device(sysfs_parent_path);
-
-    if (sysfs_parent_device.getUEvent().getAttribute("DEVTYPE") == "usb_device") {
-      setParentID(device_manager.getIDFromSysfsPath(sysfs_parent_path));
-    }
-    else {
-      setParentID(Rule::RootID);
-      setParentHash(hashString(sysfs_parent_path));
-    }
-
-    /*
-     * Set name
-     */
-    setName(sysfs_device.readAttribute("product", /*strip_last_null=*/true, /*optional=*/true));
-    /*
-     * Set USB ID
-     */
-    const std::string id_vendor(sysfs_device.readAttribute("idVendor", /*strip_last_null=*/true));
-    const std::string id_product(sysfs_device.readAttribute("idProduct", /*strip_last_null=*/true));
-    const USBDeviceID device_id(id_vendor, id_product);
-    setDeviceID(device_id);
-    /*
-     * Set serial number
-     */
-    setSerial(sysfs_device.readAttribute("serial", /*strip_last_null=*/true, /*optional=*/true));
-    /*
-     * Set USB port
-     */
-    setPort(sysfs_device.getName());
-    /*
-     * Sync authorization target
-     */
-    const std::string authorized_value(sysfs_device.readAttribute("authorized", /*strip_last_null=*/true));
-
-    if (authorized_value == "0") {
-      setTarget(Rule::Target::Block);
-    }
-    else if (authorized_value == "1") {
-      setTarget(Rule::Target::Allow);
-    }
-    else {
-      /*
-       * Block the device if we get an unexpected value
-       */
-      setTarget(Rule::Target::Block);
-    }
-
-    /*
-     * Set connect type
-     */
-    setConnectType(sysfs_device.readAttribute("port/connect_type", /*strip_last_null=*/true, /*optional=*/true));
-    /*
-     * Process USB descriptor data.
-     *
-     * FDInputStream (stdio_filebuf) is responsible for closing the file
-     * descriptor returned by sysfs_device.openAttribute().
-     *
-     */
-    FDInputStream descriptor_stream(sysfs_device.openAttribute("descriptors"));
-    /*
-     * Find out the descriptor data stream size
-     */
-    size_t descriptor_expected_size = 0;
-
-    if (!descriptor_stream.good()) {
-      throw ErrnoException("UEventDevice", sysfs_device.getPath(), errno);
-    }
-
-    initializeHash();
-    USBDescriptorParser parser(*this);
-
-    if ((descriptor_expected_size = parser.parse(descriptor_stream)) < sizeof(USBDeviceDescriptor)) {
-      throw Exception("UEventDevice", sysfs_device.getPath(),
-        "USB descriptor parser processed less data than the size of a USB device descriptor");
-    }
-
-    finalizeHash();
-    /*
-     * From now own we take ownership of the SysFSDevice instance.
-     */
-    _sysfs_device = std::move(sysfs_device);
-  }
-
-  SysFSDevice& UEventDevice::sysfsDevice()
-  {
-    return _sysfs_device;
-  }
 
-  const std::string& UEventDevice::getSysPath() const
-  {
-    return _sysfs_device.getPath();
-  }
-
-  bool UEventDevice::isController() const
-  {
-    if (getPort().substr(0, 3) != "usb" || getInterfaceTypes().size() != 1) {
-      return false;
-    }
-
-    const USBInterfaceType hub_interface("09:00:*");
-    return hub_interface.appliesTo(getInterfaceTypes()[0]);
-  }
-
-  std::string UEventDevice::getSystemName() const
-  {
-    return getSysPath();
-  }
-
-  void UEventDevice::parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
-    USBDescriptor* descriptor_out)
-  {
-    USBGUARD_LOG(Trace);
-    USBDescriptorParserHooks::parseUSBDescriptor(parser, descriptor_raw, descriptor_out);
-
-    if (isLinuxRootHubDeviceDescriptor(descriptor_out)) {
-      updateHashLinuxRootHubDeviceDescriptor(descriptor_raw);
-    }
-    else {
-      updateHash(descriptor_raw, static_cast<size_t>(descriptor_raw->bHeader.bLength));
-    }
-  }
-
-  void UEventDevice::loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor)
-  {
-    const auto type = static_cast<USBDescriptorType>(descriptor->bHeader.bDescriptorType);
-
-    switch (type) {
-    case USBDescriptorType::Device:
-      loadDeviceDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Configuration:
-      loadConfigurationDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Interface:
-      loadInterfaceDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Endpoint:
-      loadEndpointDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::AssociationInterface:
-    case USBDescriptorType::Unknown:
-    case USBDescriptorType::String:
-    default:
-      USBGUARD_LOG(Debug) << "Ignoring descriptor: type=" << (int)type
-        << " size=" << descriptor->bHeader.bLength;
-    }
-  }
-
-  bool UEventDevice::isLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
-  {
-    USBGUARD_LOG(Trace);
-
-    if (descriptor->bHeader.bDescriptorType != USB_DESCRIPTOR_TYPE_DEVICE) {
-      return false;
-    }
-
-    const USBDeviceDescriptor* const device_descriptor = \
-      reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
-
-    if (device_descriptor->idVendor == 0x1d6b /* Linux Foundation */) {
-      switch (device_descriptor->idProduct) {
-      case 0x0001: /* 1.1 root hub */
-      case 0x0002: /* 2.0 root hub */
-      case 0x0003: /* 3.0 root hub */
-        return true;
-
-      default:
-        return false;
-      }
-    }
-
-    return false;
-  }
-
-  void UEventDevice::updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
-  {
-    USBGUARD_LOG(Trace);
-    USBDeviceDescriptor descriptor_modified = *reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
-    descriptor_modified.bcdDevice = 0;
-    updateHash(&descriptor_modified, sizeof descriptor_modified);
-  }
-
-  /*
-   * Manager
-   */
   UEventDeviceManager::UEventDeviceManager(DeviceManagerHooks& hooks)
-    : DeviceManager(hooks),
-      _thread(this, &UEventDeviceManager::thread),
-      _uevent_fd(-1),
-      _wakeup_fd(-1),
-      _enumeration(false)
+    : DeviceManagerBase(hooks),
+      _thread(this, &UEventDeviceManager::thread)
   {
-    setEnumerationOnlyMode(/*state=*/false);
   }
 
   UEventDeviceManager::~UEventDeviceManager()
@@ -295,11 +71,6 @@ namespace usbguard
     }
   }
 
-  void UEventDeviceManager::setEnumerationOnlyMode(bool state)
-  {
-    _enumeration_only_mode = state;
-  }
-
   void UEventDeviceManager::start()
   {
     // Lazy initialization is used for the sockets to allow scanning a devpath
@@ -318,8 +89,7 @@ namespace usbguard
       { /* Wakeup the device manager thread */
         const uint64_t one = 1;
         USBGUARD_SYSCALL_THROW("Linux device manager",
-          write(_wakeup_fd, &one, sizeof one)
-          != sizeof one);
+          write(_wakeup_fd, &one, sizeof one) != sizeof one);
       }
       _thread.wait();
     }
@@ -348,19 +118,6 @@ namespace usbguard
     processBacklog();
   }
 
-  void UEventDeviceManager::processBacklog()
-  {
-    USBGUARD_LOG(Debug) << "Processing backlog: _backlog.size() = " << _backlog.size();
-    try {
-      for (auto & it : _backlog) {
-        ueventProcessUEvent(std::move(it));
-      }
-    }
-    catch (...) {
-      USBGUARD_LOG(Warning) << "ueventProcessBacklog: error processing uevent data";
-    }
-  }
-
   void UEventDeviceManager::scan(const std::string& devpath)
   {
     std::vector<std::string> components;
@@ -382,80 +139,33 @@ namespace usbguard
     ueventProcessAction("add", path);
   }
 
-  std::shared_ptr<Device> UEventDeviceManager::applyDevicePolicy(uint32_t id, Rule::Target target)
-  {
-    USBGUARD_LOG(Trace) << "id=" << id
-      << " target=" << Rule::targetToString(target);
-    std::shared_ptr<UEventDevice> device = std::static_pointer_cast<UEventDevice>(getDevice(id));
-    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
-    sysfsApplyTarget(device->sysfsDevice(), target);
-    device->setTarget(target);
-    return device;
-  }
-
-  int UEventDeviceManager::ueventOpen()
+  bool UEventDeviceManager::ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
+    const std::pair<std::string, std::string>& b)
   {
-    int socket_fd = -1;
-    USBGUARD_SYSCALL_THROW("UEvent device manager",
-      (socket_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0);
+    const std::string base_a = filenameFromPath(a.second, /*include_extension=*/true);
+    const std::string base_b = filenameFromPath(b.second, /*include_extension=*/true);
+    const bool a_has_usb_prefix = (0 == base_a.compare(0, 3, "usb"));
+    const bool b_has_usb_prefix = (0 == base_b.compare(0, 3, "usb"));
 
-    try {
-      const int optval = 1;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        setsockopt(socket_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof optval) != 0);
-      /*
-       * Set a 1MiB receive buffer on the netlink socket to avoid ENOBUFS error
-       * in recvmsg.
-       */
-      const size_t rcvbuf_max = 1024 * 1024;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_max, sizeof rcvbuf_max) != 0);
-      struct sockaddr_nl sa = { };
-      sa.nl_family = AF_NETLINK;
-      sa.nl_pid = getpid();
-      sa.nl_groups = -1;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        bind(socket_fd, reinterpret_cast<const sockaddr*>(&sa), sizeof sa) != 0);
+    if (a_has_usb_prefix) {
+      if (!b_has_usb_prefix) {
+        return true;
+      }
     }
-    catch (...) {
-      (void)close(socket_fd);
-      throw;
+    else {
+      if (b_has_usb_prefix) {
+        return false;
+      }
     }
 
-    return socket_fd;
-  }
-
-  void UEventDeviceManager::sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target)
-  {
-    std::string name;
-    std::string value("0");
-
-    switch (target) {
-    case Rule::Target::Allow:
-      name = "authorized";
-      value = "1";
-      break;
-
-    case Rule::Target::Block:
-      name = "authorized";
-      value = "0";
-      break;
-
-    case Rule::Target::Reject:
-      name = "remove";
-      value = "1";
-      break;
-
-    case Rule::Target::Match:
-    case Rule::Target::Device:
-    case Rule::Target::Unknown:
-    case Rule::Target::Empty:
-    case Rule::Target::Invalid:
-    default:
-      throw std::runtime_error("Unknown rule target in applyDevicePolicy");
+    if (base_a.size() < base_b.size()) {
+      return true;
+    }
+    else if (base_a.size() > base_b.size()) {
+      return false;
     }
 
-    sysfs_device.setAttribute(name, value);
+    return base_a < base_b;
   }
 
   void UEventDeviceManager::thread()
@@ -631,13 +341,11 @@ namespace usbguard
 
   void UEventDeviceManager::ueventProcessAction(const std::string& action, const std::string& sysfs_devpath)
   {
-
     try {
       uint32_t id = 0;
       const bool known_path = knownSysfsPath(sysfs_devpath, &id);
 
-      if (action == "add" || action == "change") {
-
+      if (action == "add" /*|| action == "change"*/) {
         if (known_path && id > 0) {
           processDevicePresence(id);
         }
@@ -688,36 +396,6 @@ namespace usbguard
       USBGUARD_LOG(Warning) << "USB Device Exception: unknown exception";
       DeviceException("unknown exception");
     }
-
-  }
-
-  bool UEventDeviceManager::ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
-    const std::pair<std::string, std::string>& b)
-  {
-    const std::string base_a = filenameFromPath(a.second, /*include_extension=*/true);
-    const std::string base_b = filenameFromPath(b.second, /*include_extension=*/true);
-    const bool a_has_usb_prefix = (0 == base_a.compare(0, 3, "usb"));
-    const bool b_has_usb_prefix = (0 == base_b.compare(0, 3, "usb"));
-
-    if (a_has_usb_prefix) {
-      if (!b_has_usb_prefix) {
-        return true;
-      }
-    }
-    else {
-      if (b_has_usb_prefix) {
-        return false;
-      }
-    }
-
-    if (base_a.size() < base_b.size()) {
-      return true;
-    }
-    else if (base_a.size() > base_b.size()) {
-      return false;
-    }
-
-    return base_a < base_b;
   }
 
   int UEventDeviceManager::ueventEnumerateDevices()
@@ -731,55 +409,6 @@ namespace usbguard
     UEventDeviceManager::ueventEnumerateComparePath);
   }
 
-  std::string UEventDeviceManager::ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry)
-  {
-#if defined(_DIRENT_HAVE_D_TYPE)
-
-    if (direntry->d_type != DT_UNKNOWN) {
-      switch (direntry->d_type) {
-      case DT_LNK:
-        return symlinkPath(filepath);
-
-      case DT_DIR:
-        return filepath;
-
-      default:
-        return std::string();
-      }
-    }
-    else {
-      /*
-       * Unknown type. We have to call lstat.
-       */
-#endif
-      struct stat st = { };
-
-      if (lstat(filepath.c_str(), &st) != 0) {
-        /*
-         * Cannot stat, skip this entry.
-         */
-        USBGUARD_LOG(Warning) << "lstat(" << filepath << "): errno=" << errno;
-        return std::string();
-      }
-
-      if (S_ISLNK(st.st_mode)) {
-        return symlinkPath(filepath, &st);
-      }
-      else if (S_ISDIR(st.st_mode)) {
-        return filepath;
-      }
-      else {
-        return std::string();
-      }
-
-#if defined(_DIRENT_HAVE_D_TYPE)
-    }
-
-#endif
-    /* UNREACHABLE */
-    return std::string();
-  }
-
   int UEventDeviceManager::ueventEnumerateTriggerDevice(const std::string& devpath, const std::string& buspath)
   {
     USBGUARD_LOG(Trace) << "devpath=" << devpath << " buspath=" << buspath;
@@ -822,189 +451,18 @@ namespace usbguard
     return 0;
   }
 
-  void UEventDeviceManager::processDevicePresence(const uint32_t id)
+  void UEventDeviceManager::processBacklog()
   {
-    USBGUARD_LOG(Trace) << "id=" << id;
-
-    try {
-      std::shared_ptr<UEventDevice> device = \
-        std::static_pointer_cast<UEventDevice>(DeviceManager::getDevice(id));
-      device->sysfsDevice().reload();
-      /*
-       * TODO: Check attribute state
-       *  - authorized_default (in case of controller)
-       */
-      DeviceEvent(DeviceManager::EventType::Present, device);
-      return;
-    }
-    catch (const Exception& ex) {
-      USBGUARD_LOG(Error) << "Present device exception: " << ex.message();
-      DeviceException(ex.message());
-    }
-    catch (const std::exception& ex) {
-      USBGUARD_LOG(Error) << "Present device exception: " << ex.what();
-      DeviceException(ex.what());
-    }
-    catch (...) {
-      USBGUARD_LOG(Error) << "BUG: Unknown device exception.";
-      DeviceException("BUG: Unknown device exception.");
-    }
-
-    /*
-     * We don't reject the device here (as is done in processDeviceInsertion)
-     * because the device was already connected to the system when USBGuard
-     * started. Therefore, if the device is malicious, it already had a chance
-     * to interact with the system.
-     */
-  }
+    USBGUARD_LOG(Debug) << "Processing backlog: _backlog.size() = " << _backlog.size();
 
-  void UEventDeviceManager::processDeviceInsertion(SysFSDevice& sysfs_device, const bool signal_present)
-  {
     try {
-      std::shared_ptr<UEventDevice> device = std::make_shared<UEventDevice>(*this, sysfs_device);
-      DeviceManager::AuthorizedDefaultType auth_default = getAuthorizedDefault();
-
-      if (device->isController() && !_enumeration_only_mode) {
-        USBGUARD_LOG(Debug) << "Setting default blocked state for controller device to " <<
-          DeviceManager::authorizedDefaultTypeToString(auth_default);
-        setDeviceAuthorizedDefault(&device->sysfsDevice(), auth_default);
-      }
-
-      insertDevice(device);
-
-      /*
-       * Signal insertions as presence if device enumeration hasn't
-       * completed yet.
-       */
-      if (signal_present) {
-        DeviceEvent(DeviceManager::EventType::Present, device);
-      }
-      else {
-        DeviceEvent(DeviceManager::EventType::Insert, device);
+      for (auto& it : _backlog) {
+        ueventProcessUEvent(std::move(it));
       }
-
-      return;
-    }
-    catch (const Exception& ex) {
-      USBGUARD_LOG(Error) << "Device insert exception: " << ex.message();
-      DeviceException(ex.message());
-    }
-    catch (const std::exception& ex) {
-      USBGUARD_LOG(Error) << "Device insert exception: " << ex.what();
-      DeviceException(ex.what());
-    }
-    catch (...) {
-      USBGUARD_LOG(Error) << "BUG: Unknown device insert exception.";
-      DeviceException("BUG: Unknown device insert exception.");
-    }
-
-    /*
-     * Skip device reject when in enumeration only mode.
-     */
-    if (_enumeration_only_mode) {
-      return;
-    }
-
-    /*
-     * Something went wrong and an exception was generated.
-     * Either the device is malicious or the system lacks some
-     * resources to successfully process the device. In either
-     * case, we take the safe route and fallback to rejecting
-     * the device.
-     */
-    USBGUARD_LOG(Warning) << "Rejecting device at syspath=" << sysfs_device.getPath();
-    sysfsApplyTarget(sysfs_device, Rule::Target::Reject);
-  }
-
-  void UEventDeviceManager::insertDevice(std::shared_ptr<UEventDevice> device)
-  {
-    DeviceManager::insertDevice(std::static_pointer_cast<Device>(device));
-    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
-    learnSysfsPath(device->getSysPath(), device->getID());
-  }
-
-  void UEventDeviceManager::processDeviceRemoval(const std::string& sysfs_devpath)
-  {
-    USBGUARD_LOG(Trace) << "sysfs_devpath=" << sysfs_devpath;
-
-    try {
-      std::shared_ptr<Device> device = removeDevice(sysfs_devpath);
-      DeviceEvent(DeviceManager::EventType::Remove, device);
     }
     catch (...) {
-      /* Ignore for now */
-      USBGUARD_LOG(Debug) << "Removal of an unknown device ignored.";
-      return;
-    }
-  }
-
-  std::shared_ptr<Device> UEventDeviceManager::removeDevice(const std::string& syspath)
-  {
-    /*
-     * FIXME: device map locking
-     */
-    if (!knownSysfsPath(syspath)) {
-      throw Exception("removeDevice", syspath, "unknown syspath, cannot remove device");
-    }
-
-    std::shared_ptr<Device> device = DeviceManager::removeDevice(getIDFromSysfsPath(syspath));
-    forgetSysfsPath(syspath);
-    return device;
-  }
-
-  uint32_t UEventDeviceManager::getIDFromSysfsPath(const std::string& sysfs_path) const
-  {
-    uint32_t id = 0;
-
-    if (knownSysfsPath(sysfs_path, &id)) {
-      return id;
-    }
-
-    throw Exception("UEventDeviceManager", sysfs_path, "unknown sysfs path");
-  }
-
-  bool UEventDeviceManager::isPresentSysfsPath(const std::string& sysfs_path) const
-  {
-    uint32_t id = 0;
-
-    if (knownSysfsPath(sysfs_path, &id)) {
-      return 0 == id;
-    }
-
-    return false;
-  }
-
-  bool UEventDeviceManager::knownSysfsPath(const std::string& sysfs_path, uint32_t* id_ptr) const
-  {
-    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id_ptr=" << (void*)id_ptr;
-    auto it = _sysfs_path_to_id_map.find(sysfs_path);
-    uint32_t known_id = 0;
-    bool known = false;
-
-    if (it != _sysfs_path_to_id_map.end()) {
-      known = true;
-      known_id = it->second;
-    }
-
-    if (id_ptr != nullptr) {
-      *id_ptr = known_id;
+      USBGUARD_LOG(Warning) << "ueventProcessBacklog: error processing uevent data";
     }
-
-    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " id_ptr=" << (void*)id_ptr << " known=" << known << " known_id="
-      << known_id;
-    return known;
-  }
-
-  void UEventDeviceManager::learnSysfsPath(const std::string& sysfs_path, uint32_t id)
-  {
-    USBGUARD_LOG(Trace) << "Learn sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id=" << id;
-    _sysfs_path_to_id_map[sysfs_path] = id;
-  }
-
-  void UEventDeviceManager::forgetSysfsPath(const std::string& sysfs_path)
-  {
-    USBGUARD_LOG(Trace) << "Forget sysfs_path=" << sysfs_path;
-    _sysfs_path_to_id_map.erase(sysfs_path);
   }
 } /* namespace usbguard */
 #endif /* HAVE_UDEV */
diff --git a/src/Library/UEventDeviceManager.hpp b/src/Library/UEventDeviceManager.hpp
index d9ff2fd..d351032 100644
--- a/src/Library/UEventDeviceManager.hpp
+++ b/src/Library/UEventDeviceManager.hpp
@@ -22,103 +22,39 @@
 #endif
 
 #if defined(HAVE_UEVENT)
-
 #include "Common/Thread.hpp"
-#include "SysFSDevice.hpp"
-
-#include "usbguard/Typedefs.hpp"
-#include "usbguard/DeviceManager.hpp"
-#include "usbguard/Device.hpp"
-#include "usbguard/Rule.hpp"
-#include "usbguard/USB.hpp"
-
-#include <condition_variable>
-#include <istream>
-
-#include <sys/stat.h>
-#include <dirent.h>
+#include "DeviceManagerBase.hpp"
 
 namespace usbguard
 {
-  class UEventDeviceManager;
+  class UEvent;
 
-  class UEventDevice : public Device, public USBDescriptorParserHooks
+  class UEventDeviceManager : public DeviceManagerBase
   {
-  public:
-    UEventDevice(UEventDeviceManager& device_manager, SysFSDevice& sysfs_device);
-
-    SysFSDevice& sysfsDevice();
-    const std::string& getSysPath() const;
-    bool isController() const override;
-    std::string getSystemName() const override;
-
-  private:
-    void parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
-      USBDescriptor* descriptor_out) override;
-    void loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor) override;
-    bool isLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
-    void updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
-
-    SysFSDevice _sysfs_device;
-  };
-
-  class UEventDeviceManager : public DeviceManager
-  {
-    using DeviceManager::insertDevice;
-
   public:
     UEventDeviceManager(DeviceManagerHooks& hooks);
     ~UEventDeviceManager();
 
-    void setEnumerationOnlyMode(bool state) override;
-
     void start() override;
     void stop() override;
     void scan() override;
     void scan(const std::string& devpath) override;
 
-    std::shared_ptr<Device> applyDevicePolicy(uint32_t id, Rule::Target target) override;
-    void insertDevice(std::shared_ptr<UEventDevice> device);
-    std::shared_ptr<Device> removeDevice(const std::string& syspath);
-
-    uint32_t getIDFromSysfsPath(const std::string& syspath) const;
-
-
   private:
     static bool ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
       const std::pair<std::string, std::string>& b);
-    static std::string ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry);
 
-    void sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target);
     void thread();
-
-    int ueventOpen();
     void ueventProcessRead();
     void ueventProcessUEvent(UEvent uevent);
     void ueventProcessAction(const std::string& action, const std::string& sysfs_devpath);
     int ueventEnumerateDevices();
     int ueventEnumerateTriggerDevice(const std::string& devpath, const std::string& buspath);
 
-    void processDevicePresence(SysFSDevice& sysfs_device);
-    void processDeviceInsertion(SysFSDevice& sysfs_device, bool signal_present);
-    void processDevicePresence(uint32_t id);
-    void processDeviceRemoval(const std::string& sysfs_devpath);
     void processBacklog();
 
     Thread<UEventDeviceManager> _thread;
-    int _uevent_fd;
-    int _wakeup_fd;
     std::vector<UEvent> _backlog;
-
-    bool isPresentSysfsPath(const std::string& sysfs_path) const;
-    bool knownSysfsPath(const std::string& sysfs_path, uint32_t* id = nullptr) const;
-    void learnSysfsPath(const std::string& sysfs_path, uint32_t id = 0);
-    void forgetSysfsPath(const std::string& sysfs_path);
-
-    std::map<std::string, uint32_t> _sysfs_path_to_id_map;
-
-    bool _enumeration_only_mode;
-    std::atomic<bool> _enumeration;
   };
 } /* namespace usbguard */
 #endif /* HAVE_UEVENT */
diff --git a/src/Library/UEventParser.cpp b/src/Library/UEventParser.cpp
index 1fb23ec..7d7e10f 100644
--- a/src/Library/UEventParser.cpp
+++ b/src/Library/UEventParser.cpp
@@ -28,7 +28,11 @@
 
 #include <fstream>
 
-#include <tao/pegtl/contrib/tracer.hpp>
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+  #include <tao/pegtl/contrib/trace.hpp>
+#else
+  #include <tao/pegtl/contrib/tracer.hpp>
+#endif
 using namespace tao;
 
 namespace usbguard
@@ -130,7 +134,11 @@ namespace usbguard
         tao::pegtl::parse<G, UEventParser::actions>(in, uevent);
       }
       else {
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+        tao::pegtl::complete_trace<G, UEventParser::actions>(in, uevent);
+#else
         tao::pegtl::parse<G, UEventParser::actions, tao::pegtl::tracer>(in, uevent);
+#endif
       }
     }
     catch (...) {
diff --git a/src/Library/UMockdevDeviceDefinition.cpp b/src/Library/UMockdevDeviceDefinition.cpp
index a8abb09..6bbf3a3 100644
--- a/src/Library/UMockdevDeviceDefinition.cpp
+++ b/src/Library/UMockdevDeviceDefinition.cpp
@@ -26,7 +26,11 @@
 #include <Common/Utility.hpp>
 
 #include <tao/pegtl.hpp>
-#include <tao/pegtl/contrib/tracer.hpp>
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+  #include <tao/pegtl/contrib/trace.hpp>
+#else
+  #include <tao/pegtl/contrib/tracer.hpp>
+#endif
 
 namespace usbguard
 {
@@ -49,12 +53,12 @@ namespace usbguard
      *  S:linkname: device node symlink (without the /dev/ prefix); ignored right now.
      */
 
-    struct str_path_prefix : TAOCPP_PEGTL_STRING("P:") {};
-    struct str_property_prefix : TAOCPP_PEGTL_STRING("E:") {};
-    struct str_ascii_attr_prefix : TAOCPP_PEGTL_STRING("A:") {};
-    struct str_binary_attr_prefix : TAOCPP_PEGTL_STRING("H:") {};
-    struct str_link_prefix : TAOCPP_PEGTL_STRING("L:") {};
-    struct str_name_prefix : TAOCPP_PEGTL_STRING("N:") {};
+    struct str_path_prefix : TAO_PEGTL_STRING("P:") {};
+    struct str_property_prefix : TAO_PEGTL_STRING("E:") {};
+    struct str_ascii_attr_prefix : TAO_PEGTL_STRING("A:") {};
+    struct str_binary_attr_prefix : TAO_PEGTL_STRING("H:") {};
+    struct str_link_prefix : TAO_PEGTL_STRING("L:") {};
+    struct str_name_prefix : TAO_PEGTL_STRING("N:") {};
 
     struct line_rest
       : star<not_at<ascii::eol>, not_at<eof>, ascii::any> {};
@@ -330,7 +334,11 @@ namespace usbguard
 
     try {
       tao::pegtl::string_input<> input(definitions_string, "<string>");
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+      tao::pegtl::complete_trace<UMockdevParser::grammar, UMockdevParser::actions>(input, definitions, umockdev_name);
+#else
       tao::pegtl::parse<UMockdevParser::grammar, UMockdevParser::actions, tao::pegtl::tracer>(input, definitions, umockdev_name);
+#endif
     }
     catch (...) {
       USBGUARD_LOG(Error) << "UMockdevDeviceDefinition: " << "<string>" << ": parsing failed at line <LINE>";
diff --git a/src/Library/UMockdevDeviceManager.cpp b/src/Library/UMockdevDeviceManager.cpp
index e1e8aee..9010a04 100644
--- a/src/Library/UMockdevDeviceManager.cpp
+++ b/src/Library/UMockdevDeviceManager.cpp
@@ -24,15 +24,14 @@
 #include "UMockdevDeviceManager.hpp"
 #include "UMockdevDeviceDefinition.hpp"
 
-#include "SysFSDevice.hpp"
 #include "Base64.hpp"
+#include "SysFSDevice.hpp"
 
 #include "Common/FDInputStream.hpp"
 #include "Common/Utility.hpp"
 
 #include "usbguard/Logger.hpp"
 #include "usbguard/Exception.hpp"
-#include "usbguard/USB.hpp"
 
 #include <stdexcept>
 #include <fstream>
@@ -51,231 +50,116 @@
 
 namespace usbguard
 {
-  namespace
-  {
-    void setDeviceAuthorizedDefault(SysFSDevice* device, DeviceManager::AuthorizedDefaultType auth_default)
-    {
-      if (auth_default == DeviceManager::AuthorizedDefaultType::Keep) {
-        return;
-      }
-
-      std::string auth_default_str = std::to_string(DeviceManager::authorizedDefaultTypeToInteger(auth_default));
-      device->setAttribute("authorized_default", auth_default_str);
-
-      if (device->readAttribute("authorized_default", /*strip_last_null=*/true) != auth_default_str) {
-        if (auth_default == DeviceManager::AuthorizedDefaultType::Internal) {
-          USBGUARD_LOG(Warning) << "No kernel support for authorized_default = 2, falling back to 0";
-          setDeviceAuthorizedDefault(device, DeviceManager::AuthorizedDefaultType::None);
-        }
-        else {
-          throw Exception("UEventDevice", device->getPath(), "Failed to set authorized_default to \"" + auth_default_str + "\"");
-        }
-      }
-    }
-  }  /* namespace */
 
-  UMockdevDevice::UMockdevDevice(UMockdevDeviceManager& device_manager, SysFSDevice& sysfs_device)
-    : Device(device_manager)
+  UMockdevDeviceManager::UMockdevDeviceManager(DeviceManagerHooks& hooks)
+    : DeviceManagerBase(hooks),
+      _thread(this, &UMockdevDeviceManager::thread)
   {
-    /*
-     * Look for the parent USB device and set the parent id
-     * if we find one.
-     */
-    const std::string sysfs_parent_path(sysfs_device.getParentPath());
-    const SysFSDevice sysfs_parent_device(sysfs_parent_path);
-
-    if (sysfs_parent_device.getUEvent().getAttribute("DEVTYPE") == "usb_device") {
-      setParentID(device_manager.getIDFromSysfsPath(sysfs_parent_path));
-    }
-    else {
-      setParentID(Rule::RootID);
-      setParentHash(hashString(sysfs_parent_path));
-    }
-
-    /*
-     * Set name
-     */
-    setName(sysfs_device.readAttribute("product", /*strip_last_null=*/true, /*optional=*/true));
-    /*
-     * Set USB ID
-     */
-    const std::string id_vendor(sysfs_device.readAttribute("idVendor", /*strip_last_null=*/true));
-    const std::string id_product(sysfs_device.readAttribute("idProduct", /*strip_last_null=*/true));
-    const USBDeviceID device_id(id_vendor, id_product);
-    setDeviceID(device_id);
-    /*
-     * Set serial number
-     */
-    setSerial(sysfs_device.readAttribute("serial", /*strip_last_null=*/true, /*optional=*/true));
-    /*
-     * Set USB port
-     */
-    setPort(sysfs_device.getName());
-    /*
-     * Sync authorization target
-     */
-    const std::string authorized_value(sysfs_device.readAttribute("authorized", /*strip_last_null=*/true));
+    umockdevInit();
+    USBGUARD_SYSCALL_THROW("UMockdevDeviceManager", (_wakeup_fd = eventfd(0, 0)) < 0);
+    _uevent_fd = ueventOpen();
+  }
 
-    if (authorized_value == "0") {
-      setTarget(Rule::Target::Block);
-    }
-    else if (authorized_value == "1") {
-      setTarget(Rule::Target::Allow);
-    }
-    else {
-      /*
-       * Block the device if we get an unexpected value
-       */
-      setTarget(Rule::Target::Block);
+  UMockdevDeviceManager::~UMockdevDeviceManager()
+  {
+    if (getRestoreControllerDeviceState()) {
+      setAuthorizedDefault(AuthorizedDefaultType::All); // FIXME: Set to previous state
     }
 
-    /*
-     * Process USB descriptor data.
-     *
-     * FDInputStream (stdio_filebuf) is responsible for closing the file
-     * descriptor returned by sysfs_device.openAttribute().
-     *
-     */
-    FDInputStream descriptor_stream(sysfs_device.openAttribute("descriptors"));
-    /*
-     * Find out the descriptor data stream size
-     */
-    size_t descriptor_expected_size = 0;
+    stop();
 
-    if (!descriptor_stream.good()) {
-      throw ErrnoException("UMockdevDevice", sysfs_device.getPath(), errno);
+    if (_uevent_fd >= 0) {
+      (void)close(_uevent_fd);
     }
 
-    initializeHash();
-    USBDescriptorParser parser(*this);
-
-    if ((descriptor_expected_size = parser.parse(descriptor_stream)) < sizeof(USBDeviceDescriptor)) {
-      throw Exception("UMockdevDevice", sysfs_device.getPath(),
-        "USB descriptor parser processed less data than the size of a USB device descriptor");
+    if (_wakeup_fd >= 0) {
+      (void)close(_wakeup_fd);
     }
-
-    finalizeHash();
-    /*
-     * From now own we take ownership of the SysFSDevice instance.
-     */
-    _sysfs_device = std::move(sysfs_device);
-  }
-
-  SysFSDevice& UMockdevDevice::sysfsDevice()
-  {
-    return _sysfs_device;
   }
 
-  const std::string& UMockdevDevice::getSysPath() const
+  void UMockdevDeviceManager::start()
   {
-    return _sysfs_device.getPath();
+    _thread.start();
   }
 
-  bool UMockdevDevice::isController() const
+  void UMockdevDeviceManager::stop()
   {
-    if (getPort().substr(0, 3) != "usb" || getInterfaceTypes().size() != 1) {
-      return false;
+    // stop monitor
+    _thread.stop(/*do_wait=*/false);
+    { /* Wakeup the device manager thread */
+      const uint64_t one = 1;
+      USBGUARD_SYSCALL_THROW("Linux device manager",
+        write(_wakeup_fd, &one, sizeof one) != sizeof one);
     }
-
-    const USBInterfaceType hub_interface("09:00:*");
-    return hub_interface.appliesTo(getInterfaceTypes()[0]);
-  }
-
-  std::string UMockdevDevice::getSystemName() const
-  {
-    return getSysPath();
+    _thread.wait();
   }
 
-  void UMockdevDevice::parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
-    USBDescriptor* descriptor_out)
+  void UMockdevDeviceManager::scan()
   {
     USBGUARD_LOG(Trace);
-    USBDescriptorParserHooks::parseUSBDescriptor(parser, descriptor_raw, descriptor_out);
+    Restorer<std::atomic<bool>, bool> \
+    restorer(_enumeration, /*transient=*/true, /*restored=*/false);
+    auto const enumeration_count = ueventEnumerateDevices();
+    USBGUARD_LOG(Debug) << "enumeration_count=" << enumeration_count;
 
-    if (isLinuxRootHubDeviceDescriptor(descriptor_out)) {
-      updateHashLinuxRootHubDeviceDescriptor(descriptor_raw);
+    if (enumeration_count == 0) {
+      return;
     }
-    else {
-      updateHash(descriptor_raw, static_cast<size_t>(descriptor_raw->bHeader.bLength));
+
+    if (enumeration_count < 0) {
+      throw Exception("UMockdevDeviceManager", "present devices", "failed to enumerate");
     }
   }
 
-  void UMockdevDevice::loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor)
+  void UMockdevDeviceManager::scan(const std::string& devpath)
   {
-    const auto type = static_cast<USBDescriptorType>(descriptor->bHeader.bDescriptorType);
-
-    switch (type) {
-    case USBDescriptorType::Device:
-      loadDeviceDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Configuration:
-      loadConfigurationDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Interface:
-      loadInterfaceDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::Endpoint:
-      loadEndpointDescriptor(parser, descriptor);
-      break;
-
-    case USBDescriptorType::AssociationInterface:
-    case USBDescriptorType::Unknown:
-    case USBDescriptorType::String:
-    default:
-      USBGUARD_LOG(Debug) << "Ignoring descriptor: type=" << (int)type
-        << " size=" << (int)descriptor->bHeader.bLength;
-    }
+    USBGUARD_LOG(Trace) << "devpath=" << devpath;
   }
 
-  bool UMockdevDevice::isLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
+  bool UMockdevDeviceManager::ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
+    const std::pair<std::string, std::string>& b)
   {
-    USBGUARD_LOG(Trace);
+    USBGUARD_LOG(Trace) << "a.second=" << a.second << " b.second=" << b.second;
+    const std::string full_a = a.second;
+    const std::string full_b = b.second;
+    const std::size_t component_count_a = countPathComponents(full_a);
+    const std::size_t component_count_b = countPathComponents(full_b);
 
-    if (descriptor->bHeader.bDescriptorType != USB_DESCRIPTOR_TYPE_DEVICE) {
+    if (component_count_a < component_count_b) {
+      return true;
+    }
+    else if (component_count_a > component_count_b) {
       return false;
     }
 
-    const USBDeviceDescriptor* const device_descriptor = \
-      reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
+    const std::string base_a = filenameFromPath(full_a, /*include_extension=*/true);
+    const std::string base_b = filenameFromPath(full_b, /*include_extension=*/true);
+    const bool a_has_usb_prefix = hasPrefix(base_a, "usb");
+    const bool b_has_usb_prefix = hasPrefix(base_b, "usb");
+    USBGUARD_LOG(Debug) << "a_prefix=" << a_has_usb_prefix << " b_prefix=" << b_has_usb_prefix;
 
-    if (device_descriptor->idVendor == 0x1d6b /* Linux Foundation */) {
-      switch (device_descriptor->idProduct) {
-      case 0x0001: /* 1.1 root hub */
-      case 0x0002: /* 2.0 root hub */
-      case 0x0003: /* 3.0 root hub */
+    if (a_has_usb_prefix) {
+      if (!b_has_usb_prefix) {
         return true;
-
-      default:
+      }
+      else {
+        return base_a < base_b;
+      }
+    }
+    else {
+      if (b_has_usb_prefix) {
         return false;
       }
     }
 
-    return false;
-  }
-
-  void UMockdevDevice::updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* const descriptor)
-  {
-    USBGUARD_LOG(Trace);
-    USBDeviceDescriptor descriptor_modified = *reinterpret_cast<const USBDeviceDescriptor*>(descriptor);
-    descriptor_modified.bcdDevice = 0;
-    updateHash(&descriptor_modified, sizeof descriptor_modified);
-  }
+    if (full_a.size() < full_b.size()) {
+      return true;
+    }
+    else if (full_a.size() > full_b.size()) {
+      return false;
+    }
 
-  /*
-   * Manager
-   */
-  UMockdevDeviceManager::UMockdevDeviceManager(DeviceManagerHooks& hooks)
-    : DeviceManager(hooks),
-      _thread(this, &UMockdevDeviceManager::thread),
-      _enumeration(false)
-  {
-    umockdevInit();
-    setEnumerationOnlyMode(/*state=*/false);
-    USBGUARD_SYSCALL_THROW("UEventDeviceManager", (_wakeup_fd = eventfd(0, 0)) < 0);
-    _uevent_fd = ueventOpen();
+    return full_a < full_b;
   }
 
   void UMockdevDeviceManager::umockdevInit()
@@ -293,22 +177,26 @@ namespace usbguard
       (void)dirent;
       struct stat st = {};
 
-      if (::stat(fullpath.c_str(), &st) != 0) {
+      if (::stat(fullpath.c_str(), &st) != 0)
+      {
         USBGUARD_LOG(Warning) << "stat() failed: " << fullpath << ": Skipping file!";
         return std::string();
       }
 
-      if (S_ISREG(st.st_mode)) {
+      if (S_ISREG(st.st_mode))
+      {
         return fullpath;
       }
-      else {
+      else
+      {
         return std::string();
       }
     };
     const auto lambdaUMockdevAddFromFile = [this](const std::string& fullpath, const std::string& loadpath) -> int {
       (void)fullpath;
 
-      for (const auto& device_path : umockdevLoadFromFile(loadpath)) {
+      for (const auto& device_path : umockdevLoadFromFile(loadpath))
+      {
         umockdevAdd(_sysfs_path_map.at(device_path));
       }
 
@@ -351,6 +239,46 @@ namespace usbguard
     //_sysfs_path_map.erase(sysfs_path);
   }
 
+  void UMockdevDeviceManager::umockdevProcessInotify()
+  {
+    char buffer[sizeof (inotify_event) + NAME_MAX + 1] __attribute__((aligned(__alignof__(inotify_event))));
+
+    if (read (_inotify_fd, buffer, sizeof buffer) <= 0) {
+      USBGUARD_LOG(Warning) << "Inotify event read size mismatch";
+      return;
+    }
+
+    const struct inotify_event* const event = reinterpret_cast<inotify_event*>(buffer);
+
+    if (event->len <= 0 || event->len > NAME_MAX) {
+      USBGUARD_LOG(Warning) << "Inotify event pathname size is out-of-range.";
+      return;
+    }
+
+    std::string definitions_path(event->name, event->len);
+    USBGUARD_LOG(Debug) << "inotify: definitions_path= " << definitions_path << " event_mask=" << event->mask;
+
+    if (event->mask & IN_CREATE) {
+      USBGUARD_LOG(Debug) << "inotify: IN_CREATE";
+
+      for (const auto& sysfs_path : umockdevLoadFromFile(_umockdev_deviceroot + "/" + definitions_path)) {
+        umockdevAdd(_sysfs_path_map.at(sysfs_path));
+        //umockdev_testbed_uevent(_testbed.get(), sysfs_path.c_str(), "add");
+      }
+    }
+    else if (event->mask & IN_DELETE) {
+      USBGUARD_LOG(Debug) << "inotify: IN_DELETE";
+
+      for (const auto& sysfs_path : umockdevRemoveByFile(_umockdev_deviceroot + "/" + definitions_path)) {
+        umockdevRemove(_sysfs_path_map.at(sysfs_path));
+        //umockdev_testbed_uevent(_testbed.get(), sysfs_path.c_str(), "remove");
+      }
+    }
+    else {
+      USBGUARD_LOG(Debug) << "inotify: Ignoring event.";
+    }
+  }
+
   std::vector<std::string> UMockdevDeviceManager::umockdevLoadFromFile(const std::string& definitions_path)
   {
     USBGUARD_LOG(Debug) << "Loading device definitions from " << definitions_path;
@@ -395,34 +323,6 @@ namespace usbguard
     return device_paths;
   }
 
-  std::vector<std::string> UMockdevDeviceManager::umockdevGetChildrenBySysfsPath(const std::string& sysfs_path)
-  {
-    std::vector<std::string> children;
-    auto it = _sysfs_path_map.find(sysfs_path);
-
-    if (it == _sysfs_path_map.cend()) {
-      throw Exception("UMockdevDeviceManager", sysfs_path, "cannot list children for undefined device");
-    }
-
-    const auto child_component_count = countPathComponents(sysfs_path) + 1;
-    ++it;
-
-    while (it != _sysfs_path_map.cend()) {
-      if (!hasPrefix(it->first, sysfs_path)) {
-        break;
-      }
-      else {
-        if (countPathComponents(it->first) == child_component_count) {
-          children.push_back(it->first);
-        }
-      }
-
-      ++it;
-    }
-
-    return children;
-  }
-
   std::vector<std::string> UMockdevDeviceManager::umockdevRemoveByFile(const std::string& definitions_path)
   {
     USBGUARD_LOG(Trace) << "definitions_path=" << definitions_path;
@@ -477,11 +377,40 @@ namespace usbguard
     return device_paths;
   }
 
+  std::vector<std::string> UMockdevDeviceManager::umockdevGetChildrenBySysfsPath(const std::string& sysfs_path)
+  {
+    std::vector<std::string> children;
+    auto it = _sysfs_path_map.find(sysfs_path);
+
+    if (it == _sysfs_path_map.cend()) {
+      throw Exception("UMockdevDeviceManager", sysfs_path, "cannot list children for undefined device");
+    }
+
+    const auto child_component_count = countPathComponents(sysfs_path) + 1;
+    ++it;
+
+    while (it != _sysfs_path_map.cend()) {
+      if (!hasPrefix(it->first, sysfs_path)) {
+        break;
+      }
+      else {
+        if (countPathComponents(it->first) == child_component_count) {
+          children.push_back(it->first);
+        }
+      }
+
+      ++it;
+    }
+
+    return children;
+  }
+
   /*
    * Set authorized=1 and add child devices.
    */
-  void UMockdevDeviceManager::umockdevAuthorizeBySysfsPath(const std::string& sysfs_path)
+  void UMockdevDeviceManager::sysfsAuthorizeDevice(SysFSDevice& sysfs_device)
   {
+    std::string sysfs_path = sysfs_device.getPath();
     /* set_attribute requires full device path */
     umockdev_testbed_set_attribute(_testbed.get(), (SysFSDevice::getSysfsRoot() + sysfs_path).c_str(), "authorized", "1");
 
@@ -489,13 +418,16 @@ namespace usbguard
       USBGUARD_LOG(Debug) << "(authorize) Adding " << child_device_path;
       umockdevAdd(_sysfs_path_map.at(child_device_path));
     }
+
+    sysfs_device.setAttribute("authorized", "1");
   }
 
   /*
    * Set authorized=0 and remove child devices.
    */
-  void UMockdevDeviceManager::umockdevDeauthorizeBySysfsPath(const std::string& sysfs_path)
+  void UMockdevDeviceManager::sysfsDeauthorizeDevice(SysFSDevice& sysfs_device)
   {
+    std::string sysfs_path = sysfs_device.getPath();
     /* set_attribute requires full device path */
     umockdev_testbed_set_attribute(_testbed.get(), (SysFSDevice::getSysfsRoot() + sysfs_path).c_str(), "authorized", "0");
 
@@ -503,185 +435,8 @@ namespace usbguard
       USBGUARD_LOG(Debug) << "(deauthorize) Removing " << child_device_path;
       umockdevRemove(child_device_path);
     }
-  }
-
-  void UMockdevDeviceManager::umockdevProcessInotify()
-  {
-    char buffer[sizeof (inotify_event) + NAME_MAX + 1] __attribute__((aligned(__alignof__(inotify_event))));
-
-    if (read (_inotify_fd, buffer, sizeof buffer) <= 0) {
-      USBGUARD_LOG(Warning) << "Inotify event read size mismatch";
-      return;
-    }
-
-    const struct inotify_event* const event = reinterpret_cast<inotify_event*>(buffer);
-
-    if (event->len <= 0 || event->len > NAME_MAX) {
-      USBGUARD_LOG(Warning) << "Inotify event pathname size is out-of-range.";
-      return;
-    }
-
-    std::string definitions_path(event->name, event->len);
-    USBGUARD_LOG(Debug) << "inotify: definitions_path= " << definitions_path << " event_mask=" << event->mask;
-
-    if (event->mask & IN_CREATE) {
-      USBGUARD_LOG(Debug) << "inotify: IN_CREATE";
-
-      for (const auto& sysfs_path : umockdevLoadFromFile(_umockdev_deviceroot + "/" + definitions_path)) {
-        umockdevAdd(_sysfs_path_map.at(sysfs_path));
-        //umockdev_testbed_uevent(_testbed.get(), sysfs_path.c_str(), "add");
-      }
-    }
-    else if (event->mask & IN_DELETE) {
-      USBGUARD_LOG(Debug) << "inotify: IN_DELETE";
-
-      for (const auto& sysfs_path : umockdevRemoveByFile(_umockdev_deviceroot + "/" + definitions_path)) {
-        umockdevRemove(_sysfs_path_map.at(sysfs_path));
-        //umockdev_testbed_uevent(_testbed.get(), sysfs_path.c_str(), "remove");
-      }
-    }
-    else {
-      USBGUARD_LOG(Debug) << "inotify: Ignoring event.";
-    }
-  }
-
-  UMockdevDeviceManager::~UMockdevDeviceManager()
-  {
-    if (getRestoreControllerDeviceState()) {
-      setAuthorizedDefault(AuthorizedDefaultType::All); // FIXME: Set to previous state
-    }
-
-    stop();
-
-    if (_uevent_fd >= 0) {
-      (void)close(_uevent_fd);
-    }
-
-    if (_wakeup_fd >= 0) {
-      (void)close(_wakeup_fd);
-    }
-  }
-
-  void UMockdevDeviceManager::setEnumerationOnlyMode(bool state)
-  {
-    _enumeration_only_mode = state;
-  }
-
-  void UMockdevDeviceManager::start()
-  {
-    _thread.start();
-  }
 
-  void UMockdevDeviceManager::stop()
-  {
-    // stop monitor
-    _thread.stop(/*do_wait=*/false);
-    { /* Wakeup the device manager thread */
-      const uint64_t one = 1;
-      USBGUARD_SYSCALL_THROW("Linux device manager",
-        write(_wakeup_fd, &one, sizeof one) != sizeof one);
-    }
-    _thread.wait();
-  }
-
-  void UMockdevDeviceManager::scan()
-  {
-    USBGUARD_LOG(Trace);
-    Restorer<std::atomic<bool>, bool> \
-    restorer(_enumeration, /*transient=*/true, /*restored=*/false);
-    auto const enumeration_count = ueventEnumerateDevices();
-    USBGUARD_LOG(Debug) << "enumeration_count=" << enumeration_count;
-
-    if (enumeration_count == 0) {
-      return;
-    }
-
-    if (enumeration_count < 0) {
-      throw Exception("UMockdevDeviceManager", "present devices", "failed to enumerate");
-    }
-  }
-
-  void UMockdevDeviceManager::scan(const std::string& devpath)
-  {
-    USBGUARD_LOG(Trace) << "devpath=" << devpath;
-  }
-
-  std::shared_ptr<Device> UMockdevDeviceManager::applyDevicePolicy(uint32_t id, Rule::Target target)
-  {
-    USBGUARD_LOG(Trace) << "id=" << id
-      << " target=" << Rule::targetToString(target);
-    std::shared_ptr<UMockdevDevice> device = std::static_pointer_cast<UMockdevDevice>(getDevice(id));
-    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
-    sysfsApplyTarget(device->sysfsDevice(), target);
-    device->setTarget(target);
-    return device;
-  }
-
-  int UMockdevDeviceManager::ueventOpen()
-  {
-    int socket_fd = -1;
-    USBGUARD_SYSCALL_THROW("UEvent device manager",
-      (socket_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0);
-
-    try {
-      const int optval = 1;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        setsockopt(socket_fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof optval) != 0);
-      /*
-       * Set a 1MiB receive buffer on the netlink socket to avoid ENOBUFS error
-       * in recvmsg.
-       */
-      const size_t rcvbuf_max = 1024 * 1024;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_max, sizeof rcvbuf_max) != 0);
-      struct sockaddr_nl sa = { };
-      sa.nl_family = AF_NETLINK;
-      sa.nl_pid = getpid();
-      sa.nl_groups = -1;
-      USBGUARD_SYSCALL_THROW("UEvent device manager",
-        bind(socket_fd, reinterpret_cast<const sockaddr*>(&sa), sizeof sa) != 0);
-    }
-    catch (...) {
-      (void)close(socket_fd);
-      throw;
-    }
-
-    return socket_fd;
-  }
-
-  void UMockdevDeviceManager::sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target)
-  {
-    std::string name;
-    std::string value("0");
-
-    switch (target) {
-    case Rule::Target::Allow:
-      umockdevAuthorizeBySysfsPath(sysfs_device.getPath());
-      name = "authorized";
-      value = "1";
-      break;
-
-    case Rule::Target::Block:
-      umockdevDeauthorizeBySysfsPath(sysfs_device.getPath());
-      name = "authorized";
-      value = "0";
-      break;
-
-    case Rule::Target::Reject:
-      name = "remove";
-      value = "1";
-      break;
-
-    case Rule::Target::Match:
-    case Rule::Target::Device:
-    case Rule::Target::Unknown:
-    case Rule::Target::Empty:
-    case Rule::Target::Invalid:
-    default:
-      throw std::runtime_error("Unknown rule target in applyDevicePolicy");
-    }
-
-    sysfs_device.setAttribute(name, value);
+    sysfs_device.setAttribute("authorized", "0");
   }
 
   void UMockdevDeviceManager::thread()
@@ -965,52 +720,6 @@ namespace usbguard
     }
   }
 
-  bool UMockdevDeviceManager::ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
-    const std::pair<std::string, std::string>& b)
-  {
-    USBGUARD_LOG(Trace) << "a.second=" << a.second << " b.second=" << b.second;
-    const std::string full_a = a.second;
-    const std::string full_b = b.second;
-    const std::size_t component_count_a = countPathComponents(full_a);
-    const std::size_t component_count_b = countPathComponents(full_b);
-
-    if (component_count_a < component_count_b) {
-      return true;
-    }
-    else if (component_count_a > component_count_b) {
-      return false;
-    }
-
-    const std::string base_a = filenameFromPath(full_a, /*include_extension=*/true);
-    const std::string base_b = filenameFromPath(full_b, /*include_extension=*/true);
-    const bool a_has_usb_prefix = hasPrefix(base_a, "usb");
-    const bool b_has_usb_prefix = hasPrefix(base_b, "usb");
-    USBGUARD_LOG(Debug) << "a_prefix=" << a_has_usb_prefix << " b_prefix=" << b_has_usb_prefix;
-
-    if (a_has_usb_prefix) {
-      if (!b_has_usb_prefix) {
-        return true;
-      }
-      else {
-        return base_a < base_b;
-      }
-    }
-    else {
-      if (b_has_usb_prefix) {
-        return false;
-      }
-    }
-
-    if (full_a.size() < full_b.size()) {
-      return true;
-    }
-    else if (full_a.size() > full_b.size()) {
-      return false;
-    }
-
-    return full_a < full_b;
-  }
-
   int UMockdevDeviceManager::ueventEnumerateDevices()
   {
     USBGUARD_LOG(Trace);
@@ -1024,55 +733,6 @@ namespace usbguard
         /*rootdir_required=*/false);
   }
 
-  std::string UMockdevDeviceManager::ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry)
-  {
-#if defined(_DIRENT_HAVE_D_TYPE)
-
-    if (direntry->d_type != DT_UNKNOWN) {
-      switch (direntry->d_type) {
-      case DT_LNK:
-        return symlinkPath(filepath);
-
-      case DT_DIR:
-        return filepath;
-
-      default:
-        return std::string();
-      }
-    }
-    else {
-      /*
-       * Unknown type. We have to call lstat.
-       */
-#endif
-      struct stat st = { };
-
-      if (lstat(filepath.c_str(), &st) != 0) {
-        /*
-         * Cannot stat, skip this entry.
-         */
-        USBGUARD_LOG(Warning) << "lstat(" << filepath << "): errno=" << errno;
-        return std::string();
-      }
-
-      if (S_ISLNK(st.st_mode)) {
-        return symlinkPath(filepath, &st);
-      }
-      else if (S_ISDIR(st.st_mode)) {
-        return filepath;
-      }
-      else {
-        return std::string();
-      }
-
-#if defined(_DIRENT_HAVE_D_TYPE)
-    }
-
-#endif
-    /* UNREACHABLE */
-    return std::string();
-  }
-
   int UMockdevDeviceManager::ueventEnumerateTriggerAndWaitForDevice(const std::string& devpath, const std::string& buspath)
   {
     USBGUARD_LOG(Trace) << "devpath=" << devpath << " buspath=" << buspath;
@@ -1141,192 +801,6 @@ namespace usbguard
     return 0;
   }
 
-  void UMockdevDeviceManager::processDevicePresence(const uint32_t id)
-  {
-    USBGUARD_LOG(Trace) << "id=" << id;
-
-    try {
-      std::shared_ptr<UMockdevDevice> device = \
-        std::static_pointer_cast<UMockdevDevice>(DeviceManager::getDevice(id));
-      device->sysfsDevice().reload();
-      /*
-       * TODO: Check attribute state
-       *  - authorized_default (in case of controller)
-       */
-      DeviceEvent(DeviceManager::EventType::Present, device);
-      return;
-    }
-    catch (const Exception& ex) {
-      USBGUARD_LOG(Error) << "Present device exception: " << ex.message();
-      DeviceException(ex.message());
-    }
-    catch (const std::exception& ex) {
-      USBGUARD_LOG(Error) << "Present device exception: " << ex.what();
-      DeviceException(ex.what());
-    }
-    catch (...) {
-      USBGUARD_LOG(Error) << "BUG: Unknown device exception.";
-      DeviceException("BUG: Unknown device exception.");
-    }
-
-    /*
-     * We don't reject the device here (as is done in processDeviceInsertion)
-     * because the device was already connected to the system when USBGuard
-     * started. Therefore, if the device is malicious, it already had a chance
-     * to interact with the system.
-     */
-  }
-
-  void UMockdevDeviceManager::processDeviceInsertion(SysFSDevice& sysfs_device, const bool signal_present)
-  {
-    USBGUARD_LOG(Trace) << "sysfs_device=" << sysfs_device.getPath();
-
-    try {
-      std::shared_ptr<UMockdevDevice> device = std::make_shared<UMockdevDevice>(*this, sysfs_device);
-      DeviceManager::AuthorizedDefaultType auth_default = getAuthorizedDefault();
-
-      if (device->isController() && !_enumeration_only_mode) {
-        USBGUARD_LOG(Debug) << "Setting default blocked state for controller device to " <<
-          DeviceManager::authorizedDefaultTypeToString(auth_default);
-        setDeviceAuthorizedDefault(&device->sysfsDevice(), auth_default);
-      }
-
-      insertDevice(device);
-
-      /*
-       * Signal insertions as presence if device enumeration hasn't
-       * completed yet.
-       */
-      if (signal_present) {
-        DeviceEvent(DeviceManager::EventType::Present, device);
-      }
-      else {
-        DeviceEvent(DeviceManager::EventType::Insert, device);
-      }
-
-      return;
-    }
-    catch (const Exception& ex) {
-      USBGUARD_LOG(Error) << "Device insert exception: " << ex.message();
-      DeviceException(ex.message());
-    }
-    catch (const std::exception& ex) {
-      USBGUARD_LOG(Error) << "Device insert exception: " << ex.what();
-      DeviceException(ex.what());
-    }
-    catch (...) {
-      USBGUARD_LOG(Error) << "BUG: Unknown device insert exception.";
-      DeviceException("BUG: Unknown device insert exception.");
-    }
-
-    /*
-     * Skip device reject when in enumeration only mode.
-     */
-    if (_enumeration_only_mode) {
-      return;
-    }
-
-    /*
-     * Something went wrong and an exception was generated.
-     * Either the device is malicious or the system lacks some
-     * resources to successfully process the device. In either
-     * case, we take the safe route and fallback to rejecting
-     * the device.
-     */
-    USBGUARD_LOG(Warning) << "Rejecting device at syspath=" << sysfs_device.getPath();
-    sysfsApplyTarget(sysfs_device, Rule::Target::Reject);
-  }
-
-  void UMockdevDeviceManager::insertDevice(std::shared_ptr<UMockdevDevice> device)
-  {
-    DeviceManager::insertDevice(std::static_pointer_cast<Device>(device));
-    std::unique_lock<std::mutex> device_lock(device->refDeviceMutex());
-    learnSysfsPath(device->getSysPath(), device->getID());
-  }
-
-  void UMockdevDeviceManager::processDeviceRemoval(const std::string& sysfs_devpath)
-  {
-    USBGUARD_LOG(Trace) << "sysfs_devpath=" << sysfs_devpath;
-
-    try {
-      std::shared_ptr<Device> device = removeDevice(sysfs_devpath);
-      DeviceEvent(DeviceManager::EventType::Remove, device);
-    }
-    catch (...) {
-      /* Ignore for now */
-      USBGUARD_LOG(Debug) << "Removal of an unknown device ignored.";
-      return;
-    }
-  }
-
-  std::shared_ptr<Device> UMockdevDeviceManager::removeDevice(const std::string& sysfs_path)
-  {
-    /*
-     * FIXME: device map locking
-     */
-    if (!knownSysfsPath(sysfs_path)) {
-      throw Exception("removeDevice", sysfs_path, "unknown syspath, cannot remove device");
-    }
-
-    std::shared_ptr<Device> device = DeviceManager::removeDevice(getIDFromSysfsPath(sysfs_path));
-    forgetSysfsPath(sysfs_path);
-    return device;
-  }
-
-  uint32_t UMockdevDeviceManager::getIDFromSysfsPath(const std::string& sysfs_path) const
-  {
-    uint32_t id = 0;
-
-    if (knownSysfsPath(sysfs_path, &id)) {
-      return id;
-    }
-
-    throw Exception("UMockdevDeviceManager", sysfs_path, "unknown sysfs path");
-  }
-
-  bool UMockdevDeviceManager::isPresentSysfsPath(const std::string& sysfs_path) const
-  {
-    uint32_t id = 0;
-
-    if (knownSysfsPath(sysfs_path, &id)) {
-      return 0 == id;
-    }
-
-    return false;
-  }
-
-  bool UMockdevDeviceManager::knownSysfsPath(const std::string& sysfs_path, uint32_t* id_ptr) const
-  {
-    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id_ptr=" << (void*)id_ptr;
-    auto it = _sysfs_path_to_id_map.find(sysfs_path);
-    uint32_t known_id = 0;
-    bool known = false;
-
-    if (it != _sysfs_path_to_id_map.end()) {
-      known = true;
-      known_id = it->second;
-    }
-
-    if (id_ptr != nullptr) {
-      *id_ptr = known_id;
-    }
-
-    USBGUARD_LOG(Trace) << "Known? sysfs_path=" << sysfs_path << " id_ptr=" << (void*)id_ptr << " known=" << known << " known_id="
-      << known_id;
-    return known;
-  }
-
-  void UMockdevDeviceManager::learnSysfsPath(const std::string& sysfs_path, uint32_t id)
-  {
-    USBGUARD_LOG(Trace) << "Learn sysfs_path=" << sysfs_path << " size=" << sysfs_path.size() << " id=" << id;
-    _sysfs_path_to_id_map[sysfs_path] = id;
-  }
-
-  void UMockdevDeviceManager::forgetSysfsPath(const std::string& sysfs_path)
-  {
-    USBGUARD_LOG(Trace) << "Forget sysfs_path=" << sysfs_path;
-    _sysfs_path_to_id_map.erase(sysfs_path);
-  }
 } /* namespace usbguard */
 #endif /* HAVE_UMOCKDEV */
 
diff --git a/src/Library/UMockdevDeviceManager.hpp b/src/Library/UMockdevDeviceManager.hpp
index 539d91f..2050e74 100644
--- a/src/Library/UMockdevDeviceManager.hpp
+++ b/src/Library/UMockdevDeviceManager.hpp
@@ -22,141 +22,67 @@
 #endif
 
 #if defined(HAVE_UMOCKDEV)
-
 #include "Common/Thread.hpp"
-#include "SysFSDevice.hpp"
+#include "DeviceManagerBase.hpp"
 #include "UMockdevDeviceDefinition.hpp"
 
-#include "usbguard/Typedefs.hpp"
-#include "usbguard/DeviceManager.hpp"
-#include "usbguard/Device.hpp"
-#include "usbguard/Rule.hpp"
-#include "usbguard/USB.hpp"
-
 #include <condition_variable>
-#include <istream>
-
-#include <sys/stat.h>
-#include <dirent.h>
-
 #include <umockdev.h>
 
 namespace usbguard
 {
-  class UMockdevDeviceManager;
+  class UEvent;
 
-  class UMockdevDevice : public Device, public USBDescriptorParserHooks
+  class UMockdevDeviceManager : public DeviceManagerBase
   {
-  public:
-    UMockdevDevice(UMockdevDeviceManager& device_manager, SysFSDevice& sysfs_device);
-
-    SysFSDevice& sysfsDevice();
-    const std::string& getSysPath() const;
-    bool isController() const override;
-    std::string getSystemName() const override;
-
-  private:
-    void parseUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor_raw,
-      USBDescriptor* descriptor_out) override;
-    void loadUSBDescriptor(USBDescriptorParser* parser, const USBDescriptor* descriptor) override;
-    bool isLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
-    void updateHashLinuxRootHubDeviceDescriptor(const USBDescriptor* descriptor);
-
-    SysFSDevice _sysfs_device;
-  };
-
-  /*
-   * TODO: Create a base class template that provides
-   *       a shared implementation for Linux based device manager.
-   */
-  class UMockdevDeviceManager : public DeviceManager
-  {
-    using DeviceManager::insertDevice;
-
   public:
     UMockdevDeviceManager(DeviceManagerHooks& hooks);
     ~UMockdevDeviceManager();
 
-    void setEnumerationOnlyMode(bool state) override;
-
     void start() override;
     void stop() override;
     void scan() override;
     void scan(const std::string& devpath) override;
 
-    std::shared_ptr<Device> applyDevicePolicy(uint32_t id, Rule::Target target) override;
-    void insertDevice(std::shared_ptr<UMockdevDevice> device);
-    std::shared_ptr<Device> removeDevice(const std::string& syspath);
+  private:
+    struct GObjectDeleter {
+      void operator()(void* gobject)
+      {
+        if (gobject != nullptr) {
+          g_object_unref(gobject);
+        }
+      }
+    };
 
-    uint32_t getIDFromSysfsPath(const std::string& sysfs_path) const;
+    static bool ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
+      const std::pair<std::string, std::string>& b);
 
-  protected:
     void umockdevInit();
-    std::vector<std::string> umockdevLoadFromFile(const std::string& definitions_path);
-    std::vector<std::string> umockdevRemoveByFile(const std::string& definitions_path);
-    void umockdevRemoveBySysfsPath(const std::string& sysfs_path);
-    void umockdevProcessInotify();
-    std::vector<std::string> umockdevGetChildrenBySysfsPath(const std::string& sysfs_path);
-    void umockdevAuthorizeBySysfsPath(const std::string& sysfs_path);
-    void umockdevDeauthorizeBySysfsPath(const std::string& sysfs_path);
     void umockdevAdd(const std::shared_ptr<UMockdevDeviceDefinition>& definition);
     void umockdevRemove(const std::shared_ptr<UMockdevDeviceDefinition>& definition);
     void umockdevRemove(const std::string& sysfs_path);
+    void umockdevProcessInotify();
+    std::vector<std::string> umockdevLoadFromFile(const std::string& definitions_path);
+    std::vector<std::string> umockdevRemoveByFile(const std::string& definitions_path);
+    std::vector<std::string> umockdevGetChildrenBySysfsPath(const std::string& sysfs_path);
 
-    int ueventOpen();
-    void sysfsApplyTarget(SysFSDevice& sysfs_device, Rule::Target target);
+    void sysfsAuthorizeDevice(SysFSDevice& sysfs_device) override;
+    void sysfsDeauthorizeDevice(SysFSDevice& sysfs_device) override;
 
     void thread();
     void ueventProcessRead();
     void ueventProcessUEvent(const UEvent& uevent);
-    static bool ueventEnumerateComparePath(const std::pair<std::string, std::string>& a,
-      const std::pair<std::string, std::string>& b);
     int ueventEnumerateDevices();
-
-    static std::string ueventEnumerateFilterDevice(const std::string& filepath, const struct dirent* direntry);
     int ueventEnumerateTriggerAndWaitForDevice(const std::string& devpath, const std::string& buspath);
 
-    void processDevicePresence(SysFSDevice& sysfs_device);
-
-    void processDeviceInsertion(SysFSDevice& sysfs_device, bool signal_present);
-    void processDevicePresence(uint32_t id);
-    void processDeviceRemoval(const std::string& sysfs_devpath);
-
-  private:
-    struct GObjectDeleter {
-      void operator()(void* gobject)
-      {
-        if (gobject != nullptr) {
-          g_object_unref(gobject);
-        }
-      }
-    };
-
     Thread<UMockdevDeviceManager> _thread;
     std::unique_ptr<UMockdevTestbed, GObjectDeleter> _testbed{nullptr};
     std::string _umockdev_deviceroot;
     int _inotify_fd{-1};
-    int _uevent_fd{-1};
-    int _wakeup_fd{-1};
-
-    /*
-     * The following maps sysfs devices paths to their IDs.
-     * The key must not contain the sysfs (/sys) directory
-     * root prefix in the path and must be normalized to not
-     * contain ./ and ../ path components.
-     */
-    std::map<std::string, uint32_t> _sysfs_path_to_id_map;
-
-    bool isPresentSysfsPath(const std::string& sysfs_path) const;
-    bool knownSysfsPath(const std::string& sysfs_path, uint32_t* id = nullptr) const;
-    void learnSysfsPath(const std::string& sysfs_path, uint32_t id = 0);
-    void forgetSysfsPath(const std::string& sysfs_path);
 
     std::map<std::string, std::shared_ptr<UMockdevDeviceDefinition>> _sysfs_path_map;
     std::multimap<std::string, std::weak_ptr<UMockdevDeviceDefinition>> _umockdev_file_map;
 
-    bool _enumeration_only_mode{false};
-    std::atomic<bool> _enumeration{false};
     std::condition_variable _enumeration_complete;
     std::mutex _enumeration_mutex;
   };
diff --git a/src/Library/public/usbguard/DeviceManager.cpp b/src/Library/public/usbguard/DeviceManager.cpp
index 630ba7a..c7c3784 100644
--- a/src/Library/public/usbguard/DeviceManager.cpp
+++ b/src/Library/public/usbguard/DeviceManager.cpp
@@ -71,7 +71,6 @@ namespace usbguard
 
   static const std::vector<std::pair<std::string, DeviceManager::AuthorizedDefaultType>> authorized_default_type_strings = {
     { "keep", DeviceManager::AuthorizedDefaultType::Keep },
-    { "wired", DeviceManager::AuthorizedDefaultType::Wired },
     { "none", DeviceManager::AuthorizedDefaultType::None },
     { "all", DeviceManager::AuthorizedDefaultType::All },
     { "internal", DeviceManager::AuthorizedDefaultType::Internal }
diff --git a/src/Library/public/usbguard/DeviceManager.hpp b/src/Library/public/usbguard/DeviceManager.hpp
index 4f3c358..bd0874d 100644
--- a/src/Library/public/usbguard/DeviceManager.hpp
+++ b/src/Library/public/usbguard/DeviceManager.hpp
@@ -60,8 +60,6 @@ namespace usbguard
      */
     enum class AuthorizedDefaultType {
       Keep = -128, /**< Do not change the authorization state. */
-      Wired = -1, /**< New wired USB devices start out authorized,
-                    wireless USB devices do not. */
       None = 0, /**< Every new device starts out deauthorized. */
       All = 1, /**< Every new device starts out authorized. */
       Internal = 2, /**< Internal devices start out authorized,
diff --git a/src/Library/public/usbguard/Exception.hpp b/src/Library/public/usbguard/Exception.hpp
index 476c2ae..b8c8687 100644
--- a/src/Library/public/usbguard/Exception.hpp
+++ b/src/Library/public/usbguard/Exception.hpp
@@ -122,11 +122,20 @@ namespace usbguard
       : Exception(context, object, ErrnoException::reasonFromErrno(errno_value))
     {
     }
-  private:
+
     static std::string reasonFromErrno(const int errno_value)
     {
-      char buffer[1024];
-      return std::string(strerror_r(errno_value, buffer, sizeof buffer));
+      char buffer[1024] = "Unknown error";
+      const char* error_message = buffer;
+      // See "man strerror_r" for why we need these two cases:
+#ifndef HAVE_GNU_STRERROR_R
+      // We have the XSI-compliant version of strerror_r with int return
+      strerror_r(errno_value, buffer, sizeof buffer);
+#else
+      // We have the GNU-specific version of strerror_r with char* return
+      error_message = strerror_r(errno_value, buffer, sizeof buffer);
+#endif
+      return std::string(error_message);
     }
   };
 
diff --git a/src/Library/public/usbguard/IPCClient.cpp b/src/Library/public/usbguard/IPCClient.cpp
index 1eadc0f..c86d4d8 100644
--- a/src/Library/public/usbguard/IPCClient.cpp
+++ b/src/Library/public/usbguard/IPCClient.cpp
@@ -86,6 +86,12 @@ namespace usbguard
   {
     return d_pointer->listDevices(query);
   }
+
+  bool IPCClient::checkIPCPermissions(const IPCServer::AccessControl::Section& section,
+    const IPCServer::AccessControl::Privilege& privilege)
+  {
+    return d_pointer->checkIPCPermissions(section, privilege);
+  }
 } /* namespace usbguard */
 
 /* vim: set ts=2 sw=2 et */
diff --git a/src/Library/public/usbguard/IPCClient.hpp b/src/Library/public/usbguard/IPCClient.hpp
index 922e334..b2ebaf5 100644
--- a/src/Library/public/usbguard/IPCClient.hpp
+++ b/src/Library/public/usbguard/IPCClient.hpp
@@ -21,6 +21,7 @@
 #include "DeviceManager.hpp"
 #include "Exception.hpp"
 #include "Interface.hpp"
+#include "IPCServer.hpp"
 #include "Typedefs.hpp"
 
 #include <string>
@@ -28,6 +29,9 @@
 #include <memory>
 
 #include <cstdint>
+#include <unistd.h>
+#include <sys/types.h>
+
 
 namespace usbguard
 {
@@ -150,6 +154,18 @@ namespace usbguard
       return listDevices("match");
     }
 
+    /**
+     * @brief Check if IPC client has enough permission for queried section with privilege.
+     *
+     * @param section Section to be checked for.
+     * @param privilege Privilege to be checked for.
+     *
+     * @return True if IPC client has enough permission
+     * for (section, privilege), otherwise false.
+     */
+    bool checkIPCPermissions(const IPCServer::AccessControl::Section& section,
+      const IPCServer::AccessControl::Privilege& privilege);
+
     /**
      * @brief Defines algorithm to perform in the case of IPC connection.
      */
@@ -207,6 +223,24 @@ namespace usbguard
       (void)rule_id;
     }
 
+    /**
+     * @brief Defines actions to perform when a USB device
+     * has been inserted, accepted, or rejected.
+     *
+     * @see \link Interface::DevicePolicyApplied()
+     * DevicePolicyApplied()\endlink
+     */
+    virtual void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id) override
+    {
+      (void)id;
+      (void)target_new;
+      (void)device_rule;
+      (void)rule_id;
+    }
+
     /**
      * @brief Defines algorithm to perform in the case that property parameter
      * has been changed.
diff --git a/src/Library/public/usbguard/IPCServer.cpp b/src/Library/public/usbguard/IPCServer.cpp
index 7c33e26..973eb8b 100644
--- a/src/Library/public/usbguard/IPCServer.cpp
+++ b/src/Library/public/usbguard/IPCServer.cpp
@@ -36,10 +36,8 @@ namespace usbguard
       throw Exception("IPC access control", "name too long", name);
     }
 
-    const std::string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
-
-    if (name.find_first_not_of(valid_chars) != std::string::npos) {
-      throw Exception("IPC access control", "name contains invalid character(s)", name);
+    if (!isValidName(name)) {
+      throw Exception("IPC access control", "invalid name format", name);
     }
   }
 
@@ -135,6 +133,10 @@ namespace usbguard
   bool IPCServer::AccessControl::hasPrivilege(IPCServer::AccessControl::Section section,
     IPCServer::AccessControl::Privilege privilege) const
   {
+    if (privilege == Privilege::NONE) {
+      return true;
+    }
+
     if (section == Section::ALL || section == Section::NONE) {
       throw USBGUARD_BUG("Cannot test against ALL, NONE sections");
     }
@@ -155,18 +157,26 @@ namespace usbguard
       throw USBGUARD_BUG("Cannot set privileges for NONE section");
     }
 
+    const uint8_t p = static_cast<uint8_t>(privilege);
+
     if (section == Section::ALL) {
-      for (const auto& value : {
+      for (const auto& s : {
           Section::POLICY,
           Section::PARAMETERS,
           Section::EXCEPTIONS,
           Section::DEVICES
         }) {
-        _access_control[value] |= static_cast<uint8_t>(privilege);
+        _access_control[s] |= p & ~ac_mask(s);
       }
     }
     else {
-      _access_control[section] |= static_cast<uint8_t>(privilege);
+      if (privilege != Privilege::ALL && (p & ac_mask(section))) {
+        throw std::runtime_error("Invalid privilege " +
+          privilegeToString(privilege) + " for section " +
+          sectionToString(section));
+      }
+
+      _access_control[section] |= p & ~ac_mask(section);
     }
   }
 
@@ -250,6 +260,32 @@ namespace usbguard
     merge(access_control);
   }
 
+  uint8_t IPCServer::AccessControl::ac_mask(IPCServer::AccessControl::Section section) const
+  {
+    const uint8_t MODIFY = static_cast<uint8_t>(Privilege::MODIFY);
+    const uint8_t LIST = static_cast<uint8_t>(Privilege::LIST);
+    const uint8_t LISTEN = static_cast<uint8_t>(Privilege::LISTEN);
+
+    switch (section) {
+    case Section::DEVICES:
+      return ~(MODIFY | LIST | LISTEN);
+
+    case Section::POLICY:
+      return ~(MODIFY | LIST);
+
+    case Section::EXCEPTIONS:
+      return ~(LISTEN);
+
+    case Section::PARAMETERS:
+      return ~(MODIFY | LIST | LISTEN);
+
+    case Section::ALL:
+    case Section::NONE:
+    default:
+      return 0xff;
+    }
+  }
+
   IPCServer::IPCServer()
     : d_pointer(usbguard::make_unique<IPCServerPrivate>(*this))
   {
@@ -284,6 +320,14 @@ namespace usbguard
     d_pointer->DevicePolicyChanged(id, target_old, target_new, device_rule, rule_id);
   }
 
+  void IPCServer::DevicePolicyApplied(uint32_t id,
+    Rule::Target target_new,
+    const std::string& device_rule,
+    uint32_t rule_id)
+  {
+    d_pointer->DevicePolicyApplied(id, target_new, device_rule, rule_id);
+  }
+
   void IPCServer::PropertyParameterChanged(const std::string& name,
     const std::string& value_old,
     const std::string& value_new)
diff --git a/src/Library/public/usbguard/IPCServer.hpp b/src/Library/public/usbguard/IPCServer.hpp
index aa13e6b..ddb1d8a 100644
--- a/src/Library/public/usbguard/IPCServer.hpp
+++ b/src/Library/public/usbguard/IPCServer.hpp
@@ -50,9 +50,9 @@ namespace usbguard
     /**
      * @brief Checks whether given name is a valid access control name.
      *
-     * Name is a valid access control name if and only if:
-     *  1. name is not longer then 32 characters.
-     *  2. name consists only from characters from set { A-Z, a-z, 0-9, _ }.
+     * Name is a valid access control name iff:
+     *  1. it is not longer then 32 characters
+     *  2. it matches regex [A-Za-z_][A-Za-z0-9_-]*[$]
      *
      * @param name Name to be verified.
      * @throw Exception If \p name is not a valid access control name.
@@ -277,6 +277,17 @@ namespace usbguard
         }
       };
 
+      /**
+       * @brief Get a privilege mask for given section
+       *
+       * For example, if the section is POLICY that has privileges MODIFY
+       * and LIST, the mask would be ~(MODIFY | LIST)
+       *
+       * @param section Section for which the privilege mask should be returned
+       * @return Privilege mask for section
+       */
+      uint8_t ac_mask(Section section) const;
+
       /**
        * @brief Access control represented by unordered map of
        * tuples (Section, 8b privileges).
@@ -323,6 +334,14 @@ namespace usbguard
       const std::string& device_rule,
       uint32_t rule_id);
 
+    /**
+     * @copydoc Interface::DevicePolicyApplied()
+     */
+    void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id);
+
     /**
      * @copydoc Interface::PropertyParameterChanged()
      */
diff --git a/src/Library/public/usbguard/Interface.hpp b/src/Library/public/usbguard/Interface.hpp
index 8cf1d55..48d8292 100644
--- a/src/Library/public/usbguard/Interface.hpp
+++ b/src/Library/public/usbguard/Interface.hpp
@@ -189,6 +189,32 @@ namespace usbguard
       const std::string& device_rule,
       uint32_t rule_id) = 0;
 
+    /**
+     * @brief Notify about the acceptance or rejection of a device.
+     *
+     * This signal is thrown whenever a device is inserted.
+     * It is also thrown when a device has been allowed or rejected.
+     *
+     * The device attribute dictionary contains the following attributes:
+     * - id (the USB device ID in the form VID:PID)
+     * - name
+     * - serial
+     * - via-port
+     * - hash
+     * - parent-hash
+     * - with-interface
+     *
+     * @param id ID of the device.
+     * @param target_new Current authorization target.
+     * @param device_rule Device specific rule.
+     * @param rule_id Rule ID of the matched rule.
+     * Otherwise a reserved rule ID value is used.
+     */
+    virtual void DevicePolicyApplied(uint32_t id,
+      Rule::Target target_new,
+      const std::string& device_rule,
+      uint32_t rule_id) = 0;
+
     /**
      * @brief Notify about a change of a property parameter.
      *
diff --git a/src/Library/public/usbguard/Policy.cpp b/src/Library/public/usbguard/Policy.cpp
index 8ebf997..136d502 100644
--- a/src/Library/public/usbguard/Policy.cpp
+++ b/src/Library/public/usbguard/Policy.cpp
@@ -114,10 +114,13 @@ namespace usbguard
 
     for (auto ruleset : _rulesets_ptr) {
       uint32_t id = ruleset->upsertRule(match_rule, new_rule, parent_insensitive);
-      if (id == Rule::DefaultID)
-	continue;
-      else
-	return id;
+
+      if (id == Rule::DefaultID) {
+        continue;
+      }
+      else {
+        return id;
+      }
     }
 
     return _rulesets_ptr.back()->appendRule(new_rule, Rule::LastID, /*lock*/true);
diff --git a/src/Library/public/usbguard/RuleParser.cpp b/src/Library/public/usbguard/RuleParser.cpp
index 140bf14..183117c 100644
--- a/src/Library/public/usbguard/RuleParser.cpp
+++ b/src/Library/public/usbguard/RuleParser.cpp
@@ -34,7 +34,11 @@
 #include <stdexcept>
 #include <stdlib.h>
 
-#include <tao/pegtl/contrib/tracer.hpp>
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+  #include <tao/pegtl/contrib/trace.hpp>
+#else
+  #include <tao/pegtl/contrib/tracer.hpp>
+#endif
 
 namespace usbguard
 {
@@ -48,7 +52,11 @@ namespace usbguard
         tao::pegtl::parse<RuleParser::rule_grammar, RuleParser::rule_parser_actions>(input, rule);
       }
       else {
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+        tao::pegtl::complete_trace<RuleParser::rule_grammar, RuleParser::rule_parser_actions>(input, rule);
+#else
         tao::pegtl::parse<RuleParser::rule_grammar, RuleParser::rule_parser_actions, tao::pegtl::tracer>(input, rule);
+#endif
       }
 
       return rule;
@@ -56,7 +64,11 @@ namespace usbguard
     catch (const tao::pegtl::parse_error& ex) {
       RuleParserError error(rule_spec);
       error.setHint(ex.what());
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+      error.setOffset(ex.positions().front().column);
+#else
       error.setOffset(ex.positions[0].byte_in_line);
+#endif
 
       if (!file.empty() || line != 0) {
         error.setFileInfo(file, line);
diff --git a/src/Library/public/usbguard/RuleSet.cpp b/src/Library/public/usbguard/RuleSet.cpp
index f534242..9f681a1 100644
--- a/src/Library/public/usbguard/RuleSet.cpp
+++ b/src/Library/public/usbguard/RuleSet.cpp
@@ -72,6 +72,7 @@ namespace usbguard
   uint32_t RuleSet::appendRule(const Rule& rule, uint32_t parent_id, bool lock)
   {
     std::unique_lock<std::mutex> op_lock(_op_mutex, std::defer_lock);
+    USBGUARD_LOG(Debug) << "appendRule parent:" << parent_id;
 
     if (lock) {
       op_lock.lock();
diff --git a/src/Tests/Fuzzers/Makefile.am b/src/Tests/Fuzzers/Makefile.am
index 423cc73..f926f0e 100644
--- a/src/Tests/Fuzzers/Makefile.am
+++ b/src/Tests/Fuzzers/Makefile.am
@@ -20,7 +20,7 @@ AM_CPPFLAGS=\
 	-I$(top_srcdir)/src \
 	-I$(top_srcdir)/src/Library \
 	-I$(top_srcdir)/src/Library/public \
-	-I$(top_srcdir)/src/ThirdParty/PEGTL/include
+	@pegtl_CFLAGS@
 
 AM_LDFLAGS=\
 	-static $(top_builddir)/libusbguard.la -lFuzzingEngine
diff --git a/src/Tests/Fuzzers/Makefile.in b/src/Tests/Fuzzers/Makefile.in
index 6b5ec19..35cf507 100644
--- a/src/Tests/Fuzzers/Makefile.in
+++ b/src/Tests/Fuzzers/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# Makefile.in generated by automake 1.16.2 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -325,6 +325,8 @@ ldap_CFLAGS = @ldap_CFLAGS@
 ldap_LIBS = @ldap_LIBS@
 libcapng_CFLAGS = @libcapng_CFLAGS@
 libcapng_LIBS = @libcapng_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
 libdir = @libdir@
 libexecdir = @libexecdir@
 localedir = @localedir@
@@ -345,6 +347,7 @@ protobuf_LIBS = @protobuf_LIBS@
 psdir = @psdir@
 qb_CFLAGS = @qb_CFLAGS@
 qb_LIBS = @qb_LIBS@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 seccomp_CFLAGS = @seccomp_CFLAGS@
 seccomp_LIBS = @seccomp_LIBS@
@@ -363,7 +366,7 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir)/src \
 	-I$(top_srcdir)/src/Library \
 	-I$(top_srcdir)/src/Library/public \
-	-I$(top_srcdir)/src/ThirdParty/PEGTL/include
+	@pegtl_CFLAGS@
 
 AM_LDFLAGS = \
 	-static $(top_builddir)/libusbguard.la -lFuzzingEngine
diff --git a/src/Tests/Fuzzers/fuzzer-rules.cpp b/src/Tests/Fuzzers/fuzzer-rules.cpp
index 5728d43..03fd909 100644
--- a/src/Tests/Fuzzers/fuzzer-rules.cpp
+++ b/src/Tests/Fuzzers/fuzzer-rules.cpp
@@ -20,7 +20,12 @@
 
 #include <cstdint>
 
-#include <tao/pegtl/contrib/tracer.hpp>
+#include <tao/pegtl.hpp>
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+  #include <tao/pegtl/contrib/trace.hpp>
+#else
+  #include <tao/pegtl/contrib/tracer.hpp>
+#endif
 #include <usbguard/Rule.hpp>
 #include <usbguard/RuleParser.hpp>
 
diff --git a/src/Tests/Fuzzers/fuzzer-uevent.cpp b/src/Tests/Fuzzers/fuzzer-uevent.cpp
index aeca009..df74b79 100644
--- a/src/Tests/Fuzzers/fuzzer-uevent.cpp
+++ b/src/Tests/Fuzzers/fuzzer-uevent.cpp
@@ -20,7 +20,12 @@
 
 #include <cstdint>
 
-#include <tao/pegtl/contrib/tracer.hpp>
+#include <tao/pegtl.hpp>
+#if TAO_PEGTL_VERSION_MAJOR >= 3
+  #include <tao/pegtl/contrib/trace.hpp>
+#else
+  #include <tao/pegtl/contrib/tracer.hpp>
+#endif
 #include <UEvent.hpp>
 
 using namespace usbguard;
diff --git a/src/Tests/LDAP/Sanity/ldap-nsswitch.sh b/src/Tests/LDAP/Sanity/ldap-nsswitch.sh
index ea5cb75..51d90d1 100755
--- a/src/Tests/LDAP/Sanity/ldap-nsswitch.sh
+++ b/src/Tests/LDAP/Sanity/ldap-nsswitch.sh
@@ -207,7 +207,7 @@ sudo -n kill $PID
 rckillb=$?
 sudo -n rm -f $PIDFILE
 
-grep_and_fail '(E) NSSwitch parsing: /etc/nsswitch.conf: No such file or directory'
+grep_and_fail '(i) Error when opening nsswitch file: /etc/nsswitch.conf: No such file or directory'
 
 if [ $rckillb -eq "1" ] # expected to fail
 then
diff --git a/src/Tests/LDAP/UseCase/ldap-test-3.sh b/src/Tests/LDAP/UseCase/ldap-test-3.sh
index b6a7131..13b785d 100755
--- a/src/Tests/LDAP/UseCase/ldap-test-3.sh
+++ b/src/Tests/LDAP/UseCase/ldap-test-3.sh
@@ -120,7 +120,7 @@ then
     KILLRC="0"
 fi
 
-grep "Rules: SourceLDAP: usbguard::Exception" $TMPDIR/usbguard.log
+grep "LDAPHandler query: ldap_search_ext_s: No such object" $TMPDIR/usbguard.log
 GREP1=$?
 
 grep -i "Sanitizer" $TMPDIR/usbguard.log
diff --git a/src/Tests/LDAP/UseCase/ldap-test-5.sh b/src/Tests/LDAP/UseCase/ldap-test-5.sh
index 21d9b2b..7dbc7f1 100755
--- a/src/Tests/LDAP/UseCase/ldap-test-5.sh
+++ b/src/Tests/LDAP/UseCase/ldap-test-5.sh
@@ -65,6 +65,7 @@ USBGuardHost: *
 USBGuardRuleOrder: 0
 USBID: 1038:1702
 USBSerial: ""
+USBWithConnectType: "hotplug"
 USBName: "SteelSeries Rival 100 Gaming Mouse"
 USBHash: "Ty9aMqdLp96HdR+3R3oFUeWy250MhWmb8zznl5+uHWk="
 USBParentHash: "OkrTUwAUxn55t8+ezGtkhdgxjz9TIluGUS+bjFE+iC4="
@@ -129,7 +130,7 @@ ROOTPW passme
 EOF
 
 sudo -n cat > "${TMPDIR}/result" <<EOF
-1: allow id 1038:1702 serial "" name "SteelSeries Rival 100 Gaming Mouse" hash "Ty9aMqdLp96HdR+3R3oFUeWy250MhWmb8zznl5+uHWk=" parent-hash "OkrTUwAUxn55t8+ezGtkhdgxjz9TIluGUS+bjFE+iC4=" via-port "2-3"
+1: allow id 1038:1702 serial "" name "SteelSeries Rival 100 Gaming Mouse" hash "Ty9aMqdLp96HdR+3R3oFUeWy250MhWmb8zznl5+uHWk=" parent-hash "OkrTUwAUxn55t8+ezGtkhdgxjz9TIluGUS+bjFE+iC4=" via-port "2-3" with-connect-type "hotplug"
 2: allow id 1038:1702 serial "" name "Keyboard..." hash "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=" parent-hash "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=" with-interface { 03:01:01 03:00:00 03:00:00 }
 3: allow
 4: allow id 1038:1702 serial "" name "Flash" hash "ccccccccccccccccccccccccccccccccccccccccccccc=" parent-hash "ddddddddddddddddddddddddddddddddddddddddddddd="
diff --git a/src/Tests/Makefile.am b/src/Tests/Makefile.am
index d8c8879..e10e845 100644
--- a/src/Tests/Makefile.am
+++ b/src/Tests/Makefile.am
@@ -23,7 +23,8 @@ AM_CPPFLAGS=\
 	-I$(top_srcdir)/src \
 	-I$(top_srcdir)/src/Library \
 	-I$(top_srcdir)/src/Library/public \
-	@catch_CFLAGS@ 
+	@catch_CFLAGS@ \
+	@pegtl_CFLAGS@
 
 EXTRA_DIST=\
 	$(top_srcdir)/src/Tests/custom.supp \
@@ -41,6 +42,7 @@ EXTRA_DIST=\
 	$(top_srcdir)/src/Tests/UseCase/002_cli_devices.sh \
 	$(top_srcdir)/src/Tests/UseCase/003_cli_devices_umockdev.sh \
 	$(top_srcdir)/src/Tests/UseCase/004_daemonize.sh \
+	$(top_srcdir)/src/Tests/UseCase/005_cli_devices_advanced.sh  \
 	$(top_srcdir)/src/Tests/UseCase/devices.umockdev \
 	$(top_srcdir)/src/Tests/Source/check-driver.sh \
 	$(top_srcdir)/src/Tests/Source/CheckScripts/copyright.sh \
@@ -84,7 +86,8 @@ TESTS+=\
 	UseCase/001_cli_policy.sh \
 	UseCase/002_cli_devices.sh \
 	UseCase/003_cli_devices_umockdev.sh \
-	UseCase/004_daemonize.sh
+	UseCase/004_daemonize.sh \
+	UseCase/005_cli_devices_advanced.sh
 endif
 
 if WITH_LDAP
diff --git a/src/Tests/Makefile.in b/src/Tests/Makefile.in
index 74b0256..2784a30 100644
--- a/src/Tests/Makefile.in
+++ b/src/Tests/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# Makefile.in generated by automake 1.16.2 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -96,7 +96,8 @@ TESTS = test-unit$(EXEEXT) test-regression$(EXEEXT) \
 @FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/001_cli_policy.sh \
 @FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/002_cli_devices.sh \
 @FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/003_cli_devices_umockdev.sh \
-@FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/004_daemonize.sh
+@FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/004_daemonize.sh \
+@FULL_TEST_SUITE_ENABLED_TRUE@	UseCase/005_cli_devices_advanced.sh
 
 @WITH_LDAP_TRUE@am__append_2 = \
 @WITH_LDAP_TRUE@	LDAP/Sanity/ldap-nsswitch.sh \
@@ -638,6 +639,8 @@ ldap_CFLAGS = @ldap_CFLAGS@
 ldap_LIBS = @ldap_LIBS@
 libcapng_CFLAGS = @libcapng_CFLAGS@
 libcapng_LIBS = @libcapng_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
 libdir = @libdir@
 libexecdir = @libexecdir@
 localedir = @localedir@
@@ -658,6 +661,7 @@ protobuf_LIBS = @protobuf_LIBS@
 psdir = @psdir@
 qb_CFLAGS = @qb_CFLAGS@
 qb_LIBS = @qb_LIBS@
+runstatedir = @runstatedir@
 sbindir = @sbindir@
 seccomp_CFLAGS = @seccomp_CFLAGS@
 seccomp_LIBS = @seccomp_LIBS@
@@ -677,7 +681,8 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir)/src \
 	-I$(top_srcdir)/src/Library \
 	-I$(top_srcdir)/src/Library/public \
-	@catch_CFLAGS@ 
+	@catch_CFLAGS@ \
+	@pegtl_CFLAGS@
 
 EXTRA_DIST = \
 	$(top_srcdir)/src/Tests/custom.supp \
@@ -695,6 +700,7 @@ EXTRA_DIST = \
 	$(top_srcdir)/src/Tests/UseCase/002_cli_devices.sh \
 	$(top_srcdir)/src/Tests/UseCase/003_cli_devices_umockdev.sh \
 	$(top_srcdir)/src/Tests/UseCase/004_daemonize.sh \
+	$(top_srcdir)/src/Tests/UseCase/005_cli_devices_advanced.sh  \
 	$(top_srcdir)/src/Tests/UseCase/devices.umockdev \
 	$(top_srcdir)/src/Tests/Source/check-driver.sh \
 	$(top_srcdir)/src/Tests/Source/CheckScripts/copyright.sh \
@@ -1434,6 +1440,13 @@ UseCase/004_daemonize.sh.log: UseCase/004_daemonize.sh
 	--log-file $$b.log --trs-file $$b.trs \
 	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
 	"$$tst" $(AM_TESTS_FD_REDIRECT)
+UseCase/005_cli_devices_advanced.sh.log: UseCase/005_cli_devices_advanced.sh
+	@p='UseCase/005_cli_devices_advanced.sh'; \
+	b='UseCase/005_cli_devices_advanced.sh'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
 LDAP/Sanity/ldap-nsswitch.sh.log: LDAP/Sanity/ldap-nsswitch.sh
 	@p='LDAP/Sanity/ldap-nsswitch.sh'; \
 	b='LDAP/Sanity/ldap-nsswitch.sh'; \
diff --git a/src/Tests/Source/CheckScripts/code-style.sh b/src/Tests/Source/CheckScripts/code-style.sh
index 93d688c..4f217bf 100755
--- a/src/Tests/Source/CheckScripts/code-style.sh
+++ b/src/Tests/Source/CheckScripts/code-style.sh
@@ -7,17 +7,22 @@
 #
 set -e -o pipefail
 
-SOURCE_FILEPATH="$1"
-ASTYLE="${PROJECT_ROOT}/scripts/astyle.sh"
-ASTYLERC_PATH="${PROJECT_ROOT}/src/astylerc"
-ASTYLE_VERSION_MAJOR=$(${ASTYLE} -V | sed -n 's|^Artistic Style Version \([0-9]\+\)\.[0-9]\+\.[0-9]\+|\1|p')
+SOURCE_FILEPATH="${1:?needs one filename as an argument}"
+ASTYLE="${PROJECT_ROOT:?not set in the environment}/scripts/astyle.sh"
+ASTYLE_VERSION_MAJOR=$(${ASTYLE} -V | sed -n 's|^Artistic Style Version \([0-9]\+\)\.[0-9]\+\(\.[0-9]\+\)\?|\1|p')
 
 if [[ "$ASTYLE_VERSION_MAJOR" -lt 3 ]]; then
   exit 77
 fi
 
-if [[ -n "$(${ASTYLE} --formatted --dry-run $(< ${ASTYLERC_PATH}) "$SOURCE_FILEPATH")" ]]; then
-   exit 1
-fi
+tempfile="$(mktemp)"
+delete_tempfile() {
+    rm -f "${tempfile}"
+}
+trap delete_tempfile EXIT
+
+"${ASTYLE}" < "${SOURCE_FILEPATH}" > "${tempfile}"
 
-exit 0
+# NOTE: We cannot use "exec" here or the trap callback above will not be run
+# NOTE: This is meant to return code 1 on non-empty diff
+diff -u --color=always "${SOURCE_FILEPATH}" "${tempfile}"
diff --git a/src/Tests/Source/CheckScripts/private-with-build-config.sh b/src/Tests/Source/CheckScripts/private-with-build-config.sh
index 2618f35..dc75e77 100755
--- a/src/Tests/Source/CheckScripts/private-with-build-config.sh
+++ b/src/Tests/Source/CheckScripts/private-with-build-config.sh
@@ -6,6 +6,7 @@
 # check-path-include: *.h
 # check-path-exclude: src/Library/public/*.hpp
 # check-path-exclude: src/Tests/*
+# check-path-exclude: src/test_filesystem.cpp
 #
 SOURCE_FILEPATH="$1"
 
diff --git a/src/Tests/Source/CheckScripts/spell-check.rws b/src/Tests/Source/CheckScripts/spell-check.rws
index 08b56ac..3a77478 100644
--- a/src/Tests/Source/CheckScripts/spell-check.rws
+++ b/src/Tests/Source/CheckScripts/spell-check.rws
@@ -90,8 +90,5 @@ usbguard
 usbN
 username
 usernames
-whitelist
-Whitelisting
-whitelisting
 workflow
 Yubikey
diff --git a/src/Tests/Source/CheckScripts/spell-check.sh b/src/Tests/Source/CheckScripts/spell-check.sh
index f3a0b87..5d62a2f 100755
--- a/src/Tests/Source/CheckScripts/spell-check.sh
+++ b/src/Tests/Source/CheckScripts/spell-check.sh
@@ -52,7 +52,7 @@ if [[ -n "${WORDS}" ]]; then
   echo "========================================"
   echo -e "${WORDS}"
   echo "========================================"
-  echo "To whitelist a word, add it to this dictionary:"
+  echo "To exempt a word, add it to this dictionary:"
   echo "  $(readlink -f "${EXTRA_DICTIONARY}")"
   echo
   retval=1
diff --git a/src/Tests/Source/check-driver.sh b/src/Tests/Source/check-driver.sh
index 51127c0..c13d911 100755
--- a/src/Tests/Source/check-driver.sh
+++ b/src/Tests/Source/check-driver.sh
@@ -39,6 +39,8 @@ GLOB_EXCLUDE=(
 *ThirdParty/*
 *build/*
 *m4/*
+*.pb.*
+*build-config.h
 )
 
 ##################
diff --git a/src/Tests/Unit/test_UEvent.cpp b/src/Tests/Unit/test_UEvent.cpp
index d03738f..759ee2c 100644
--- a/src/Tests/Unit/test_UEvent.cpp
+++ b/src/Tests/Unit/test_UEvent.cpp
@@ -17,7 +17,7 @@
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
 //
 #include <catch.hpp>
-#include <UEvent.hpp>
+#include <UEvent.cpp>
 
 using namespace usbguard;
 
diff --git a/src/Tests/Unit/test_UEventParser.cpp b/src/Tests/Unit/test_UEventParser.cpp
index 225baf0..cefbc3b 100644
--- a/src/Tests/Unit/test_UEventParser.cpp
+++ b/src/Tests/Unit/test_UEventParser.cpp
@@ -17,7 +17,7 @@
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
 //
 #include <catch.hpp>
-#include <UEvent.hpp>
+#include <UEventParser.cpp>
 
 using namespace usbguard;
 
diff --git a/src/Tests/Unit/test_UMockdevDeviceDefinition.cpp b/src/Tests/Unit/test_UMockdevDeviceDefinition.cpp
index e611b7a..fbe8bbd 100644
--- a/src/Tests/Unit/test_UMockdevDeviceDefinition.cpp
+++ b/src/Tests/Unit/test_UMockdevDeviceDefinition.cpp
@@ -17,7 +17,7 @@
 // Authors: Daniel Kopecek <dkopecek@redhat.com>
 //
 #include <catch.hpp>
-#include <UMockdevDeviceDefinition.hpp>
+#include <UMockdevDeviceDefinition.cpp>
 
 #include "test_UMockdevDeviceDefinition.data.hpp"
 
diff --git a/src/Tests/UseCase/002_cli_devices.sh b/src/Tests/UseCase/002_cli_devices.sh
index da1df4b..d9cb275 100755
--- a/src/Tests/UseCase/002_cli_devices.sh
+++ b/src/Tests/UseCase/002_cli_devices.sh
@@ -81,7 +81,7 @@ EOF
 
 # Create a dummy USB mass storage device
 sudo -n dd bs=4096 count=1 if=/dev/zero of=/tmp/usbguard_disk
-sudo -n modprobe dummy_hcd
+sudo -n modprobe dummy_hcd || exit 77 # Skip test if dummy_hcd is missing
 sudo -n rmmod g_mass_storage
 sudo -n modprobe g_mass_storage file=/tmp/usbguard_disk iSerialNumber=555666111
 
diff --git a/src/Tests/UseCase/003_cli_devices_umockdev.sh b/src/Tests/UseCase/003_cli_devices_umockdev.sh
index 43e622f..d0a7dda 100755
--- a/src/Tests/UseCase/003_cli_devices_umockdev.sh
+++ b/src/Tests/UseCase/003_cli_devices_umockdev.sh
@@ -46,29 +46,31 @@ function test_cli_devices()
   local id_dock="$(${USBGUARD} list-devices | sed -n 's|^\([0-9]\+\):.*hash "2y2qS3rcuMr1Ye5knWsbD8CGzPtrs+eiRR/haro7+Ng=".*$|\1|p')"
 
   if [ -z "$id_dock" ]; then
-    echo "Test error: Unable to parse device ID"
+    echo "Test error: Unable to parse device ID (test line ${LINENO})"
     exit 1
   fi
 
 
   ${USBGUARD} block-device "$id_dock"
   ${USBGUARD} allow-device "$id_dock"
+  sleep 1
 
   local id_dock_child="$(${USBGUARD} list-devices | sed -n 's|^\([0-9]\+\):.*hash "D3deklX12Ir3kJPfUZ5AQNwHeZn1bwtPkQkw6e+8B38=".*$|\1|p')"
   
   if [ -z "$id_dock_child" ]; then
-    echo "Test error: Unable to parse device ID"
+    echo "Test error: Unable to parse device ID (test line ${LINENO})"
     exit 1
   fi
 
   ${USBGUARD} block-device "$id_dock_child"
   ${USBGUARD} block-device "$id_dock"
   ${USBGUARD} allow-device "$id_dock"
+  sleep 1
 
   local id_dock_child="$(${USBGUARD} list-devices | sed -n 's|^\([0-9]\+\):.*hash "D3deklX12Ir3kJPfUZ5AQNwHeZn1bwtPkQkw6e+8B38=".*$|\1|p')"
   
   if [ -z "$id_dock_child" ]; then
-    echo "Test error: Unable to parse device ID"
+    echo "Test error: Unable to parse device ID (test line ${LINENO})"
     exit 1
   fi
 
diff --git a/src/Tests/UseCase/005_cli_devices_advanced.sh b/src/Tests/UseCase/005_cli_devices_advanced.sh
new file mode 100755
index 0000000..fb8103c
--- /dev/null
+++ b/src/Tests/UseCase/005_cli_devices_advanced.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors: Attila Lakatos <alakatos@redhat.com>
+#
+# Test CLI apply-device-policy.
+#
+source "${USBGUARD_TESTLIB_BASH}" || exit 129
+
+# Skip if udevmock-wrapper is not available
+command -v umockdev-wrapper || exit 77
+
+#set -x
+
+# TODO? Move to testlib
+export USBGUARD_TESTLIB_TMPDIR="$(mktemp -d --tmpdir usbguard-test.XXXXXX)"
+
+export config_path="${USBGUARD_TESTLIB_TMPDIR}/daemon.conf"
+export policy_path="${USBGUARD_TESTLIB_TMPDIR}/policy.conf"
+
+
+function test_cli_devices_advanced()
+{
+  set -e
+  sleep 4
+
+  export USBGUARD_DEBUG=1
+
+  ${USBGUARD} list-devices
+  ${USBGUARD} list-devices -a
+  ${USBGUARD} list-devices -b
+
+
+  #
+  # Test case: usbguard (allow|block|reject)-device rule is specified in multiple arguments
+  #
+  ${USBGUARD} allow-device allow
+  ${USBGUARD} allow-device block
+
+  ${USBGUARD} allow-device match name \"Test\"
+  ${USBGUARD} block-device match name \"Test\"
+  ${USBGUARD} reject-device match name \"Test\"
+  ${USBGUARD} allow-device match id 4321:"*"
+  ${USBGUARD} block-device match id 4321:"*"
+  ${USBGUARD} reject-device match id 4321:"*"
+  ${USBGUARD} allow-device match id 1532:006e serial \"\" name \"Razer DeathAdder Essential\" via-port \"1-4.4.3\" with-interface \{ 03:01:02 03:00:01 03:00:01 \} with-connect-type \"unknown\"
+
+
+  #
+  # Test case: usbguard (allow|block|reject)-device partial rule is specified in multiple arguments
+  #
+  ${USBGUARD} allow-device name \"Test\"
+  ${USBGUARD} block-device name \"Test\"
+  ${USBGUARD} reject-device name \"Test\"
+  ${USBGUARD} allow-device id 4321:"*"
+  ${USBGUARD} block-device id 4321:"*"
+  ${USBGUARD} reject-device id 4321:"*"
+  ${USBGUARD} allow-device id 1532:006e serial \"\" name \"Razer DeathAdder Essential\" via-port \"1-4.4.3\" with-interface \{ 03:01:02 03:00:01 03:00:01 \} with-connect-type \"unknown\"
+
+
+  #
+  # Test case: usbguard (allow|block|reject)-device rule is specified in one argument
+  #
+  ${USBGUARD} allow-device 'match name "Test"'
+  ${USBGUARD} block-device 'match name "Test"'
+  ${USBGUARD} reject-device 'match name "Test"'
+  ${USBGUARD} allow-device 'match id 4321:*'
+  ${USBGUARD} block-device 'match id 4321:*'
+  ${USBGUARD} reject-device 'match id 4321:*'
+  ${USBGUARD} allow-device 'match id 1532:006e serial "" name "Razer DeathAdder Essential" via-port "1-4.4.3" with-interface { 03:01:02 03:00:01 03:00:01 } with-connect-type "unknown"'
+
+  set +e
+  return 0
+}
+
+cat > "$config_path" <<EOF
+RuleFile=$policy_path
+ImplicitPolicyTarget=block
+PresentDevicePolicy=apply-policy
+PresentControllerPolicy=allow
+InsertedDevicePolicy=apply-policy
+RestoreControllerDeviceState=false
+DeviceManagerBackend=umockdev
+IPCAllowedUsers=$(id -un)
+IPCAllowedGroups=$(id -gn)
+DeviceRulesWithPort=false
+EOF
+
+cat > "$policy_path" <<EOF
+EOF
+
+export USBGUARD_UMOCKDEV_DEVICEDIR=/tmp/usbguard-dummy
+
+rm -rf "$USBGUARD_UMOCKDEV_DEVICEDIR"
+mkdir -p "$USBGUARD_UMOCKDEV_DEVICEDIR"
+cp "${srcdir}/src/Tests/UseCase/devices.umockdev" "${USBGUARD_UMOCKDEV_DEVICEDIR}"
+
+schedule "umockdev-wrapper ${USBGUARD_DAEMON} -d -k -P -c $config_path" :service
+schedule "test_cli_devices_advanced"
+execute 60
+retval=$?
+
+rm -rf "$USBGUARD_UMOCKDEV_DEVICEDIR"
+
+exit $retval
diff --git a/src/build-config.h.in.in b/src/build-config.h.in.in
index 06911da..63fc9c9 100644
--- a/src/build-config.h.in.in
+++ b/src/build-config.h.in.in
@@ -61,6 +61,14 @@
 /* Define to 1 if you have the `gettimeofday' function. */
 #undef HAVE_GETTIMEOFDAY
 
+/* Wether the GNU version of function basename(3) is available (or just the
+   POSIX one). */
+#undef HAVE_GNU_BASENAME
+
+/* Wether the GNU version of function strerror_r(3) is available (or just the
+   XSI one). */
+#undef HAVE_GNU_STRERROR_R
+
 /* Define to 1 if you have the <inttypes.h> header file. */
 #undef HAVE_INTTYPES_H
 
@@ -70,6 +78,9 @@
 /* cap-ng API usable */
 #undef HAVE_LIBCAPNG
 
+/* libcrypto API available */
+#undef HAVE_LIBCRYPTO
+
 /* libgcrypt API available */
 #undef HAVE_LIBGCRYPT
 
@@ -274,6 +285,9 @@
 /* Use libsodium as crypto backend */
 #undef USBGUARD_USE_LIBSODIUM
 
+/* Use openssl as crypto backend */
+#undef USBGUARD_USE_OPENSSL
+
 /* Version number of package */
 #undef VERSION
 
diff --git a/src/test_filesystem.cpp b/src/test_filesystem.cpp
new file mode 100644
index 0000000..9ca81a5
--- /dev/null
+++ b/src/test_filesystem.cpp
@@ -0,0 +1,24 @@
+//
+// Copyright (c) 2022 Sebastian Pipping <sebastian@pipping.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <filesystem>
+
+int main()
+{
+  return std::filesystem::temp_directory_path().is_absolute();
+}
+
+/* vim: set ts=2 sw=2 et */
diff --git a/usbguard-daemon.conf.in b/usbguard-daemon.conf.in
index f7fdb64..5ea9b5f 100644
--- a/usbguard-daemon.conf.in
+++ b/usbguard-daemon.conf.in
@@ -91,8 +91,6 @@ InsertedDevicePolicy=apply-policy
 # default authorization is set to.
 #
 # * keep         - do not change the authorization state
-# * wired        - new wired USB devices start out authorized, wireless USB
-#                  devices do not
 # * none         - every new device starts out deauthorized
 # * all          - every new device starts out authorized
 # * internal     - internal devices start out authorized, external devices start
@@ -131,7 +129,7 @@ RestoreControllerDeviceState=false
 DeviceManagerBackend=uevent
 
 #!!! WARNING: It's good practice to set at least one of the !!!
-#!!!          two options bellow. If none of them are set,  !!!
+#!!!          two options below. If none of them are set,   !!!
 #!!!          the daemon will accept IPC connections from   !!!
 #!!!          anyone, thus allowing anyone to modify the    !!!
 #!!!          rule set and (de)authorize USB devices.       !!!
@@ -159,18 +157,20 @@ IPCAllowedGroups=
 #
 # IPC access control definition files path.
 #
-# The files at this location will be interpreted by the daemon
-# as access control definition files. The (base)name of a file
-# should be in the form:
+# The files at this location will be interpreted by the USBGuard
+# daemon as access control definition files for the IPC interface.
+# The (base)name of a file should be in the form:
 #
 #   [user][:<group>]
 #
-# and should contain lines in the form:
+# where user is either username or UID and group is either groupname or GID.
+# IPC access control files should contain lines in the form:
 #
-#   <section>=[privilege] ...
+#   <section>=[privilege1][,privilege2] ...
 #
 # This way each file defines who is able to connect to the IPC
-# bus and what privileges he has.
+# bus and what privileges he has. Note that the IPC access control
+# files need to have file permissions set to 0600.
 #
 IPCAccessControlFiles=%sysconfdir%/usbguard/IPCAccessControl.d/
 
diff --git a/usbguard.service.in b/usbguard.service.in
index f34018e..c618618 100644
--- a/usbguard.service.in
+++ b/usbguard.service.in
@@ -5,14 +5,14 @@ Documentation=man:usbguard-daemon(8)
 
 [Service]
 AmbientCapabilities=
-CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER
-DeviceAllow=/dev/null rw
-DevicePolicy=strict
-ExecStart=%sbindir%/usbguard-daemon -k -c %sysconfdir%/usbguard/usbguard-daemon.conf
+CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_AUDIT_WRITE
+DevicePolicy=closed
+ExecStart=%sbindir%/usbguard-daemon -f -s -c %sysconfdir%/usbguard/usbguard-daemon.conf
 IPAddressDeny=any
 LockPersonality=yes
 MemoryDenyWriteExecute=yes
 NoNewPrivileges=yes
+PIDFile=/run/usbguard.pid
 PrivateDevices=yes
 PrivateTmp=yes
 ProtectControlGroups=yes
@@ -20,14 +20,14 @@ ProtectHome=yes
 ProtectKernelModules=yes
 ProtectSystem=yes
 ReadOnlyPaths=-/
-ReadWritePaths=-/dev/shm -%localstatedir%/log/usbguard -/tmp -%sysconfdir%/usbguard/
+ReadWritePaths=-/dev/shm -%localstatedir%/log/usbguard -/tmp -%sysconfdir%/usbguard/ -/var/run
 Restart=on-failure
 RestrictAddressFamilies=AF_UNIX AF_NETLINK
 RestrictNamespaces=yes
 RestrictRealtime=yes
 SystemCallArchitectures=native
 SystemCallFilter=@system-service
-Type=simple
+Type=forking
 UMask=0077
 
 [Install]