unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

commit 3256af1b0b3fb5170e96d66f9072ee45aea31b0f
parent 3c6d873d6af3452c39cc91037041a4fc2bf2f28c
Author: acidvegas <acid.vegas@acid.vegas>
Date: Thu, 28 May 2020 22:06:50 -0400

updated to 5.0.5

Diffstat:
MConfig | 17+++++++++++++++--
MMakefile.windows | 14+++++++++++++-
Mconfigure | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mconfigure.ac | 18++++++++++++------
Mdoc/Config.header | 2+-
Mdoc/RELEASE-NOTES.md | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdoc/conf/modules.conf | 4++++
Mdoc/conf/modules.sources.list | 3++-
Mdoc/conf/opers.conf | 2+-
Mdoc/conf/tls/curl-ca-bundle.crt | 38++++++++++++++++++++++++++++++++++++--
Mdoc/conf/unrealircd.remote.conf | 55+++++++++++++++++++++++++++++++++++++++----------------
Mextras/build-tests/nix/run-tests | 12++++++++++--
Mextras/build-tests/windows/compilecmd/vs2019.bat | 2+-
Mextras/c-ares.tar.gz | 0
Mextras/curlinstall | 2+-
Mextras/doxygen/Doxyfile | 2+-
Mextras/pcre2.tar.gz | 0
Minclude/h.h | 6+++++-
Minclude/modules.h | 21++++++++++++---------
Minclude/struct.h | 8++++++++
Minclude/windows/setup.h | 2+-
Msrc/api-clicap.c | 5+++--
Msrc/api-moddata.c | 1-
Msrc/channel.c | 16++++++++++++++++
Msrc/conf.c | 22++++++++++++++++++----
Msrc/crule.c | 14+++++++++++---
Msrc/match.c | 2+-
Msrc/misc.c | 12++++++++++++
Msrc/modules/Makefile.in | 16+++++++++++++++-
Msrc/modules/antimixedutf8.c | 22++++++++++++++++++++++
Msrc/modules/chanmodes/censor.c | 4++--
Msrc/modules/chanmodes/delayjoin.c | 4++--
Msrc/modules/chanmodes/floodprot.c | 20+++++++++++++-------
Msrc/modules/chanmodes/history.c | 10+++++++---
Msrc/modules/chanmodes/link.c | 60++++++++++++++++++++++++++++++++----------------------------
Msrc/modules/chanmodes/nocolor.c | 4++--
Msrc/modules/chanmodes/noctcp.c | 4++--
Msrc/modules/chanmodes/nonotice.c | 9+++++----
Msrc/modules/chanmodes/regonlyspeak.c | 4++--
Msrc/modules/chanmodes/secureonly.c | 7+++----
Msrc/modules/chanmodes/stripcolor.c | 4++--
Msrc/modules/channeldb.c | 16+++++++++-------
Asrc/modules/clienttagdeny.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/dccdeny.c | 10+++++-----
Msrc/modules/echo-message.c | 44++++++++++++++++++++++++++++++--------------
Msrc/modules/join.c | 31++++++++++++++++---------------
Msrc/modules/labeled-response.c | 18++++++++++++++----
Msrc/modules/message-tags.c | 10----------
Msrc/modules/message.c | 182++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/modules/names.c | 3++-
Msrc/modules/nocodes.c | 4++--
Msrc/modules/restrict-commands.c | 100++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/modules/sajoin.c | 2++
Msrc/modules/sapart.c | 4++--
Msrc/modules/svsnick.c | 2+-
Asrc/modules/targetfloodprot.c | 315+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/tkl.c | 26+++++++++++++++++++++++++-
Msrc/modules/tkldb.c | 12++++++++----
Asrc/modules/typing-indicator.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/usermodes/censor.c | 4++--
Msrc/modules/usermodes/noctcp.c | 7++++---
Msrc/modules/usermodes/privdeaf.c | 4++--
Msrc/modules/usermodes/regonlymsg.c | 4++--
Msrc/modules/usermodes/secureonlymsg.c | 4++--
Msrc/modules/whox.c | 2+-
Msrc/send.c | 4++--
Msrc/socket.c | 1+
Msrc/user.c | 3++-
Msrc/version.c.SH | 2+-
Msrc/windows/UnrealIRCd.exe.manifest | 2+-
Msrc/windows/unrealinst.iss | 2+-

71 files changed, 1323 insertions(+), 350 deletions(-)

diff --git a/Config b/Config
@@ -88,6 +88,19 @@ fi
 if [ "x$INSTALLCURL" = "x1" ]; then
 	extras/curlinstall "$PRIVATELIBDIR" || exit 1
 fi
+# At least do SOME parallel compiling by default, IF:
+# - the MAKE environment variable is not set
+# - the MAKEFLAGS environment variable is not set
+# - we are using GNU Make
+if [ "x$MAKE" = "x" ]; then
+	if [ "x$MAKEFLAGS" = "x" ]; then
+		if make --version 2>&1|grep -q "GNU Make"; then
+			echo "Running with 4 concurrent build processes by default (make -j4)."
+			export MAKE='make -j4'
+		fi
+	fi
+fi
+
 echo $CONF
 $CONF || exit 1
 cd "$UNREALCWD"
@@ -313,7 +326,7 @@ echo "We will now ask you a number of questions. You can just press ENTER to acc
 echo ""
 
 # This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0 unrealircd-5.0.0-rc2 unrealircd-5.0.0-rc1"
+UNREALRELEASES="unrealircd-5.0.4 unrealircd-5.0.3.1 unrealircd-5.0.3 unrealircd-5.0.2 unrealircd-5.0.1 unrealircd-5.0.0 unrealircd-5.0.0-rc2 unrealircd-5.0.0-rc1"
 if [ -f "config.settings" ]; then
 	. ./config.settings
 else
@@ -479,7 +492,7 @@ while [ -z "$TEST" ] ; do
     echo ""
     echo "Do you want to enable remote includes?"
     echo "This allows stuff like this in your configuration file:"
-    echo "include \"http://www.somesite.org/files/opers.conf\";"
+    echo "include \"https://www.somesite.org/files/opers.conf\";"
     echo $n "[$TEST] -> $c"
 	read cc
     if [ -z "$cc" ] ; then
diff --git a/Makefile.windows b/Makefile.windows
@@ -290,7 +290,10 @@ DLL_FILES=SRC/MODULES/CLOAK.DLL \
  SRC/MODULES/USERHOST-TAG.DLL \
  SRC/MODULES/REQUIRE-MODULE.DLL \
  SRC/MODULES/IDENT_LOOKUP.DLL \
- SRC/MODULES/HISTORY.DLL
+ SRC/MODULES/HISTORY.DLL \
+ SRC/MODULES/TARGETFLOODPROT.DLL \
+ SRC/MODULES/TYPING-INDICATOR.DLL \
+ SRC/MODULES/CLIENTTAGDENY.DLL
 
 
 ALL: CONF UNREALSVC.EXE UnrealIRCd.exe MODULES 
@@ -1081,4 +1084,13 @@ src/modules/ident_lookup.dll: src/modules/ident_lookup.c $(INCLUDES)
 src/modules/history.dll: src/modules/history.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/history.c $(MODLFLAGS)
 
+src/modules/targetfloodprot.dll: src/modules/targetfloodprot.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/targetfloodprot.c $(MODLFLAGS)
+
+src/modules/typing-indicator.dll: src/modules/typing-indicator.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/typing-indicator.c $(MODLFLAGS)
+
+src/modules/clienttagdeny.dll: src/modules/clienttagdeny.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/clienttagdeny.c $(MODLFLAGS)
+
 dummy:
diff --git 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 unrealircd 5.0.4.
+# Generated by GNU Autoconf 2.69 for unrealircd 5.0.5.
 #
 # Report bugs to <https://bugs.unrealircd.org/>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unrealircd'
 PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='5.0.4'
-PACKAGE_STRING='unrealircd 5.0.4'
+PACKAGE_VERSION='5.0.5'
+PACKAGE_STRING='unrealircd 5.0.5'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
@@ -1325,7 +1325,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 unrealircd 5.0.4 to adapt to many kinds of systems.
+\`configure' configures unrealircd 5.0.5 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1391,7 +1391,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unrealircd 5.0.4:";;
+     short | recursive ) echo "Configuration of unrealircd 5.0.5:";;
    esac
   cat <<\_ACEOF
 
@@ -1544,7 +1544,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unrealircd configure 5.0.4
+unrealircd configure 5.0.5
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1913,7 +1913,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 unrealircd $as_me 5.0.4, which was
+It was created by unrealircd $as_me 5.0.5, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2321,7 +2321,7 @@ _ACEOF
 
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="4"
+UNREAL_VERSION_MINOR="5"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -4078,6 +4078,104 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
+  # Added in UnrealIRCd 5.0.5 (default on Ubuntu 19.10)
+  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
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-clash-protection" >&5
+$as_echo_n "checking whether C compiler accepts -fstack-clash-protection... " >&6; }
+if ${ax_cv_check_cflags__Werror___fstack_clash_protection+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror  -fstack-clash-protection"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ax_cv_check_cflags__Werror___fstack_clash_protection=yes
+else
+  ax_cv_check_cflags__Werror___fstack_clash_protection=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fstack_clash_protection" >&5
+$as_echo "$ax_cv_check_cflags__Werror___fstack_clash_protection" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fstack_clash_protection" = xyes; then :
+  HARDEN_CFLAGS="$HARDEN_CFLAGS -fstack-clash-protection"
+else
+  :
+fi
+
+  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
+
+
+  # Control Flow Enforcement (ROP hardening) - requires CPU hardware support
+  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
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fcf-protection" >&5
+$as_echo_n "checking whether C compiler accepts -fcf-protection... " >&6; }
+if ${ax_cv_check_cflags__Werror___fcf_protection+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror  -fcf-protection"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ax_cv_check_cflags__Werror___fcf_protection=yes
+else
+  ax_cv_check_cflags__Werror___fcf_protection=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fcf_protection" >&5
+$as_echo "$ax_cv_check_cflags__Werror___fcf_protection" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fcf_protection" = xyes; then :
+  HARDEN_CFLAGS="$HARDEN_CFLAGS -fcf-protection"
+else
+  :
+fi
+
+  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
+
+
   # At the link step, we might want -pie (GCC) or -Wl,-pie (Clang on OS X)
   #
   # The linker checks also compile code, so we need to include -fPIE as well.
@@ -6783,12 +6881,12 @@ if test -n "$PCRE2_CFLAGS"; then
     pkg_cv_PCRE2_CFLAGS="$PCRE2_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.00\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.00") 2>&5
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.34\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.34") 2>&5
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_PCRE2_CFLAGS=`$PKG_CONFIG --cflags "libpcre2-8 >= 10.00" 2>/dev/null`
+  pkg_cv_PCRE2_CFLAGS=`$PKG_CONFIG --cflags "libpcre2-8 >= 10.34" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -6800,12 +6898,12 @@ if test -n "$PCRE2_LIBS"; then
     pkg_cv_PCRE2_LIBS="$PCRE2_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.00\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.00") 2>&5
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.34\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.34") 2>&5
   ac_status=$?
   $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_PCRE2_LIBS=`$PKG_CONFIG --libs "libpcre2-8 >= 10.00" 2>/dev/null`
+  pkg_cv_PCRE2_LIBS=`$PKG_CONFIG --libs "libpcre2-8 >= 10.34" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -6826,9 +6924,9 @@ else
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre2-8 >= 10.00" 2>&1`
+	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre2-8 >= 10.34" 2>&1`
         else
-	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre2-8 >= 10.00" 2>&1`
+	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre2-8 >= 10.34" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$PCRE2_PKG_ERRORS" >&5
@@ -6852,7 +6950,7 @@ fi
 
 if test "$has_system_pcre2" = "no"; then :
 
-pcre2_version="10.33"
+pcre2_version="10.34"
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting PCRE2 regex library" >&5
 $as_echo "extracting PCRE2 regex library" >&6; }
 cur_dir=`pwd`
@@ -6869,7 +6967,7 @@ fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring PCRE2 regex library" >&5
 $as_echo "configuring PCRE2 regex library" >&6; }
 cd pcre2-$pcre2_version
-./configure --enable-jit --enable-shared --disable-unicode --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
+./configure --enable-jit --enable-shared --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling PCRE2 regex library" >&5
 $as_echo "compiling PCRE2 regex library" >&6; }
 $ac_cv_prog_MAKER || exit 1
@@ -7089,7 +7187,7 @@ fi
 
 if test "$has_system_cares" = "no"; then :
 
-cares_version="1.15.0"
+cares_version="1.16.0"
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting c-ares resolver library" >&5
 $as_echo "extracting c-ares resolver library" >&6; }
 cur_dir=`pwd`
@@ -8300,7 +8398,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 unrealircd $as_me 5.0.4, which was
+This file was extended by unrealircd $as_me 5.0.5, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -8363,7 +8461,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="\\
-unrealircd config.status 5.0.4
+unrealircd config.status 5.0.5
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
@@ -7,7 +7,7 @@ dnl src/windows/unrealinst.iss
 dnl doc/Config.header
 dnl src/version.c.SH
 
-AC_INIT([unrealircd], [5.0.4], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [5.0.5], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
 AC_CONFIG_SRCDIR([src/ircd.c])
 AC_CONFIG_HEADER([include/setup.h])
 AC_CONFIG_AUX_DIR([autoconf])
@@ -34,7 +34,7 @@ UNREAL_VERSION_MAJOR=["0"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)])
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR=["4"]
+UNREAL_VERSION_MINOR=["5"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MINOR], [$UNREAL_VERSION_MINOR], [Minor version number (e.g.: Z for X.Y.Z)])
 
 # The version suffix such as a beta marker or release candidate
@@ -129,6 +129,12 @@ AS_IF([test x"$hardening" != x"no"], [
       check_cc_flag([--param ssp-buffer-size=1], [HARDEN_CFLAGS="$HARDEN_CFLAGS --param ssp-buffer-size=1"],
         [], [-fstack-protector-all])])])
 
+  # Added in UnrealIRCd 5.0.5 (default on Ubuntu 19.10)
+  check_cc_flag([-fstack-clash-protection], [HARDEN_CFLAGS="$HARDEN_CFLAGS -fstack-clash-protection"])
+
+  # Control Flow Enforcement (ROP hardening) - requires CPU hardware support
+  check_cc_flag([-fcf-protection], [HARDEN_CFLAGS="$HARDEN_CFLAGS -fcf-protection"])
+
   # At the link step, we might want -pie (GCC) or -Wl,-pie (Clang on OS X)
   #
   # The linker checks also compile code, so we need to include -fPIE as well.
@@ -531,12 +537,12 @@ export PATH_SEPARATOR
 dnl Use system pcre2 when available, unless --without-system-pcre2.
 has_system_pcre2="no"
 AS_IF([test "x$with_system_pcre2" = "xyes"],[
-PKG_CHECK_MODULES([PCRE2], libpcre2-8 >= 10.00,[has_system_pcre2=yes
+PKG_CHECK_MODULES([PCRE2], libpcre2-8 >= 10.34,[has_system_pcre2=yes
 AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libpcre2*])],[has_system_pcre2=no])])
 
 AS_IF([test "$has_system_pcre2" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW PCRE2 RELEASE!
-pcre2_version="10.33"
+pcre2_version="10.34"
 AC_MSG_RESULT(extracting PCRE2 regex library)
 cur_dir=`pwd`
 cd extras
@@ -553,7 +559,7 @@ else
 fi
 AC_MSG_RESULT(configuring PCRE2 regex library)
 cd pcre2-$pcre2_version
-./configure --enable-jit --enable-shared --disable-unicode --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
+./configure --enable-jit --enable-shared --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
 AC_MSG_RESULT(compiling PCRE2 regex library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing PCRE2 regex library)
@@ -627,7 +633,7 @@ AS_IF([test "$has_system_cares" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW C-ARES RELEASE!
 dnl NOTE: when changing this here, ALSO change it in extras/curlinstall
 dnl       and in the comment in this file around line 400!
-cares_version="1.15.0"
+cares_version="1.16.0"
 AC_MSG_RESULT(extracting c-ares resolver library)
 cur_dir=`pwd`
 cd extras
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 5.0.4
+                                for UnrealIRCd 5.0.5
                                     
 This program will help you to compile your IRC server, and ask you
 questions regarding the compile-time settings of it during the process. 
diff --git a/doc/RELEASE-NOTES.md b/doc/RELEASE-NOTES.md
@@ -1,6 +1,82 @@
-UnrealIRCd 5.0.4 Release Notes
+UnrealIRCd 5.0.5 Release Notes
 ===============================
 
+This release mainly focuses on new features, while also fixing a few bugs.
+
+Fixes:
+* [except ban { }](https://www.unrealircd.org/docs/Except_ban_block)
+  without 'type' was not exempting from gline.
+* Channel mode ```+L #forward``` and ```+k key```: should forward
+  on wrong key, but was also redirecting on correct key.
+* Crash on 32-bit machines in tkldb (on start or rehash)
+* Crash when saving channeldb when a parameter channel mode is combined
+  with ```+P``` and that module was loaded after channeldb. This may
+  happen if you use 3rd party modules that add parameter channel modes.
+
+Enhancements:
+* [Spamfilter](https://www.unrealircd.org/docs/Spamfilter) is now UTF8-aware.
+  This means, among other things:
+  * Case insensitive matches work better. For example, with extended
+    Latin, a spamfilter on ```ę``` now also matches ```Ę```.
+  * Other PCRE2 features such as [\p](https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC5)
+    are now available. For example you can now set a spamfilter with the regex
+    ```\p{Arabic}``` to block all Arabic script, or ```\p{Cyrillic}``` to
+    block all Cyrillic script (such as Russian).
+    Please do use these new tools with care. Blocking an entire language
+    or script is quite a drastic measure.
+  * These new features require the PCRE2 10.34 regex library. If you
+    have a lower version on your system then UnrealIRCd will fall back
+    to using the UnrealIRCd-shipped-library version 10.34. The only
+    downside to that is that compiling during ```./Config``` may take
+    a little longer than usual.
+* [antimixedutf8](https://www.unrealircd.org/docs/Set_block#set::antimixedutf8)
+  has been improved to detect CJK and other scripts and this will now
+  catch more mixed UTF8 spam. Note that, if you previously manually
+  set the score very tight (much lower than the default of 10) then you
+  may have to increase it a bit, or not, depending on your network.
+* Support for IRCv3 [+typing clienttag](https://ircv3.net/specs/client-tags/typing.html),
+  which adds "user is typing" support to channels and PM (if the client
+  supports it).
+* New flood countermeasure,
+  [set::anti-flood::target-flood](https://www.unrealircd.org/docs/Set_block#set%3A%3Aanti-flood%3A%3Atarget-flood),
+  which limits flooding to channels and users. This is only meant as a
+  filter for high rate floods. You are still encouraged to use
+  [channel mode +f](https://www.unrealircd.org/docs/Anti-flood_features#Channel_mode_f)
+  in channels which give you more customized and fine-grained options
+  to deal with low- and medium-rate floods.
+* If a chanop /INVITEs someone, it will now override ban forwards
+  such as ```+b ~f:#forward:*!*@*```.
+
+Changes:
+* We now do parallel builds by default (```make -j4```) within ./Config,
+  unless the ```$MAKE``` or ```$MAKEFLAGS``` environment variable is set.
+* [set::restrict-commands](https://www.unrealircd.org/docs/Set_block#set%3A%3Arestrict-commands):
+  * The ```disable``` option is now removed as it is implied. In other words: if
+    you want to disable a command, then simply don't use ```connect-delay```.
+  * You can now have a block without ```connect-delay``` but still make
+    users bypass the restriction with ```exempt-identified``` and/or
+    ```exempt-reputation-score```. Previously this was not possible.
+* We now give an error when an IRCOp tries to place an *LINE that already
+  exists. (Previously we sometimes replaced the existing *LINE and other
+  times we did not)
+* Add Polish HELPOP (help.pl.conf)
+
+Module coders / Developers:
+* Breaking API change in ```HOOKTYPE_CAN_SEND_TO_USER``` and
+  ```HOOKTYPE_CAN_SEND_TO_CHANNEL```: the final argument has changed
+  from ```int notice``` to ```SendType sendtype```, which is an
+  enum, since we now have 3 message options (PRIVMSG, NOTICE, TAGMSG).
+
+Upgrading from UnrealIRCd 4?
+-----------------------------
+
+Are you upgrading from UnrealIRCd 4.x to UnrealIRCd 5?
+Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5). At the
+very least, check out [Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
+
+UnrealIRCd 5.0.4
+------------------
+
 This new 5.0.4 version fixes quite a number of bugs. It contains only two small feature improvements.
 
 Fixes:
@@ -35,13 +111,6 @@ Changes:
   [set::plaintext-policy::oper-message](https://www.unrealircd.org/docs/Set_block#set::plaintext-policy).
 * The French HELPOP text was updated.
 
-Upgrading from UnrealIRCd 4?
------------------------------
-
-Are you upgrading from UnrealIRCd 4.x to UnrealIRCd 5?
-Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5). At the
-very least, check out [Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
-
 UnrealIRCd 5.0.3.1
 -------------------
 This fixes a crash issue after REHASH in 5.0.3.
diff --git a/doc/conf/modules.conf b/doc/conf/modules.conf
@@ -156,8 +156,10 @@ loadmodule "extbans/textban";     /* +b ~T */
 loadmodule "extbans/timedban";    /* +b ~t */
 
 // IRCv3 Extensions
+loadmodule "account-notify";
 loadmodule "account-tag";
 loadmodule "batch";
+loadmodule "clienttagdeny";
 loadmodule "echo-message";
 loadmodule "labeled-response";
 loadmodule "link-security";
@@ -166,6 +168,7 @@ loadmodule "message-tags";
 loadmodule "plaintext-policy";
 loadmodule "server-time";
 loadmodule "sts";
+loadmodule "typing-indicator";
 
 // Other
 loadmodule "antimixedutf8";
@@ -180,6 +183,7 @@ loadmodule "history_backend_mem";
 #loadmodule "history_backend_null";
 loadmodule "ident_lookup";
 loadmodule "jointhrottle";
+#loadmodule "targetfloodprot";
 loadmodule "tkldb";
 loadmodule "tls_antidos";
 loadmodule "userhost-tag";
diff --git a/doc/conf/modules.sources.list b/doc/conf/modules.sources.list
@@ -1 +1 @@
-https://modules.unrealircd.org/modules.list
+https://modules.unrealircd.org/modules.list
+\ No newline at end of file
diff --git a/doc/conf/opers.conf b/doc/conf/opers.conf
@@ -101,7 +101,7 @@ operclass netadmin {
 	permissions {
 		chat { notice { global; } }
 		client { set; }
-		immune { join-flood; max-conncurrent-conversionations; maxchannelsperuser; nick-flood; server-ban { spamfilter; } }
+		immune { join-flood; max-conncurrent-conversionations; maxchannelsperuser; nick-flood; server-ban { spamfilter; }; target-flood; }
 		kill;
 		channel { operonly; override; see; }
 		route;
diff --git a/doc/conf/tls/curl-ca-bundle.crt b/doc/conf/tls/curl-ca-bundle.crt
@@ -1,7 +1,7 @@
 ##
 ## Bundle of CA Root Certificates
 ##
-## Certificate data from Mozilla as of: Wed Oct 16 03:12:09 2019 GMT
+## Certificate data from Mozilla as of: Wed Jan  1 04:12:10 2020 GMT
 ##
 ## This is a bundle of X.509 certificates of public Certificate Authorities
 ## (CA). These were automatically extracted from Mozilla's root certificates
@@ -14,7 +14,7 @@
 ## Just configure this file as the SSLCACertificateFile.
 ##
 ## Conversion done with mk-ca-bundle.pl version 1.27.
-## SHA256: c979c6f35714a0fedb17d9e5ba37adecbbc91a8faf4186b4e23d6f9ca44fd6cb
+## SHA256: f3bdcd74612952da8476a9d4147f50b29ad0710b7dd95b4c8690500209986d70
 ##
 
 
@@ -3430,3 +3430,37 @@ hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
 60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
 dBb9HxEGmpv0
 -----END CERTIFICATE-----
+
+Entrust Root Certification Authority - G4
+=========================================
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
+bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
+dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
+AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
+L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D
+umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
+3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
+8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ
+e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
+ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
+xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
+7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
+dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
+Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
+MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q
+jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht
+7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
+YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
+jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
+RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA
+JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
+kcpG2om3PVODLAgfi49T3f+sHw==
+-----END CERTIFICATE-----
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -16,6 +16,11 @@ class servers { pingfreq 120; maxclients  10; sendq 1M; connfreq 30;           }
 
 allow { ip *; class clients; maxperip 2; }
 
+#require authentication {
+#	mask *@*;
+#	reason "8,4   E N T E R   T H E   V O I D   ";
+#}
+
 listen { ip *; port 6667;      options { clientsonly;      } }
 listen { ip *; port 6697;      options { clientsonly; tls; } }
 listen { ip *; port REDACTED;  options { serversonly; tls; } }
@@ -90,12 +95,12 @@ set {
 	level-on-join "op";
 	restrict-channelmodes "CnLpPs";
 	restrict-commands {
-		channel-message { connect-delay 60;   exempt-identified yes; exempt-reputation-score 24; }
-		invite          { connect-delay 3600; exempt-identified yes; exempt-reputation-score 24; }
-		join            { connect-delay 60;   exempt-identified yes; exempt-reputation-score 24; }
-		list            { connect-delay 120;  exempt-identified yes; exempt-reputation-score 24; }
-		private-message { connect-delay 300;  exempt-identified yes; exempt-reputation-score 24; }
-		private-notice  { connect-delay 3600; exempt-identified yes; exempt-reputation-score 24; }
+		channel-message { connect-delay 60;   exempt-identified yes; exempt-reputation-score 100; }
+		invite          { connect-delay 3600; exempt-identified yes; exempt-reputation-score 100; }
+		join            { connect-delay 60;   exempt-identified yes; exempt-reputation-score 100; }
+		list            { connect-delay 120;  exempt-identified yes; exempt-reputation-score 100; }
+		private-message { connect-delay 300;  exempt-identified yes; exempt-reputation-score 100; }
+		private-notice  { connect-delay 3600; exempt-identified yes; exempt-reputation-score 100; }
 	}
 	auto-join "#superbowl";
 	oper-auto-join "#help";
@@ -132,24 +137,37 @@ set {
 		user-message "4WARNING: You are using an outdated SSL/TLS protocol or cipher";
 		oper-message "Network operators must connect using an up-to-date SSL/TLS protocol or cipher";
 	}
+	#handshake-timeout 60s; # Enable for authprompt
 	anti-flood {
-		away-flood 3:300;
+		away-flood    3:300;
 		connect-flood 3:300;
-		invite-flood 3:300;
-		join-flood 3:300;
-		knock-flood 3:300;
-		max-concurrent-conversations { users 5; new-user-every 60s; }
-		nick-flood 3:300;
+		invite-flood  3:300;
+		join-flood    3:300;
+		knock-flood   3:300;
+		nick-flood    3:300;
+		max-concurrent-conversations {
+			users 5;
+			new-user-every 60s;
+		}
+		#target-flood {
+		#	channel-privmsg 45:5;
+		#	channel-notice  15:5;
+		#	channel-tagmsg  15:5;
+		#	private-privmsg 30:5;
+		#	private-notice  10:5;
+		#	private-tagmsg  10:5;
+		#}
 		unknown-flood-amount 2048;
 		unknown-flood-bantime 1h;
 	}
-	default-bantime 1d;
+	default-bantime 30d;
+	modef-default-unsettime 5;
 	spamfilter {
 		ban-time 1d;
 		ban-reason "8,4   E N T E R   T H E   V O I D   ";
 		except "#anythinggoes";
 	}
-	max-targets-per-command { kick 1; part 1; privmsg 1; }
+	max-targets-per-command { kick 1; notice 1; part 1; privmsg 1; }
 	hide-ban-reason yes;
 	reject-message {
 		gline                "8,4   E N T E R   T H E   V O I D   ";
@@ -163,8 +181,13 @@ set {
 		score 10;
 		ban-action block;
 		ban-reason "8,4   E N T E R   T H E   V O I D   ";
-		ban-time 1h;
 	}
+	#authentication-prompt {
+	#	enabled yes;
+	#	message "The server requires clients from this IP address to authenticate with a registered nickname and password.";
+	#	message "Please reconnect using SASL, or authenticate now by typing: /QUOTE AUTH nick:password";
+	#	fail-message "Authentication failed";
+	#}
 	connthrottle {
 		known-users   { minimum-reputation-score 24; sasl-bypass yes;       }
 		new-users     { local-throttle 20:60;        global-throttle 30:60; }
@@ -176,7 +199,7 @@ set {
 			max-storage-per-channel { lines 100; time 1d; }
 		}
 	}
-	manual-ban-target ip;
+	hide-idle-time { policy always; }
 }
 
 hideserver {
diff --git a/extras/build-tests/nix/run-tests b/extras/build-tests/nix/run-tests
@@ -23,8 +23,15 @@ fi
 git clone -q https://github.com/unrealircd/unrealircd-tests.git
 cd unrealircd-tests
 
-# Run the test framework, testing both services:
+# FreeBSD has various issues with the tests from us and others,
+# better set a flag to keep it simple:
+FREEBSD=0
 if uname -a|grep -q FreeBSD; then
+	FREEBSD=1
+fi
+
+# Run the test framework, testing both services:
+if [ "$FREEBSD" = 1 ]; then
 	# FreeBSD runs without services since they fail mysteriously:
 	./run -services none || exit 1
 else
@@ -34,7 +41,8 @@ else
 fi
 
 # Do cipherscan test at the end
-if [[ "$OSTYPE" != "freebsd"* ]]; then
+# Has problems on non-Linux-64-bit, so we skip there:
+if [ "$FREEBSD" = 0 -a "$HOSTNAME" != "ub18-ia32" ]; then
 	sleep 2
 	cd ../extras/tests/tls
 	./tls-tests
diff --git a/extras/build-tests/windows/compilecmd/vs2019.bat b/extras/build-tests/windows/compilecmd/vs2019.bat
@@ -3,7 +3,7 @@ rem Build command for Visual Studio 2019
 nmake -f makefile.windows ^
 LIBRESSL_INC_DIR="c:\projects\unrealircd-5-libs\libressl\include" ^
 LIBRESSL_LIB_DIR="c:\projects\unrealircd-5-libs\libressl\lib" ^
-SSLLIB="crypto-45.lib ssl-47.lib" ^
+SSLLIB="crypto-46.lib ssl-48.lib" ^
 USE_REMOTEINC=1 ^
 LIBCURL_INC_DIR="c:\projects\unrealircd-5-libs\curl\include" ^
 LIBCURL_LIB_DIR="c:\projects\unrealircd-5-libs\curl\builds\libcurl-vc-x64-release-dll-ssl-dll-cares-dll-ipv6-obj-lib" ^
diff --git a/extras/c-ares.tar.gz b/extras/c-ares.tar.gz
Binary files differ.
diff --git a/extras/curlinstall b/extras/curlinstall
@@ -4,7 +4,7 @@ OUTF="curl-latest.tar.gz"
 OUTD="curl-latest"
 ARESPATH="`pwd`/extras/c-ares"
 UNREALDIR="`pwd`"
-CARESVERSION="1.15.0"
+CARESVERSION="1.16.0"
 LIBDIR="$1"
 
 if [ "x$1" = "x" ]; then
diff --git a/extras/doxygen/Doxyfile b/extras/doxygen/Doxyfile
@@ -38,7 +38,7 @@ PROJECT_NAME           = "UnrealIRCd"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 5.0.4
+PROJECT_NUMBER         = 5.0.5
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/extras/pcre2.tar.gz b/extras/pcre2.tar.gz
Binary files differ.
diff --git a/include/h.h b/include/h.h
@@ -174,7 +174,6 @@ extern void set_snomask(Client *client, char *snomask);
 extern char *get_snomask_string(Client *client);
 extern int check_tkls(Client *cptr);
 /* for services */
-extern void del_invite(Client *, Channel *);
 extern void send_user_joins(Client *, Client *);
 extern int valid_channelname(const char *);
 extern int valid_server_name(char *name);
@@ -623,6 +622,8 @@ extern char *spamfilter_inttostring_long(int v);
 extern Channel *get_channel(Client *cptr, char *chname, int flag);
 extern MODVAR char backupbuf[];
 extern void add_invite(Client *, Client *, Channel *, MessageTag *);
+extern void del_invite(Client *, Channel *);
+extern int is_invited(Client *client, Channel *channel);
 extern void channel_modes(Client *cptr, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel);
 extern MODVAR char modebuf[BUFSIZE], parabuf[BUFSIZE];
 extern int op_can_override(char *acl, Client *client,Channel *channel,void* extra);
@@ -884,6 +885,7 @@ extern char *certificate_name(SSL *ssl);
 extern void start_of_normal_client_handshake(Client *acptr);
 extern void clicap_pre_rehash(void);
 extern void clicap_post_rehash(void);
+extern void unload_all_unused_mtag_handlers(void);
 extern void send_cap_notify(int add, char *token);
 extern void sendbufto_one(Client *to, char *msg, unsigned int quick);
 extern MODVAR int current_serial;
@@ -970,3 +972,5 @@ extern void link_generator(void);
 extern void update_throttling_timer_settings(void);
 extern int hide_idle_time(Client *client, Client *target);
 extern void lost_server_link(Client *serv, FORMAT_STRING(const char *fmt), ...);
+extern char *sendtype_to_cmd(SendType sendtype);
+extern MODVAR MessageTagHandler *mtaghandlers;
diff --git a/include/modules.h b/include/modules.h
@@ -901,7 +901,7 @@ extern void SavePersistentIntX(ModuleInfo *modinfo, char *varshortname, int var)
 #define SavePersistentInt(modinfo, var) SavePersistentIntX(modinfo, #var, var)
 
 extern int LoadPersistentLongX(ModuleInfo *modinfo, char *varshortname, long *var);
-#define LoadPersistentLong(modinfo, var) LoadPersistentIntX(modinfo, #var, &var)
+#define LoadPersistentLong(modinfo, var) LoadPersistentLongX(modinfo, #var, &var)
 extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long var);
 #define SavePersistentLong(modinfo, var) SavePersistentLongX(modinfo, #var, var)
 
@@ -1009,6 +1009,7 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va
 #define HOOKTYPE_CAN_SEND_TO_USER 105
 #define HOOKTYPE_SERVER_SYNC 106
 #define HOOKTYPE_ACCOUNT_LOGIN 107
+#define HOOKTYPE_CLOSE_CONNECTION 108
 
 /* Adding a new hook here?
  * 1) Add the #define HOOKTYPE_.... with a new number
@@ -1042,12 +1043,12 @@ char *hooktype_pre_local_kick(Client *client, Client *victim, Channel *channel, 
 int hooktype_can_kick(Client *client, Client *victim, Channel *channel, char *comment, long client_flags, long victim_flags, char **error);
 int hooktype_local_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
 int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
-char *hooktype_pre_usermsg(Client *client, Client *to, char *text, int notice);
-int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, int notice);
-int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, char **text, char **errmsg, int notice);
-int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
-int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, int notice);
-int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice);
+char *hooktype_pre_usermsg(Client *client, Client *to, char *text, SendType sendtype);
+int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype);
+int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, char **text, char **errmsg, SendType sendtype);
+int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype);
+int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
 char *hooktype_pre_local_topic(Client *client, Channel *channel, char *topic);
 int hooktype_local_topic(Client *client, Channel *channel, char *topic);
 int hooktype_topic(Client *client, Channel *channel, MessageTag *mtags, char *topic);
@@ -1084,7 +1085,7 @@ int hooktype_tkl_add(Client *client, TKL *tkl);
 int hooktype_tkl_del(Client *client, TKL *tkl);
 int hooktype_log(int flags, char *timebuf, char *buf);
 int hooktype_local_spamfilter(Client *acptr, char *str, char *str_in, int type, char *target, TKL *tkl);
-int hooktype_silenced(Client *client, Client *to, int notice);
+int hooktype_silenced(Client *client, Client *to, SendType sendtype);
 int hooktype_rawpacket_in(Client *client, char *readbuf, int *length);
 int hooktype_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length);
 int hooktype_handshake(Client *client);
@@ -1121,6 +1122,7 @@ int hooktype_is_handshake_finished(Client *acptr);
 char *hooktype_pre_local_quit_chan(Client *client, Channel *channel, char *comment);
 int hooktype_ident_lookup(Client *acptr);
 int hooktype_account_login(Client *client, MessageTag *mtags);
+int hooktype_close_connection(Client *client);
 
 #ifdef GCC_TYPECHECKING
 #define ValidateHook(validatefunc, func) __builtin_types_compatible_p(__typeof__(func), __typeof__(validatefunc))
@@ -1229,7 +1231,8 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_PRE_LOCAL_QUIT_CHAN) && !ValidateHook(hooktype_pre_local_quit_chan, func)) || \
         ((hooktype == HOOKTYPE_IDENT_LOOKUP) && !ValidateHook(hooktype_ident_lookup, func)) || \
         ((hooktype == HOOKTYPE_CONFIGRUN_EX) && !ValidateHook(hooktype_configrun_ex, func)) || \
-        ((hooktype == HOOKTYPE_ACCOUNT_LOGIN) && !ValidateHook(hooktype_account_login, func)) ) \
+        ((hooktype == HOOKTYPE_ACCOUNT_LOGIN) && !ValidateHook(hooktype_account_login, func)) || \
+        ((hooktype == HOOKTYPE_CLOSE_CONNECTION) && !ValidateHook(hooktype_close_connection, func)) ) \
         _hook_error_incompatible();
 #endif /* GCC_TYPECHECKING */
 
diff --git a/include/struct.h b/include/struct.h
@@ -140,6 +140,12 @@ typedef enum OperClassEntryType { OPERCLASSENTRY_ALLOW=1, OPERCLASSENTRY_DENY=2}
 
 typedef enum OperPermission { OPER_ALLOW=1, OPER_DENY=0} OperPermission;
 
+typedef enum SendType {
+	SEND_TYPE_PRIVMSG	= 0,
+	SEND_TYPE_NOTICE	= 1,
+	SEND_TYPE_TAGMSG	= 2
+} SendType;
+
 struct OperClassValidator;
 typedef struct OperClassValidator OperClassValidator;
 typedef struct OperClassACLPath OperClassACLPath;
@@ -338,6 +344,8 @@ typedef enum ClientStatus {
 #define PROTO_MTAGS	0x010000	/* Support message tags and big buffers */
 
 /* For client capabilities: */
+#define CAP_INVERT	1L
+
 /** HasCapabilityFast() checks for a token if you know exactly which bit to check */
 #define HasCapabilityFast(cptr, val) ((cptr)->local->caps & (val))
 /** HasCapability() checks for a token by name and is slightly slower */
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -63,7 +63,7 @@
 #define UNREAL_VERSION_MAJOR 0
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 4
+#define UNREAL_VERSION_MINOR 5
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
diff --git a/src/api-clicap.c b/src/api-clicap.c
@@ -98,10 +98,11 @@ void ClearCapability(Client *client, const char *token)
 
 long clicap_allocate_cap(void)
 {
-	long v = 1;
+	long v;
 	ClientCapability *clicap;
 
-	for (v=1; v < 2147483648; v = v * 2)
+	/* The first bit (v=1) is used by the "invert" marker */
+	for (v=2; v < LONG_MAX; v = v * 2)
 	{
 		unsigned char found = 0;
 		for (clicap = clicaps; clicap; clicap = clicap->next)
diff --git a/src/api-moddata.c b/src/api-moddata.c
@@ -125,7 +125,6 @@ void moddata_free_local_client(Client *client)
 	memset(client->moddata, 0, sizeof(client->moddata));
 }
 
-// FIXME: this is never called
 void moddata_free_channel(Channel *channel)
 {
 	ModDataInfo *md;
diff --git a/src/channel.c b/src/channel.c
@@ -1031,6 +1031,20 @@ void del_invite(Client *client, Channel *channel)
 		}
 }
 
+/** Is the user 'client' invited to channel 'channel' by a chanop?
+ * @param client	The client who was invited
+ * @param channel	The channel to which the person was invited
+ */
+int is_invited(Client *client, Channel *channel)
+{
+	Link *lp;
+
+	for (lp = client->user->invited; lp; lp = lp->next)
+		if (lp->value.channel == channel)
+			return 1;
+	return 0;
+}
+
 /** Subtract one user from channel i. Free the channel if it became empty.
  * @param channel The channel
  * @returns 1 if the channel was freed, 0 if the channel still exists.
@@ -1057,6 +1071,8 @@ int sub1_from_channel(Channel *channel)
 	 * But first we will destroy all kinds of references and lists...
 	 */
 
+	moddata_free_channel(channel);
+
 	while ((lp = channel->invites))
 		del_invite(lp->value.client, channel);
 
diff --git a/src/conf.c b/src/conf.c
@@ -1746,6 +1746,8 @@ void config_setdefaultsettings(Configuration *i)
 	i->manual_ban_target = BAN_TARGET_HOST;
 
 	i->hide_idle_time = HIDE_IDLE_TIME_OPER_USERMODE;
+
+	i->who_limit = 100;
 }
 
 static void make_default_logblock(void)
@@ -2133,6 +2135,7 @@ int	init_conf(char *rootconf, int rehash)
 	postconf();
 	config_status("Configuration loaded.");
 	clicap_post_rehash();
+	unload_all_unused_mtag_handlers();
 	return 0;
 }
 
@@ -8335,16 +8338,20 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					}
 					continue; /* required here, due to checknull directly below */
 				}
-				CheckNull(cepp);
 				if (!strcmp(cepp->ce_varname, "unknown-flood-bantime"))
 				{
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_unknown_flood_bantime, "anti-flood::unknown-flood-bantime");
 				}
-				else if (!strcmp(cepp->ce_varname, "unknown-flood-amount")) {
+				else if (!strcmp(cepp->ce_varname, "unknown-flood-amount"))
+				{
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_unknown_flood_amount, "anti-flood::unknown-flood-amount");
 				}
-				else if (!strcmp(cepp->ce_varname, "away-count")) {
+				else if (!strcmp(cepp->ce_varname, "away-count"))
+				{
 					int temp = atol(cepp->ce_vardata);
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_away_count, "anti-flood::away-count");
 					if (temp < 1 || temp > 255)
 					{
@@ -8353,7 +8360,9 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 						errors++;
 					}
 				}
-				else if (!strcmp(cepp->ce_varname, "away-period")) {
+				else if (!strcmp(cepp->ce_varname, "away-period"))
+				{
+					CheckNull(cepp);
 					int temp = config_checkval(cepp->ce_vardata, CFG_TIME);
 					CheckDuplicate(cepp, anti_flood_away_period, "anti-flood::away-period");
 					if (temp < 10)
@@ -8366,6 +8375,7 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "away-flood"))
 				{
 					int cnt, period;
+					CheckNull(cepp);
 					if (settings.has_anti_flood_away_period)
 					{
 						config_warn("%s:%d: set::anti-flood::away-flood overrides set::anti-flood::away-period",
@@ -8392,6 +8402,7 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "nick-flood"))
 				{
 					int cnt, period;
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_nick_flood, "anti-flood::nick-flood");
 					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 255) || (period < 5))
@@ -8405,6 +8416,7 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "invite-flood"))
 				{
 					int cnt, period;
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_invite_flood, "anti-flood::invite-flood");
 					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 255) || (period < 5))
@@ -8418,6 +8430,7 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "knock-flood"))
 				{
 					int cnt, period;
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_knock_flood, "anti-flood::knock-flood");
 					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 255) || (period < 5))
@@ -8431,6 +8444,7 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				else if (!strcmp(cepp->ce_varname, "connect-flood"))
 				{
 					int cnt, period;
+					CheckNull(cepp);
 					CheckDuplicate(cepp, anti_flood_connect_flood, "anti-flood::connect-flood");
 					if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) ||
 					    (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
diff --git a/src/crule.c b/src/crule.c
@@ -306,7 +306,7 @@ int  crule_gettoken(int *next_tokp, char **ruleptr)
 
 void crule_getword(char *word, int *wordlenp, int maxlen, char **ruleptr)
 {
-	char *word_ptr;
+	char *word_ptr, c;
 
 	word_ptr = word;
 	/* Both - and : can appear in hostnames so they must not be 
@@ -315,8 +315,16 @@ void crule_getword(char *word, int *wordlenp, int maxlen, char **ruleptr)
 	while ((isalnum(**ruleptr)) || (**ruleptr == '*') ||
 	    (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-') ||
 	    (**ruleptr == ':'))
-		*word_ptr++ = *(*ruleptr)++;
-	*word_ptr = '\0';
+	{
+		c = *(*ruleptr)++;
+		if (maxlen > 1) /* >1 instead of >0 so we (possibly) still have room for NUL */
+		{
+			*word_ptr++ = c;
+			maxlen--;
+		}
+	}
+	if (maxlen)
+		*word_ptr = '\0';
 	*wordlenp = word_ptr - word;
 }
 
diff --git a/src/match.c b/src/match.c
@@ -405,7 +405,7 @@ Match *unreal_create_match(MatchType type, char *str, char **error)
 		int options = 0;
 		char buf2[512];
 		
-		options = PCRE2_CASELESS|PCRE2_NEVER_UTF|PCRE2_NEVER_UCP;
+		options = PCRE2_CASELESS|PCRE2_MATCH_INVALID_UTF;
 		
 		m->ext.pcre2_expr = pcre2_compile(str, PCRE2_ZERO_TERMINATED, options, &errorcode, &erroroffset, NULL);
 		if (m->ext.pcre2_expr == NULL)
diff --git a/src/misc.c b/src/misc.c
@@ -1912,3 +1912,15 @@ void freemultiline(MultiLine *l)
 		safe_free(l);
 	}
 }
+
+/** Convert a sendtype to a command string */
+char *sendtype_to_cmd(SendType sendtype)
+{
+	if (sendtype == SEND_TYPE_PRIVMSG)
+		return "PRIVMSG";
+	if (sendtype == SEND_TYPE_NOTICE)
+		return "NOTICE";
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return "TAGMSG";
+	return NULL;
+}
diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in
@@ -71,7 +71,9 @@ R_MODULES= \
 	account-tag.so labeled-response.so link-security.so \
 	message-ids.so plaintext-policy.so server-time.so sts.so \
 	echo-message.so userip-tag.so userhost-tag.so \
-	ident_lookup.so history.so
+	typing-indicator.so \
+	ident_lookup.so history.so \
+	targetfloodprot.so clienttagdeny.so
 
 MODULES=cloak.so $(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
@@ -610,6 +612,10 @@ userhost-tag.so: userhost-tag.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
 		-o userhost-tag.so userhost-tag.c
 
+typing-indicator.so: typing-indicator.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o typing-indicator.so typing-indicator.c
+
 require-module.so: require-module.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
 		-o require-module.so require-module.c
@@ -626,6 +632,14 @@ history.so: history.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
 		-o history.so history.c
 
+targetfloodprot.so: targetfloodprot.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o targetfloodprot.so targetfloodprot.c
+
+clienttagdeny.so: clienttagdeny.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o clienttagdeny.so clienttagdeny.c
+
 #############################################################################
 # capabilities
 #############################################################################
diff --git a/src/modules/antimixedutf8.c b/src/modules/antimixedutf8.c
@@ -66,6 +66,10 @@ int antimixedutf8_config_run(ConfigFile *, ConfigEntry *, int);
 #define SCRIPT_UNDEFINED	0
 #define SCRIPT_LATIN		1
 #define SCRIPT_CYRILLIC		2
+#define SCRIPT_CJK		3
+#define SCRIPT_HANGUL		4
+#define SCRIPT_CANADIAN		5
+#define SCRIPT_TELUGU		6
 
 /**** the detection algorithm follows first, the module/config code is at the end ****/
 
@@ -95,6 +99,24 @@ int detect_script(const char *t)
 	else if ((t[0] == 0xd3) && (t[1] >= 0x80) && (t[1] <= 0xbf))
 		return SCRIPT_CYRILLIC;
 
+	if((t[0] == 0xe4) && (t[1] >= 0xb8) && (t[1] <= 0xbf))
+		return SCRIPT_CJK;
+	else if ((t[0] >= 0xe5) && (t[0] <= 0xe9) && (t[1] >= 0x80) && (t[1] <= 0xbf))
+		return SCRIPT_CJK;
+
+	if((t[0] == 0xea) && (t[1] >= 0xb0) && (t[1] <= 0xbf))
+		return SCRIPT_HANGUL;
+	else if ((t[0] >= 0xeb) && (t[0] <= 0xec) && (t[1] >= 0x80) && (t[1] <= 0xbf))
+		return SCRIPT_HANGUL;
+	else if ((t[0] == 0xed) && (t[1] >= 0x80) && (t[1] <= 0x9f))
+		return SCRIPT_HANGUL;
+
+	if((t[0] == 0xe1) && (t[1] >= 0x90) && (t[1] <= 0x99))
+		return SCRIPT_CANADIAN;
+
+	if((t[0] == 0xe0) && (t[1] >= 0xb0) && (t[1] <= 0xb1))
+		return SCRIPT_TELUGU;
+
 	if ((t[0] >= 'a') && (t[0] <= 'z'))
 		return SCRIPT_LATIN;
 	if ((t[0] >= 'A') && (t[0] <= 'Z'))
diff --git a/src/modules/chanmodes/censor.c b/src/modules/chanmodes/censor.c
@@ -20,7 +20,7 @@ Cmode_t EXTMODE_CENSOR = 0L;
 
 #define IsCensored(x) ((x)->mode.extmode & EXTMODE_CENSOR)
 
-int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 char *censor_pre_local_part(Client *client, Channel *channel, char *text);
 char *censor_pre_local_quit(Client *client, char *text);
 
@@ -253,7 +253,7 @@ char *stripbadwords_channel(char *str, int *blocked)
 	return stripbadwords(str, conf_badword_channel, blocked);
 }
 
-int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int censor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	int blocked;
 	Hook *h;
diff --git a/src/modules/chanmodes/delayjoin.c b/src/modules/chanmodes/delayjoin.c
@@ -30,7 +30,7 @@ int moded_part(Client *client, Channel *channel, MessageTag *mtags, char *commen
 int deny_all(Client *client, Channel *channel, char mode, char *para, int checkt, int what);
 int moded_chanmode(Client *client, Channel *channel,
                    MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
-int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, int notice);
+int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype);
 char *moded_serialize(ModData *m);
 void moded_unserialize(char *str, ModData *m);
 
@@ -367,7 +367,7 @@ int moded_chanmode(Client *client, Channel *channel, MessageTag *recv_mtags, cha
 	return 0;
 }
 
-int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, int notice)
+int moded_prechanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype)
 {
 	if ((channel_is_delayed(channel) || channel_is_post_delayed(channel)) && (moded_user_invisible(client, channel)))
 		clear_user_invisible_announce(channel, client, mtags);
diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c
@@ -117,8 +117,8 @@ int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int floodprot_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
 EVENT(modef_event);
 int cmodef_channel_destroy(Channel *channel, int *should_destroy);
-int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
-int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice);
+int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
 int floodprot_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment);
 int floodprot_nickchange(Client *client, char *oldnick);
 int floodprot_chanmode_del(Channel *channel, int m);
@@ -623,10 +623,11 @@ void *cmodef_put_param(void *fld_in, char *param)
 	/* if new 'per xxx seconds' is smaller than current 'per' then reset timers/counters (t, c) */
 	if (v < fld->per)
 	{
-		for (v=0; v < NUMFLD; v++)
+		int i;
+		for (i=0; i < NUMFLD; i++)
 		{
-			fld->timer[v] = 0;
-			fld->counter[v] = 0;
+			fld->timer[i] = 0;
+			fld->counter[i] = 0;
 		}
 	}
 	fld->per = v;
@@ -939,7 +940,7 @@ char *channel_modef_string(ChannelFloodProtection *x, char *retbuf)
 	return retbuf;
 }
 
-int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	Membership *mb;
 	ChannelFloodProtection *chp;
@@ -952,6 +953,8 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	if (!MyUser(client))
 		return HOOK_CONTINUE;
 
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return 0; // TODO: some TAGMSG specific limit? (1 of 2)
 
 	if (ValidatePermissionsForPath("channel:override:flood",client,NULL,channel,NULL) || !IsFloodLimit(channel) || is_skochanop(client, channel))
 		return HOOK_CONTINUE;
@@ -1061,11 +1064,14 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	return HOOK_CONTINUE;
 }
 
-int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice)
+int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype)
 {
 	if (!IsFloodLimit(channel) || is_skochanop(client, channel) || IsULine(client))
 		return 0;
 
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return 0; // TODO: some TAGMSG specific limit? (2 of 2)
+
 	/* HINT: don't be so stupid to reorder the items in the if's below.. you'll break things -- Syzop. */
 
 	if (do_floodprot(channel, FLD_MSG) && MyUser(client))
diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c
@@ -47,7 +47,7 @@ void history_chanmode_free_param(void *r);
 void *history_chanmode_dup_struct(void *r_in);
 int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int history_channel_destroy(Channel *channel, int *should_destroy);
-int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice);
+int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
 int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
 
 MOD_TEST()
@@ -490,7 +490,7 @@ int history_channel_destroy(Channel *channel, int *should_destroy)
 	return 0;
 }
 
-int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice)
+int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype)
 {
 	char buf[512];
 	char source[64];
@@ -503,6 +503,10 @@ int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix,
 	if ((*text == '\001') && strncmp(text+1, "ACTION", 6))
 		return 0;
 
+	/* Filter out TAGMSG */
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return 0;
+
 	/* Lazy: if any prefix is addressed (eg: @#channel) then don't record it.
 	 * This so we don't have to check privileges during history playback etc.
 	 */
@@ -516,7 +520,7 @@ int history_chanmsg(Client *client, Channel *channel, int sendflags, int prefix,
 
 	snprintf(buf, sizeof(buf), ":%s %s %s :%s",
 		source,
-		notice ? "NOTICE" : "PRIVMSG",
+		sendtype_to_cmd(sendtype),
 		channel->chname,
 		text);
 
diff --git a/src/modules/chanmodes/link.c b/src/modules/chanmodes/link.c
@@ -357,39 +357,43 @@ int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
 	if (IsULine(client) || find_membership_link(client->user->channel, channel))
 		return HOOK_CONTINUE;
 
-	// Extbans take precedence over +L #channel and other restrictions
-	for(ban = channel->banlist; ban; ban = ban->next)
+	// Extbans take precedence over +L #channel and other restrictions,
+	// only /INVITE from chanop bypasses:
+	if (!is_invited(client, channel))
 	{
-		if (!strncmp(ban->banstr, "~f:", 3))
+		for(ban = channel->banlist; ban; ban = ban->next)
 		{
-			strlcpy(bantmp, ban->banstr + 3, sizeof(bantmp));
-		} else
-		if (!strncmp(ban->banstr, "~t:", 3))
-		{
-			/* A timed ban, but is it for us? Need to parse a little:
-			 * ~t:dddd:~f:...
-			 */
-			char *p = strchr(ban->banstr + 3, ':');
-			if (p && !strncmp(p, ":~f:", 4))
+			if (!strncmp(ban->banstr, "~f:", 3))
+			{
+				strlcpy(bantmp, ban->banstr + 3, sizeof(bantmp));
+			} else
+			if (!strncmp(ban->banstr, "~t:", 3))
+			{
+				/* A timed ban, but is it for us? Need to parse a little:
+				 * ~t:dddd:~f:...
+				 */
+				char *p = strchr(ban->banstr + 3, ':');
+				if (p && !strncmp(p, ":~f:", 4))
+				{
+					strlcpy(bantmp, p + 4, sizeof(bantmp));
+				} else {
+					/* Not for us - some other ~t ban */
+					continue;
+				}
+			} else
 			{
-				strlcpy(bantmp, p + 4, sizeof(bantmp));
-			} else {
-				/* Not for us - some other ~t ban */
+				/* Not for us */
 				continue;
 			}
-		} else
-		{
-			/* Not for us */
-			continue;
+			banchan = bantmp;
+			banmask = strchr(bantmp, ':');
+			if (!banmask || !banmask[1])
+				continue;
+			*banmask++ = '\0';
+
+			if (ban_check_mask(client, channel, banmask, BANCHK_JOIN, NULL, NULL, 0))
+				return link_doforward(client, channel, banchan, LINKTYPE_BAN);
 		}
-		banchan = bantmp;
-		banmask = strchr(bantmp, ':');
-		if (!banmask || !banmask[1])
-			continue;
-		*banmask++ = '\0';
-
-		if (ban_check_mask(client, channel, banmask, BANCHK_JOIN, NULL, NULL, 0))
-			return link_doforward(client, channel, banchan, LINKTYPE_BAN);
 	}
 
 	// Either +L is not set, or it is set but the parameter isn't stored somehow
@@ -397,7 +401,7 @@ int link_pre_localjoin_cb(Client *client, Channel *channel, char *parv[])
 		return HOOK_CONTINUE;
 
 	// can_join() actually returns 0 if we *can* join a channel, so we don't need to bother checking any further conditions
-	if (!(canjoin = can_join(client, channel, NULL, parv)))
+	if (!(canjoin = can_join(client, channel, parv[2], parv)))
 		return HOOK_CONTINUE;
 
 	// Oper only channel
diff --git a/src/modules/chanmodes/nocolor.c b/src/modules/chanmodes/nocolor.c
@@ -34,7 +34,7 @@ Cmode_t EXTCMODE_NOCOLOR;
 
 #define IsNoColor(channel)    (channel->mode.extmode & EXTCMODE_NOCOLOR)
 
-int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 char *nocolor_prelocalpart(Client *client, Channel *channel, char *comment);
 char *nocolor_prelocalquit(Client *client, char *comment);
 
@@ -85,7 +85,7 @@ static int IsUsingColor(char *s)
         return 0;
 }
 
-int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int nocolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
diff --git a/src/modules/chanmodes/noctcp.c b/src/modules/chanmodes/noctcp.c
@@ -34,7 +34,7 @@ Cmode_t EXTCMODE_NOCTCP;
 
 #define IsNoCTCP(channel)    (channel->mode.extmode & EXTCMODE_NOCTCP)
 
-int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -78,7 +78,7 @@ static int IsACTCP(char *s)
 	return 0;
 }
 
-int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int noctcp_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	if (IsNoCTCP(channel) && IsACTCP(*msg))
 	{
diff --git a/src/modules/chanmodes/nonotice.c b/src/modules/chanmodes/nonotice.c
@@ -32,7 +32,7 @@ Cmode_t EXTCMODE_NONOTICE;
 
 #define IsNoNotice(channel)    (channel->mode.extmode & EXTCMODE_NONOTICE)
 
-int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -65,13 +65,14 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int nonotice_check_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
 
-	if (notice && IsNoNotice(channel) &&
-	   (!lp || !(lp->flags & (CHFL_CHANOP | CHFL_CHANOWNER | CHFL_CHANADMIN))))
+	if ((sendtype == SEND_TYPE_NOTICE) &&
+	    IsNoNotice(channel) &&
+	    (!lp || !(lp->flags & (CHFL_CHANOP | CHFL_CHANOWNER | CHFL_CHANADMIN))))
 	{
 		for (h = Hooks[HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION]; h; h = h->next)
 		{
diff --git a/src/modules/chanmodes/regonlyspeak.c b/src/modules/chanmodes/regonlyspeak.c
@@ -34,7 +34,7 @@ static char errMsg[2048];
 
 #define IsRegOnlySpeak(channel)    (channel->mode.extmode & EXTCMODE_REGONLYSPEAK)
 
-int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 char *regonlyspeak_part_message (Client *client, Channel *channel, char *comment);
 
 MOD_TEST()
@@ -81,7 +81,7 @@ char *regonlyspeak_part_message (Client *client, Channel *channel, char *comment
 	return comment;
 }
 
-int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int regonlyspeak_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
diff --git a/src/modules/chanmodes/secureonly.c b/src/modules/chanmodes/secureonly.c
@@ -140,11 +140,10 @@ int secureonly_check_join(Client *client, Channel *channel, char *key, char *par
 			/* if the channel is +z we still allow an ircop to bypass it
 			 * if they are invited.
 			 */
-			for (lp = client->user->invited; lp; lp = lp->next)
-				if (lp->value.channel == channel)
-					return HOOK_CONTINUE;
+			if (is_invited(client, channel))
+				return HOOK_CONTINUE;
 		}
-		return (ERR_SECUREONLYCHAN);
+		return ERR_SECUREONLYCHAN;
 	}
 	return 0;
 }
diff --git a/src/modules/chanmodes/stripcolor.c b/src/modules/chanmodes/stripcolor.c
@@ -34,7 +34,7 @@ Cmode_t EXTCMODE_STRIPCOLOR;
 
 #define IsStripColor(channel)    (channel->mode.extmode & EXTCMODE_STRIPCOLOR)
 
-int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 char *stripcolor_prelocalpart(Client *client, Channel *channel, char *comment);
 char *stripcolor_prelocalquit(Client *client, char *comment);
 
@@ -73,7 +73,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int stripcolor_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	Hook *h;
 	int i;
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -67,7 +67,7 @@ struct cfgstruct {
 };
 static struct cfgstruct cfg;
 
-static int channeldb_loaded = 0;
+static long channeldb_next_event = 0;
 
 MOD_TEST()
 {
@@ -80,7 +80,7 @@ MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
-	LoadPersistentInt(modinfo, channeldb_loaded);
+	LoadPersistentLong(modinfo, channeldb_next_event);
 
 	setcfg();
 
@@ -90,7 +90,7 @@ MOD_INIT()
 
 MOD_LOAD()
 {
-	if (!channeldb_loaded)
+	if (!channeldb_next_event)
 	{
 		/* If this is the first time that our module is loaded, then read the database. */
 		if (!read_channeldb())
@@ -102,9 +102,9 @@ MOD_LOAD()
 			else
 				config_warn("[channeldb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
 		}
-		channeldb_loaded = 1;
+		channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY;
 	}
-	EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, CHANNELDB_SAVE_EVERY*1000, 0);
+	EventAdd(modinfo->handle, "channeldb_write_channeldb", write_channeldb_evt, NULL, 1000, 0);
 	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
 	{
 		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
@@ -115,9 +115,8 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
-	write_channeldb();
 	freecfg();
-	SavePersistentInt(modinfo, channeldb_loaded);
+	SavePersistentLong(modinfo, channeldb_next_event);
 	return MOD_SUCCESS;
 }
 
@@ -191,6 +190,9 @@ int channeldb_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
 
 EVENT(write_channeldb_evt)
 {
+	if (channeldb_next_event > TStime())
+		return;
+	channeldb_next_event = TStime() + CHANNELDB_SAVE_EVERY;
 	write_channeldb();
 }
 
diff --git a/src/modules/clienttagdeny.c b/src/modules/clienttagdeny.c
@@ -0,0 +1,77 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/echo-message.c
+ *   (C) 2020 k4be for The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   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 1, 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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+char *ct_isupport_param(void);
+int tags_rehash_complete(void);
+
+Module *module;
+
+ModuleHeader MOD_HEADER = {
+	"clienttagdeny",
+	"5.0",
+	"Informs clients about supported client tags",
+	"k4be",
+	"unrealircd-5",
+};
+
+MOD_INIT(){
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD(){
+	module = modinfo->handle;
+	ISupportAdd(module, "CLIENTTAGDENY", ct_isupport_param());
+	HookAdd(module, HOOKTYPE_REHASH_COMPLETE, 0, tags_rehash_complete);
+
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD(){
+	return MOD_SUCCESS;
+}
+
+#define BUFLEN 500
+
+char *ct_isupport_param(void){
+	static char buf[BUFLEN];
+	MessageTagHandler *m;
+	
+	strlcpy(buf, "*", sizeof(buf));
+
+	for (m = mtaghandlers; m; m = m->next) {
+		if(!m->unloaded && m->name[0] == '+'){
+			strlcat(buf, ",-", sizeof(buf));
+			strlcat(buf, m->name+1, sizeof(buf));
+		}
+	}
+	return buf;
+}
+
+int tags_rehash_complete(void){
+	ISupportSet(module, "CLIENTTAGDENY", ct_isupport_param());
+	return HOOK_CONTINUE;
+}
+
diff --git a/src/modules/dccdeny.c b/src/modules/dccdeny.c
@@ -44,8 +44,8 @@ int dccdeny_stats(Client *client, char *para);
 CMD_FUNC(cmd_dccdeny);
 CMD_FUNC(cmd_undccdeny);
 CMD_FUNC(cmd_svsfline);
-int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
-int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 int dccdeny_server_sync(Client *client);
 static ConfigItem_deny_dcc *dcc_isforbidden(Client *client, char *filename);
 static ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, char *filename);
@@ -489,7 +489,7 @@ int dccdeny_server_sync(Client *client)
 }
 
 /** Check if a DCC should be blocked (user-to-user) */
-int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	if (**text == '\001')
 	{
@@ -507,7 +507,7 @@ int dccdeny_can_send_to_user(Client *client, Client *target, char **text, char *
 }
 
 /** Check if a DCC should be blocked (user-to-channel, unusual) */
-int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	static char errbuf[512];
 
@@ -517,7 +517,7 @@ int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp
 		char *filename = get_dcc_filename(*msg);
 		if (filename && !can_dcc(client, channel->chname, NULL, filename, &err))
 		{
-			if (!IsDead(client) && !notice)
+			if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE))
 			{
 				strlcpy(errbuf, err, sizeof(errbuf));
 				*errmsg = errbuf;
diff --git a/src/modules/echo-message.c b/src/modules/echo-message.c
@@ -35,8 +35,8 @@ ModuleHeader MOD_HEADER
 long CAP_ECHO_MESSAGE = 0L;
 
 /* Forward declarations */
-int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice);
-int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, int notice);
+int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
+int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype);
 
 MOD_INIT()
 {
@@ -64,28 +64,44 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice)
+int em_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype)
 {
 	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
 	{
-		sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
-			client->name,
-			notice ? "NOTICE" : "PRIVMSG",
-			target,
-			text);
+		if (sendtype != SEND_TYPE_TAGMSG)
+		{
+			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				target,
+				text);
+		} else {
+			sendto_prefix_one(client, client, mtags, ":%s %s %s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				target);
+		}
 	}
 	return 0;
 }
 
-int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, int notice)
+int em_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype)
 {
 	if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
 	{
-		sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
-			client->name,
-			notice ? "NOTICE" : "PRIVMSG",
-			to->name,
-			text);
+		if (sendtype != SEND_TYPE_TAGMSG)
+		{
+			sendto_prefix_one(client, client, mtags, ":%s %s %s :%s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				to->name,
+				text);
+		} else {
+			sendto_prefix_one(client, client, mtags, ":%s %s %s",
+				client->name,
+				sendtype_to_cmd(sendtype),
+				to->name);
+		}
 	}
 	return 0;
 }
diff --git a/src/modules/join.c b/src/modules/join.c
@@ -113,9 +113,8 @@ int _can_join(Client *client, Channel *channel, char *key, char *parv[])
 	if (banned && j == HOOK_DENY)
 		return (ERR_BANNEDFROMCHAN);
 
-	for (lp = client->user->invited; lp; lp = lp->next)
-		if (lp->value.channel == channel)
-			return 0;
+	if (is_invited(client, channel))
+		return 0; /* allowed */
 
         if (channel->users >= channel->mode.limit)
         {
@@ -515,20 +514,12 @@ void _do_join(Client *client, int parc, char *parv[])
 			    !strcasecmp(name, SPAMFILTER_VIRUSCHAN) &&
 			    !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride)
 			{
-				int invited = 0;
-				Link *lp;
 				Channel *channel = find_channel(name, NULL);
 				
-				if (channel)
+				if (!channel || !is_invited(client, channel))
 				{
-					for (lp = client->user->invited; lp; lp = lp->next)
-						if (lp->value.channel == channel)
-							invited = 1;
-				}
-				if (!invited)
-				{
-					sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel which is "
-					                 "reserved for infected users only", name);
+					sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel "
+					                   "which is reserved for infected users only", name);
 					continue;
 				}
 			}
@@ -549,7 +540,17 @@ void _do_join(Client *client, int parc, char *parv[])
 			Hook *h;
 			for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) 
 			{
-				i = (*(h->func.intfunc))(client,channel,parv);
+				/* Note: this is just a hack not to break the ABI but still be
+				 * able to fix https://bugs.unrealircd.org/view.php?id=5644
+				 * In the future we should just drop the parv/parx argument
+				 * and use key as an argument instead.
+				 */
+				char *parx[4];
+				parx[0] = NULL;
+				parx[1] = name;
+				parx[2] = key;
+				parx[3] = NULL;
+				i = (*(h->func.intfunc))(client,channel,parx);
 				if (i == HOOK_DENY || i == HOOK_ALLOW)
 					break;
 			}
diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c
@@ -45,6 +45,7 @@ struct LabeledResponseContext {
 /* Forward declarations */
 int lr_pre_command(Client *from, MessageTag *mtags, char *buf);
 int lr_post_command(Client *from, MessageTag *mtags, char *buf);
+int lr_close_connection(Client *client);
 int lr_packet(Client *from, Client *to, Client *intended_to, char **msg, int *len);
 void *_labeled_response_save_context(void);
 void _labeled_response_set_context(void *ctx);
@@ -94,6 +95,7 @@ MOD_INIT()
 
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_COMMAND, 2000000000, lr_pre_command);
 	HookAdd(modinfo->handle, HOOKTYPE_POST_COMMAND, -2000000000, lr_post_command);
+	HookAdd(modinfo->handle, HOOKTYPE_CLOSE_CONNECTION, 2000000000, lr_close_connection);
 	HookAdd(modinfo->handle, HOOKTYPE_PACKET, 0, lr_packet);
 
 	return MOD_SUCCESS;
@@ -179,11 +181,12 @@ int lr_post_command(Client *from, MessageTag *mtags, char *buf)
 
 		if (currentcmd.responses == 0)
 		{
-			/* Note: we blindly send recv_mtags back here,
-			 * which is OK now, but may not be OK later.
-			 */
+			MessageTag *m = safe_alloc(sizeof(MessageTag));
+			safe_strdup(m->name, "label");
+			safe_strdup(m->value, currentcmd.label);
 			memset(&currentcmd, 0, sizeof(currentcmd));
-			sendto_one(from, mtags, ":%s ACK", me.name);
+			sendto_one(from, m, ":%s ACK", me.name);
+			free_message_tags(m);
 			goto done;
 		} else
 		if (currentcmd.responses == 1)
@@ -219,6 +222,13 @@ done:
 	return 0;
 }
 
+int lr_close_connection(Client *client)
+{
+	/* Flush all data before closing connection */
+	lr_post_command(client, NULL, NULL);
+	return 0;
+}
+
 /** Helper function for lr_packet() to skip the message tags prefix,
  * and possibly @batch as well.
  */
diff --git a/src/modules/message-tags.c b/src/modules/message-tags.c
@@ -34,7 +34,6 @@ ModuleHeader MOD_HEADER
 long CAP_MESSAGE_TAGS = 0L;
 char *_mtags_to_string(MessageTag *m, Client *client);
 void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list);
-CMD_FUNC(cmd_tagmsg);
 
 MOD_TEST()
 {
@@ -55,7 +54,6 @@ MOD_INIT()
 	memset(&cap, 0, sizeof(cap));
 	cap.name = "message-tags";
 	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_MESSAGE_TAGS);
-	CommandAdd(modinfo->handle, "TAGMSG", cmd_tagmsg, 1, CMD_USER);
 	return MOD_SUCCESS;
 }
 
@@ -296,11 +294,3 @@ char *_mtags_to_string(MessageTag *m, Client *client)
 
 	return buf;
 }
-
-/* Dummy handler for TAGMSG.
- * We do not permit user tags, so implementing a real TAGMSG makes no sense.
- * By having a dummy command we avoid clients from getting "Unknown command".
- */
-CMD_FUNC(cmd_tagmsg)
-{
-}
diff --git a/src/modules/message.c b/src/modules/message.c
@@ -20,20 +20,19 @@
 
 #include "unrealircd.h"
 
+/* Forward declarations */
 char *_StripColors(unsigned char *text);
 char *_StripControlCodes(unsigned char *text);
-
 int ban_version(Client *client, char *text);
-
 CMD_FUNC(cmd_private);
 CMD_FUNC(cmd_notice);
-void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], int notice);
-int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, int notice);
-int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, int notice);
+CMD_FUNC(cmd_tagmsg);
+void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], SendType sendtype);
+int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, SendType sendtype);
+int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, SendType sendtype);
 
-/* Place includes here */
-#define MSG_PRIVATE     "PRIVMSG"       /* PRIV */
-#define MSG_NOTICE      "NOTICE"        /* NOTI */
+/* Variables */
+long CAP_MESSAGE_TAGS = 0; /**< Looked up at MOD_LOAD, may stay 0 if message-tags support is absent */
 
 ModuleHeader MOD_HEADER
   = {
@@ -56,8 +55,9 @@ MOD_TEST()
 /* This is called on module init, before Server Ready */
 MOD_INIT()
 {
-	CommandAdd(modinfo->handle, MSG_PRIVATE, cmd_private, 2, CMD_USER|CMD_SERVER|CMD_RESETIDLE|CMD_VIRUS);
-	CommandAdd(modinfo->handle, MSG_NOTICE, cmd_notice, 2, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, "PRIVMSG", cmd_private, 2, CMD_USER|CMD_SERVER|CMD_RESETIDLE|CMD_VIRUS);
+	CommandAdd(modinfo->handle, "NOTICE", cmd_notice, 2, CMD_USER|CMD_SERVER);
+	CommandAdd(modinfo->handle, "TAGMSG", cmd_tagmsg, 1, CMD_USER|CMD_SERVER);
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
 }
@@ -65,6 +65,8 @@ MOD_INIT()
 /* Is first run when server is 100% ready */
 MOD_LOAD()
 {
+	CAP_MESSAGE_TAGS = ClientCapabilityBit("message-tags");
+
 	return MOD_SUCCESS;
 }
 
@@ -79,11 +81,11 @@ MOD_UNLOAD()
 /** Check if PRIVMSG's are permitted from a person to another person.
  * client:	source client
  * target:	target client
- * notice:	1 if notice, 0 if privmsg
+ * sendtype:	One of SEND_TYPE_*
  * text:	Pointer to a pointer to a text [in, out]
  * cmd:		Pointer to a pointer which contains the command to use [in, out]
  */
-int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, int notice)
+int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, SendType sendtype)
 {
 	int ret;
 	Hook *h;
@@ -109,19 +111,19 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 
 	if (is_silenced(client, target))
 	{
-		RunHook3(HOOKTYPE_SILENCED, client, target, notice);
+		RunHook3(HOOKTYPE_SILENCED, client, target, sendtype);
 		/* Silently discarded, no error message */
 		return 0;
 	}
 
 	// Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric?
-	if (MyUser(client) && match_spamfilter(client, *msgtext, (notice ? SPAMF_USERNOTICE : SPAMF_USERMSG), target->name, 0, NULL))
+	if (MyUser(client) && match_spamfilter(client, *msgtext, (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG), target->name, 0, NULL))
 		return 0;
 
 	n = HOOK_CONTINUE;
 	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_USER]; h; h = h->next)
 	{
-		n = (*(h->func.intfunc))(client, target, msgtext, errmsg, notice);
+		n = (*(h->func.intfunc))(client, target, msgtext, errmsg, sendtype);
 		if (n == HOOK_DENY)
 		{
 			if (!*errmsg)
@@ -132,13 +134,14 @@ int can_send_to_user(Client *client, Client *target, char **msgtext, char **errm
 			return 0;
 		}
 		if (!*msgtext || !**msgtext)
-			return 0;
+		{
+			if (sendtype != SEND_TYPE_TAGMSG)
+				return 0;
+			else
+				*msgtext = "";
+		}
 	}
 
-	/* This may happen, if nothing is left to send anymore (don't send empty messages) */
-	if (!*msgtext || !**msgtext)
-		return 0;
-
 	return 1;
 }
 
@@ -249,18 +252,19 @@ int can_send_to_prefix(Client *client, Channel *channel, int prefix)
 	return 1;
 }
 
-/*
-** cmd_message (used in cmd_private() and cmd_notice())
-** the general function to deliver MSG's between users/channels
-**
-**	parv[1] = receiver list
-**	parv[2] = message text
-**
-** massive cleanup
-** rev argv 6/91
-**
-*/
-void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], int notice)
+int has_client_mtags(MessageTag *mtags)
+{
+	MessageTag *m;
+
+	for (m = mtags; m; m = m->next)
+		if (*m->name == '+')
+			return 1;
+	return 0;
+}
+
+/* General message handler to users and channels. Used by PRIVMSG, NOTICE, etc.
+ */
+void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], SendType sendtype)
 {
 	Client *target;
 	Channel *channel;
@@ -269,7 +273,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 	char pfixchan[CHANNELLEN + 4];
 	int ret;
 	int ntargets = 0;
-	char *cmd = notice ? "NOTICE" : "PRIVMSG";
+	char *cmd = sendtype_to_cmd(sendtype);
 	int maxtargets = max_targets_for_command(cmd);
 	Hook *h;
 	MessageTag *mtags;
@@ -287,7 +291,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 		return;
 	}
 
-	if (parc < 3 || *parv[2] == '\0')
+	if ((sendtype != SEND_TYPE_TAGMSG) && (parc < 3 || *parv[2] == '\0'))
 	{
 		sendnumeric(client, ERR_NOTEXTTOSEND);
 		return;
@@ -303,6 +307,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 			sendnumeric(client, ERR_TOOMANYTARGETS, targetstr, maxtargets, cmd);
 			break;
 		}
+
 		/* The nicks "ircd" and "irc" are special (and reserved) */
 		if (!strcasecmp(targetstr, "ircd") && MyUser(client))
 			return;
@@ -352,7 +357,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 			errmsg = NULL;
 			if (MyUser(client) && !IsULine(client))
 			{
-				if (!can_send_to_channel(client, channel, &text, &errmsg, notice))
+				if (!can_send_to_channel(client, channel, &text, &errmsg, sendtype))
 				{
 					/* Send the error message, but only if:
 					 * 1) The user has not been killed
@@ -360,7 +365,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 					 */
 					if (IsDead(client))
 						return;
-					if (!IsDead(client) && !notice && errmsg)
+					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && errmsg)
 						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->chname, errmsg, p2);
 					continue; /* skip delivery to this target */
 				}
@@ -374,12 +379,12 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 			if ((*parv[2] == '\001') && strncmp(&parv[2][1], "ACTION ", 7))
 				sendflags |= SKIP_CTCP;
 
-			if (MyUser(client) && match_spamfilter(client, text, notice ? SPAMF_CHANNOTICE : SPAMF_CHANMSG, channel->chname, 0, NULL))
+			if (MyUser(client) && match_spamfilter(client, text, (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG), channel->chname, 0, NULL))
 				return;
 
 			new_message(client, recv_mtags, &mtags);
 
-			RunHook5(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, notice);
+			RunHook5(HOOKTYPE_PRE_CHANMSG, client, channel, mtags, text, sendtype);
 
 			if (!text)
 			{
@@ -387,12 +392,31 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 				continue;
 			}
 
-			sendto_channel(channel, client, client->direction,
-				       prefix, 0, sendflags, mtags,
-				       notice ? ":%s NOTICE %s :%s" : ":%s PRIVMSG %s :%s",
-				       client->name, targetstr, text);
+			if (sendtype != SEND_TYPE_TAGMSG)
+			{
+				/* PRIVMSG or NOTICE */
+				sendto_channel(channel, client, client->direction,
+					       prefix, 0, sendflags, mtags,
+					       ":%s %s %s :%s",
+					       client->name, cmd, targetstr, text);
+			} else {
+				/* TAGMSG:
+				 * Only send if the message includes any user message tags
+				 * and if the 'message-tags' module is loaded.
+				 * Do not allow empty and useless TAGMSG.
+				 */
+				if (!CAP_MESSAGE_TAGS || !has_client_mtags(mtags))
+				{
+					free_message_tags(mtags);
+					continue;
+				}
+				sendto_channel(channel, client, client->direction,
+					       prefix, CAP_MESSAGE_TAGS, sendflags, mtags,
+					       ":%s TAGMSG %s",
+					       client->name, targetstr);
+			}
 
-			RunHook8(HOOKTYPE_CHANMSG, client, channel, sendflags, prefix, targetstr, mtags, text, notice);
+			RunHook8(HOOKTYPE_CHANMSG, client, channel, sendflags, prefix, targetstr, mtags, text, sendtype);
 
 			free_message_tags(mtags);
 
@@ -436,12 +460,12 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 		{
 			char *errmsg = NULL;
 			text = parv[2];
-			if (!can_send_to_user(client, target, &text, &errmsg, notice))
+			if (!can_send_to_user(client, target, &text, &errmsg, sendtype))
 			{
 				/* Message is discarded */
 				if (IsDead(client))
 					return;
-				if (!notice && errmsg)
+				if ((sendtype != SEND_TYPE_NOTICE) && errmsg)
 					sendnumeric(client, ERR_CANTSENDTOUSER, target->name, errmsg);
 			} else
 			{
@@ -449,23 +473,43 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 				MessageTag *mtags = NULL;
 
 				/* Inform sender that recipient is away, if this is so */
-				if (!notice && MyConnect(client) && target->user && target->user->away)
+				if ((sendtype == SEND_TYPE_PRIVMSG) && MyConnect(client) && target->user && target->user->away)
 					sendnumeric(client, RPL_AWAY, target->name, target->user->away);
 
 				new_message(client, recv_mtags, &mtags);
+				if ((sendtype == SEND_TYPE_TAGMSG) && !has_client_mtags(mtags))
+				{
+					free_message_tags(mtags);
+					continue;
+				}
 				labeled_response_inhibit = 1;
 				if (MyUser(target))
 				{
 					/* Deliver to end-user */
-					sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
-							  client->name, cmd, target->name, text);
+					if (sendtype == SEND_TYPE_TAGMSG)
+					{
+						if (HasCapability(target, "message-tags"))
+						{
+							sendto_prefix_one(target, client, mtags, ":%s %s %s",
+									  client->name, cmd, target->name);
+						}
+					} else {
+						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
+								  client->name, cmd, target->name, text);
+					}
 				} else {
 					/* Send to another server */
-					sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
-							  client->id, cmd, target->id, text);
+					if (sendtype == SEND_TYPE_TAGMSG)
+					{
+						sendto_prefix_one(target, client, mtags, ":%s %s %s",
+								  client->id, cmd, target->id);
+					} else {
+						sendto_prefix_one(target, client, mtags, ":%s %s %s :%s",
+								  client->id, cmd, target->id, text);
+					}
 				}
 				labeled_response_inhibit = 0;
-				RunHook5(HOOKTYPE_USERMSG, client, target, mtags, text, notice);
+				RunHook5(HOOKTYPE_USERMSG, client, target, mtags, text, sendtype);
 				free_message_tags(mtags);
 				continue;
 			}
@@ -496,7 +540,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[],
 */
 CMD_FUNC(cmd_private)
 {
-	cmd_message(client, recv_mtags, parc, parv, 0);
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_PRIVMSG);
 }
 
 /*
@@ -506,7 +550,19 @@ CMD_FUNC(cmd_private)
 */
 CMD_FUNC(cmd_notice)
 {
-	cmd_message(client, recv_mtags, parc, parv, 1);
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_NOTICE);
+}
+
+/*
+** cmd_tagmsg
+**	parv[1] = receiver list
+*/
+CMD_FUNC(cmd_tagmsg)
+{
+	/* compatibility hack */
+	parv[2] = "";
+	parv[3] = NULL;
+	cmd_message(client, recv_mtags, parc, parv, SEND_TYPE_TAGMSG);
 }
 
 /* Taken from xchat by Peter Zelezny
@@ -707,13 +763,13 @@ int ban_version(Client *client, char *text)
 /** Can user send a message to this channel?
  * @param client    The client
  * @param channel   The channel
- * @param msgtext The message to send (MAY be changed, even if user is allowed to send)
- * @param errmsg  The error message (will be filled in)
- * @param notice  If it's a NOTICE then this is set to 1. Set to 0 for PRIVMSG.
+ * @param msgtext   The message to send (MAY be changed, even if user is allowed to send)
+ * @param errmsg    The error message (will be filled in)
+ * @param sendtype  One of SEND_TYPE_*
  * @returns Returns 1 if the user is allowed to send, otherwise 0.
  * (note that this behavior was reversed in UnrealIRCd versions <5.x.
  */
-int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, int notice)
+int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, SendType sendtype)
 {
 	Membership *lp;
 	int  member, i = 0;
@@ -769,7 +825,7 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 	/* Modules can plug in as well */
 	for (h = Hooks[HOOKTYPE_CAN_SEND_TO_CHANNEL]; h; h = h->next)
 	{
-		i = (*(h->func.intfunc))(client, channel, lp, msgtext, errmsg, notice);
+		i = (*(h->func.intfunc))(client, channel, lp, msgtext, errmsg, sendtype);
 		if (i != HOOK_CONTINUE)
 		{
 			if (!*errmsg)
@@ -780,7 +836,12 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 			break;
 		}
 		if (!*msgtext || !**msgtext)
-			return 0;
+		{
+			if (sendtype != SEND_TYPE_TAGMSG)
+				return 0;
+			else
+				*msgtext = "";
+		}
 	}
 
 	if (i != HOOK_CONTINUE)
@@ -794,9 +855,6 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char 
 			*errmsg = NULL;
 		return 0;
 	}
-	if (!*msgtext || !**msgtext)
-		return 0;
-
 
 	/* Now we are going to check bans */
 
diff --git a/src/modules/names.c b/src/modules/names.c
@@ -183,7 +183,8 @@ CMD_FUNC(cmd_names)
 		 */
 		for (; *s; s++)
 			buf[idx++] = *s;
-		buf[idx++] = ' ';
+		if (cm->next)
+			buf[idx++] = ' ';
 		buf[idx] = '\0';
 		flag = 1;
 		if (mlen + idx + bufLen > BUFSIZE - 7)
diff --git a/src/modules/nocodes.c b/src/modules/nocodes.c
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER
 	"unrealircd-5",
 };
 
-int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
+int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
 
 MOD_INIT()
 {
@@ -57,7 +57,7 @@ static int has_controlcodes(char *p)
 	return 0;
 }
 
-int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int nocodes_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
 	static char retbuf[4096];
 	Hook *h;
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
@@ -38,7 +38,6 @@ struct RestrictedCommand {
 	int exempt_identified;
 	int exempt_reputation_score;
 	int exempt_webirc;
-	int disable;
 };
 
 typedef struct {
@@ -52,9 +51,9 @@ RestrictedCommand *find_restrictions_bycmd(char *cmd);
 RestrictedCommand *find_restrictions_byconftag(char *conftag);
 int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
-int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice);
-int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
-int rcmd_block_message(Client *client, char *text, int notice, char **errmsg, char *display, char *conftag);
+int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+int rcmd_block_message(Client *client, char *text, SendType sendtype, char **errmsg, char *display, char *conftag);
 CMD_OVERRIDE_FUNC(rcmd_override);
 
 // Globals
@@ -145,12 +144,8 @@ RestrictedCommand *find_restrictions_byconftag(char *conftag) {
 int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
+	int warn_disable = 0;
 	ConfigEntry *cep, *cep2;
-	RestrictedCommand *rcmd;
-	long connect_delay;
-	int exempt_reputation_score;
-	int exempt_webirc;
-	int has_restriction;
 
 	// We are only interested in set::restrict-commands
 	if (type != CONFIG_SET)
@@ -161,12 +156,18 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 
 	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
 	{
-		has_restriction = 0;
 		for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next)
 		{
 			if (!strcmp(cep2->ce_varname, "disable"))
 			{
-				has_restriction = 1;
+				config_warn("%s:%i: set::restrict-commands::%s: the 'disable' option has been removed.",
+				            cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
+				if (!warn_disable)
+				{
+					config_warn("Simply remove 'disable yes;' from the configuration file and "
+				                   "it will have the same effect without it (will disable the command).");
+					warn_disable = 1;
+				}
 				continue;
 			}
 
@@ -179,9 +180,8 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 
 			if (!strcmp(cep2->ce_varname, "connect-delay"))
 			{
-				has_restriction = 1;
-				connect_delay = config_checkval(cep2->ce_vardata, CFG_TIME);
-				if (connect_delay < 10 || connect_delay > 3600)
+				long v = config_checkval(cep2->ce_vardata, CFG_TIME);
+				if ((v < 10) || (v > 3600))
 				{
 					config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 10-3600", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
 					errors++;
@@ -197,8 +197,8 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 			
 			if (!strcmp(cep2->ce_varname, "exempt-reputation-score"))
 			{
-				exempt_reputation_score = atoi(cep2->ce_vardata);
-				if (exempt_reputation_score <= 0)
+				int v = atoi(cep2->ce_vardata);
+				if (v <= 0)
 				{
 					config_error("%s:%i: set::restrict-commands::%s::exempt-reputation-score must be greater than 0", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname);
 					errors++;
@@ -209,12 +209,6 @@ int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname, cep2->ce_varname);
 			errors++;
 		}
-
-		if (!has_restriction)
-		{
-			config_error("%s:%i: no restrictions were set for set::restrict-commands::%s (either 'connect-delay' or 'disable' is required)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
-			errors++;
-		}
 	}
 
 	*errs = errors;
@@ -292,12 +286,6 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
 				rcmd->exempt_reputation_score = atoi(cep2->ce_vardata);
 				continue;
 			}
-
-			if (!strcmp(cep2->ce_varname, "disable"))
-			{
-				rcmd->disable = cep2->ce_vardata ? config_checkval(cep2->ce_vardata, CFG_YESNO) : 1;
-				break; // Using break instead of continue since 'disable' takes precedence anyways
-			}
 		}
 		AddListItem(rcmd, RestrictedCommandList);
 	}
@@ -315,32 +303,32 @@ int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
 		return 1;
 	if (rcmd->exempt_reputation_score > 0 && (GetReputation(client) >= rcmd->exempt_reputation_score))
 		return 1;
-	if (client->local && (TStime() - client->local->firsttime < rcmd->connect_delay))
-		return 0;
-	return 1; // Default to yes so we don't drop too many commands
+	if (rcmd->connect_delay && client->local && (TStime() - client->local->firsttime >= rcmd->connect_delay))
+		return 1;
+	return 0;
 }
 
-int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice)
+int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
 {
-	if (rcmd_block_message(client, *msg, notice, errmsg, "channel", (notice ? "channel-notice" : "channel-message")))
+	if (rcmd_block_message(client, *msg, sendtype, errmsg, "channel", (sendtype == SEND_TYPE_NOTICE ? "channel-notice" : "channel-message")))
 		return HOOK_DENY;
 
 	return HOOK_CONTINUE;
 }
 
-int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	// Need a few extra exceptions for user messages only =]
 	if ((client == target) || IsULine(target))
 		return HOOK_CONTINUE; /* bypass/exempt */
 
-	if (rcmd_block_message(client, *text, notice, errmsg, "user", (notice ? "private-notice" : "private-message")))
+	if (rcmd_block_message(client, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message")))
 		return HOOK_DENY;
 
 	return HOOK_CONTINUE;
 }
 
-int rcmd_block_message(Client *client, char *text, int notice, char **errmsg, char *display, char *conftag)
+int rcmd_block_message(Client *client, char *text, SendType sendtype, char **errmsg, char *display, char *conftag)
 {
 	RestrictedCommand *rcmd;
 	static char errbuf[256];
@@ -350,24 +338,21 @@ int rcmd_block_message(Client *client, char *text, int notice, char **errmsg, ch
 		return 0;
 
 	rcmd = find_restrictions_byconftag(conftag);
-	if (rcmd)
+	if (rcmd && !rcmd_canbypass(client, rcmd))
 	{
-		if (rcmd->disable)
+		int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!!
+		if (rcmd->connect_delay)
 		{
 			ircsnprintf(errbuf, sizeof(errbuf),
-			            "Sending of %ss to %ss been disabled by the network administrators",
-			            (notice ? "notice" : "message"), display);
-			*errmsg = errbuf;
-			return 1;
-		}
-		if (!rcmd_canbypass(client, rcmd))
-		{
+				    "You cannot send %ss to %ss until you've been connected for %ld seconds or more",
+				    (notice ? "notice" : "message"), display, rcmd->connect_delay);
+		} else {
 			ircsnprintf(errbuf, sizeof(errbuf),
-			            "You cannot send %ss to %ss until you've been connected for %ld seconds or more",
-			            (notice ? "notice" : "message"), display, rcmd->connect_delay);
-			*errmsg = errbuf;
-			return 1;
+				    "Sending of %ss to %ss been disabled by the network administrators",
+				    (notice ? "notice" : "message"), display);
 		}
+		*errmsg = errbuf;
+		return 1;
 	}
 
 	// No restrictions apply, process command as normal =]
@@ -385,22 +370,19 @@ CMD_OVERRIDE_FUNC(rcmd_override)
 	}
 
 	rcmd = find_restrictions_bycmd(ovr->command->cmd);
-	if (rcmd)
+	if (rcmd && !rcmd_canbypass(client, rcmd))
 	{
-		if (rcmd->disable)
-		{
-			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
-			               "%s :This command is disabled by the network administrator",
-			               ovr->command->cmd);
-			return;
-		}
-		if (!rcmd_canbypass(client, rcmd))
+		if (rcmd->connect_delay)
 		{
 			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
 			               "%s :You must be connected for at least %ld seconds before you can use this command",
 			               ovr->command->cmd, rcmd->connect_delay);
-			return;
+		} else {
+			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
+			               "%s :This command is disabled by the network administrator",
+			               ovr->command->cmd);
 		}
+		return;
 	}
 
 	// No restrictions apply, process command as normal =]
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
@@ -275,12 +275,14 @@ CMD_FUNC(cmd_sajoin)
 				strlcat(jbuf, ",", sizeof jbuf);
 			strlcat(jbuf, name, sizeof jbuf);
 		}
+		
 		if (did_anything)
 		{
 			if (!sjmode)
 			{
 				//sendnotice(target, "*** You were forced to join %s", jbuf);
 				sendto_umode_global(UMODE_OPER, "%s used SAJOIN to make %s join %s", client->name, target->name, jbuf);
+				/* Logging function added by XeRXeS */
 				ircd_log(LOG_SACMDS,"SAJOIN: %s used SAJOIN to make %s join %s",
 					client->name, target->name, jbuf);
 			}
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -161,7 +161,7 @@ CMD_FUNC(cmd_sapart)
 	parv[2] = comment ? commentx : NULL; // comment
 	if (comment)
 	{
-		//sendnotice(target, "*** You were forced to part %s (%s)", parv[1], commentx);
+		sendnotice(target, "*** You were forced to part %s (%s)", parv[1], commentx);
 		sendto_umode_global(UMODE_OPER, "%s used SAPART to make %s part %s (%s)",
 				    client->name, target->name, parv[1], comment);
 		ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s (%s)",
@@ -169,7 +169,7 @@ CMD_FUNC(cmd_sapart)
 	}
 	else
 	{
-		//sendnotice(target, "*** You were forced to part %s", parv[1]);
+		sendnotice(target, "*** You were forced to part %s", parv[1]);
 		sendto_umode_global(UMODE_OPER, "%s used SAPART to make %s part %s",
 				    client->name, target->name, parv[1]);
 		ircd_log(LOG_SACMDS,"SAPART: %s used SAPART to make %s part %s",
diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c
@@ -95,7 +95,7 @@ CMD_FUNC(cmd_svsnick)
 
 	/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
 	new_message(acptr, NULL, &mtags);
-	sendto_local_common_channels(acptr, NULL, 0, mtags, ":%s NICK :%s", acptr->name, parv[2]);
+	sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, parv[2]);
 	sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, parv[2]);
 	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%ld", acptr->id, parv[2], atol(parv[3]));
 	free_message_tags(mtags);
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
@@ -0,0 +1,315 @@
+/* Target flood protection
+ * (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2
+ */
+   
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"targetfloodprot",
+	"5.0",
+	"Target flood protection (set::anti-flood::target-flood)",
+	"UnrealIRCd Team",
+	"unrealircd-5",
+    };
+
+#define TFP_PRIVMSG	0
+#define TFP_NOTICE	1
+#define TFP_TAGMSG	2
+#define TFP_MAX		3
+
+typedef struct TargetFlood TargetFlood;
+struct TargetFlood {
+	unsigned short cnt[TFP_MAX];
+	time_t t[TFP_MAX];
+};
+
+typedef struct TargetFloodConfig TargetFloodConfig;
+struct TargetFloodConfig {
+	int cnt[TFP_MAX];
+	int t[TFP_MAX];
+};
+
+/* Forward declarations */
+int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+void targetfloodprot_mdata_free(ModData *m);
+int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype);
+int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+
+/* Global variables */
+ModDataInfo *targetfloodprot_client_md = NULL;
+ModDataInfo *targetfloodprot_channel_md = NULL;
+TargetFloodConfig *channelcfg = NULL;
+TargetFloodConfig *privatecfg = NULL;
+
+MOD_TEST()
+{
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, targetfloodprot_config_test);
+	return MOD_SUCCESS;
+}
+
+/** Allocate config and set default configuration */
+void targetfloodprot_defaults(void)
+{
+	channelcfg = safe_alloc(sizeof(TargetFloodConfig));
+	privatecfg = safe_alloc(sizeof(TargetFloodConfig));
+
+	/* set::anti-flood::target-flood::channel-privmsg */
+	channelcfg->cnt[TFP_PRIVMSG] = 45;
+	channelcfg->t[TFP_PRIVMSG] = 5;
+	/* set::anti-flood::target-flood::channel-notice */
+	channelcfg->cnt[TFP_NOTICE] = 15;
+	channelcfg->t[TFP_NOTICE] = 5;
+	/* set::anti-flood::target-flood::channel-tagmsg */
+	channelcfg->cnt[TFP_TAGMSG] = 15;
+	channelcfg->t[TFP_TAGMSG] = 5;
+
+	/* set::anti-flood::target-flood::private-privmsg */
+	privatecfg->cnt[TFP_PRIVMSG] = 30;
+	privatecfg->t[TFP_PRIVMSG] = 5;
+	/* set::anti-flood::target-flood::private-notice */
+	privatecfg->cnt[TFP_NOTICE] = 10;
+	privatecfg->t[TFP_NOTICE] = 5;
+	/* set::anti-flood::target-flood::private-tagmsg */
+	privatecfg->cnt[TFP_TAGMSG] = 10;
+	privatecfg->t[TFP_TAGMSG] = 5;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, targetfloodprot_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, targetfloodprot_can_send_to_channel);
+	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, targetfloodprot_can_send_to_user);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "targetfloodprot";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = targetfloodprot_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_LOCAL_CLIENT;
+	targetfloodprot_client_md = ModDataAdd(modinfo->handle, mreq);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "targetfloodprot";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = targetfloodprot_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_CHANNEL;
+	targetfloodprot_channel_md = ModDataAdd(modinfo->handle, mreq);
+
+	targetfloodprot_defaults();
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+#ifndef CheckNull
+ #define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
+#endif
+
+int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	if (type != CONFIG_SET_ANTI_FLOOD)
+		return 0;
+
+	/* We are only interrested in set::anti-flood::target-flood.. */
+	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "target-flood"))
+		return 0;
+
+	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	{
+		CheckNull(cep);
+
+		if (!strcmp(cep->ce_varname, "channel-privmsg") ||
+		    !strcmp(cep->ce_varname, "channel-notice") ||
+		    !strcmp(cep->ce_varname, "channel-tagmsg") ||
+		    !strcmp(cep->ce_varname, "private-privmsg") ||
+		    !strcmp(cep->ce_varname, "private-notice") ||
+		    !strcmp(cep->ce_varname, "private-tagmsg"))
+		{
+			int cnt = 0, period = 0;
+
+			if (!config_parse_flood(cep->ce_vardata, &cnt, &period) ||
+			    (cnt < 1) || (cnt > 10000) || (period < 1) || (period > 120))
+			{
+				config_error("%s:%i: set::anti-flood::target-flood::%s error. "
+				             "Syntax is '<count>:<period>' (eg 5:60). "
+				             "Count must be 1-10000 and period must be 1-120.",
+				             cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+				             cep->ce_varname);
+				errors++;
+			}
+		} else
+		{
+			config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
+				cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
+			errors++;
+			continue;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep, *cepp;
+
+	if (type != CONFIG_SET_ANTI_FLOOD)
+		return 0;
+
+	/* We are only interrested in set::anti-flood::target-flood.. */
+	if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "target-flood"))
+		return 0;
+
+	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	{
+		if (!strcmp(cep->ce_varname, "channel-privmsg"))
+			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
+		else if (!strcmp(cep->ce_varname, "channel-notice"))
+			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
+		else if (!strcmp(cep->ce_varname, "channel-tagmsg"))
+			config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
+		else if (!strcmp(cep->ce_varname, "private-privmsg"))
+			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
+		else if (!strcmp(cep->ce_varname, "private-notice"))
+			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
+		else if (!strcmp(cep->ce_varname, "private-tagmsg"))
+			config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_TAGMSG], &privatecfg->t[TFP_TAGMSG]);
+	}
+
+	return 1;
+}
+
+/** UnrealIRCd internals: free object. */
+void targetfloodprot_mdata_free(ModData *m)
+{
+	/* we don't have any members to free, so this is easy */
+	safe_free(m->ptr);
+}
+
+int sendtypetowhat(SendType sendtype)
+{
+	if (sendtype == SEND_TYPE_PRIVMSG)
+		return 0;
+	if (sendtype == SEND_TYPE_NOTICE)
+		return 1;
+	if (sendtype == SEND_TYPE_TAGMSG)
+		return 2;
+#ifdef DEBUGMODE
+	ircd_log(LOG_ERROR, "sendtypetowhat() for unknown value %d", (int)sendtype);
+	abort();
+#endif
+	return 0; /* otherwise, default to privmsg i guess */
+}
+
+int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
+{
+	TargetFlood *flood;
+	static char errbuf[256];
+	int what;
+
+	/* This is redundant, right? */
+	if (!MyUser(client))
+		return HOOK_CONTINUE;
+
+	/* Really, only IRCOps override */
+	if (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL))
+		return HOOK_CONTINUE;
+
+	what = sendtypetowhat(sendtype);
+
+	if (moddata_channel(channel, targetfloodprot_channel_md).ptr == NULL)
+	{
+		/* Alloc a new entry if it doesn't exist yet */
+		moddata_channel(channel, targetfloodprot_channel_md).ptr = safe_alloc(sizeof(TargetFlood));
+	}
+
+	flood = (TargetFlood *)moddata_channel(channel, targetfloodprot_channel_md).ptr;
+
+	if ((TStime() - flood->t[what]) >= channelcfg->t[what])
+	{
+		/* Reset due to moving into a new time slot */
+		flood->t[what] = TStime();
+		flood->cnt[what] = 1;
+		return HOOK_CONTINUE; /* forget about it.. */
+	}
+
+	if (flood->cnt[what] >= channelcfg->cnt[what])
+	{
+		/* Flood detected */
+		snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
+		*errmsg = errbuf;
+		return HOOK_DENY;
+	}
+
+	flood->cnt[what]++;
+	return HOOK_CONTINUE;
+}
+
+int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
+{
+	TargetFlood *flood;
+	static char errbuf[256];
+	int what;
+
+	/* Check if it is our TARGET ('target'), so yeah
+	 * be aware that 'client' may be remote client in all the code that follows!
+	 */
+	if (!MyUser(target))
+		return HOOK_CONTINUE;
+
+	/* Really, only IRCOps override */
+	if (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL))
+		return HOOK_CONTINUE;
+
+	what = sendtypetowhat(sendtype);
+
+	if (moddata_local_client(target, targetfloodprot_client_md).ptr == NULL)
+	{
+		/* Alloc a new entry if it doesn't exist yet */
+		moddata_local_client(target, targetfloodprot_client_md).ptr = safe_alloc(sizeof(TargetFlood));
+	}
+
+	flood = (TargetFlood *)moddata_local_client(target, targetfloodprot_client_md).ptr;
+
+	if ((TStime() - flood->t[what]) >= privatecfg->t[what])
+	{
+		/* Reset due to moving into a new time slot */
+		flood->t[what] = TStime();
+		flood->cnt[what] = 1;
+		return HOOK_CONTINUE; /* forget about it.. */
+	}
+
+	if (flood->cnt[what] >= privatecfg->cnt[what])
+	{
+		/* Flood detected */
+		snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
+		*errmsg = errbuf;
+		return HOOK_DENY;
+	}
+
+	flood->cnt[what]++;
+	return HOOK_CONTINUE;
+}
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -115,6 +115,7 @@ struct TKLTypeTable
  * IMPORTANT IF YOU ARE ADDING A NEW TYPE TO THIS TABLE:
  * - also update eline_syntax()
  * - also check if eline_type_requires_ip() needs to be updated
+ * - update help.conf (HELPOP ELINE)
  * - more?
  */
 TKLTypeTable tkl_types[] = {
@@ -868,7 +869,7 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	{
 		/* Default setting if no 'type' is specified: */
 		if (!strcmp(ce->ce_vardata, "ban"))
-			strlcpy(bantypes, "kgzZs", sizeof(bantypes));
+			strlcpy(bantypes, "kGzZs", sizeof(bantypes));
 		else if (!strcmp(ce->ce_vardata, "throttle"))
 			strlcpy(bantypes, "c", sizeof(bantypes));
 		else if (!strcmp(ce->ce_vardata, "blacklist"))
@@ -1208,6 +1209,22 @@ int ban_too_broad(char *usermask, char *hostmask)
 	return 1;
 }
 
+/** Ugly function, only meant to be called by cmd_tkl_line() */
+static int xline_exists(char *type, char *usermask, char *hostmask)
+{
+	char *umask = usermask;
+	int softban = 0;
+	int tpe = tkl_chartotype(type[0]);
+
+	if (*umask == '%')
+	{
+		umask++;
+		softban = 1;
+	}
+
+	return find_tkl_serverban(tpe, umask, hostmask, softban) ? 1 : 0;
+}
+
 /** Intermediate layer between user functions such as KLINE/GLINE
  * and the TKL layer (cmd_tkl).
  * This allows us doing some syntax checking and other helpful
@@ -1434,6 +1451,13 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 			return;
 		}
 
+		/* Some stupid checking */
+		if (xline_exists(type, usermask, hostmask))
+		{
+			sendnotice(client, "ERROR: Ban for %s@%s already exists.", usermask, hostmask);
+			return;
+		}
+
 		/* call the tkl layer .. */
 		cmd_tkl(&me, NULL, 9, tkllayer);
 	}
diff --git a/src/modules/tkldb.c b/src/modules/tkldb.c
@@ -402,9 +402,10 @@ int read_tkldb(void)
 	FILE *fd;
 	TKL *tkl = NULL;
 	uint32_t magic = 0;
+	uint32_t version;
 	uint64_t cnt;
 	uint64_t tklcount = 0;
-	uint32_t version;
+	uint64_t v;
 	int added_cnt = 0;
 	char c;
 	char *str;
@@ -483,8 +484,10 @@ int read_tkldb(void)
 
 		/* Read the common types (same for all TKLs) */
 		R_SAFE(read_str(fd, &tkl->set_by));
-		R_SAFE(read_int64(fd, &tkl->set_at));
-		R_SAFE(read_int64(fd, &tkl->expire_at));
+		R_SAFE(read_int64(fd, &v));
+		tkl->set_at = v;
+		R_SAFE(read_int64(fd, &v));
+		tkl->expire_at = v;
 
 		/* Save some CPU... if it's already expired then don't bother adding */
 		if (tkl->expire_at != 0 && tkl->expire_at <= TStime())
@@ -645,7 +648,8 @@ int read_tkldb(void)
 			}
 
 			R_SAFE(read_str(fd, &tkl->ptr.spamfilter->tkl_reason));
-			R_SAFE(read_int64(fd, &tkl->ptr.spamfilter->tkl_duration));
+			R_SAFE(read_int64(fd, &v));
+			tkl->ptr.spamfilter->tkl_duration = v;
 
 			if (find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
 			                        tkl->ptr.spamfilter->action,
diff --git a/src/modules/typing-indicator.c b/src/modules/typing-indicator.c
@@ -0,0 +1,105 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/typing-indicator.c
+ *   (C) 2020 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   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 1, 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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"typing-indicator",
+	"5.0",
+	"+typing client tag",
+	"UnrealIRCd Team",
+	"unrealircd-5",
+	};
+
+int ti_mtag_is_ok(Client *client, char *name, char *value);
+void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+typing";
+	mtag.is_ok = ti_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "+draft/typing";
+	mtag.is_ok = ti_mtag_is_ok;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_ti);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending the mtag is permitted to do so.
+ */
+int ti_mtag_is_ok(Client *client, char *name, char *value)
+{
+	/* Require a non-empty parameter */
+	if (BadPtr(value))
+		return 0;
+
+	/* These are the only valid values: */
+	if (!strcmp(value, "active") || !strcmp(value, "paused") || !strcmp(value, "done"))
+		return 1;
+
+	/* All the rest is considered illegal */
+	return 0;
+}
+
+void mtag_add_ti(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature)
+{
+	MessageTag *m;
+
+	if (IsUser(client))
+	{
+		m = find_mtag(recv_mtags, "+typing");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+		m = find_mtag(recv_mtags, "+draft/typing");
+		if (m)
+		{
+			m = duplicate_mtag(m);
+			AddListItem(m, *mtag_list);
+		}
+	}
+}
diff --git a/src/modules/usermodes/censor.c b/src/modules/usermodes/censor.c
@@ -20,7 +20,7 @@ long UMODE_CENSOR = 0L;
 
 #define IsCensored(x) (x->umodes & UMODE_CENSOR)
 
-int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
+int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
 
 int censor_config_test(ConfigFile *, ConfigEntry *, int, int *);
 int censor_config_run(ConfigFile *, ConfigEntry *, int);
@@ -237,7 +237,7 @@ char *stripbadwords_message(char *str, int *blocked)
 	return stripbadwords(str, conf_badword_message, blocked);
 }
 
-int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	int blocked = 0;
 
diff --git a/src/modules/usermodes/noctcp.c b/src/modules/usermodes/noctcp.c
@@ -34,7 +34,7 @@ long UMODE_NOCTCP = 0L;
 
 #define IsNoCTCP(client)    (client->umodes & UMODE_NOCTCP)
 
-int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
+int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
 
 MOD_TEST()
 {
@@ -74,9 +74,10 @@ static int IsACTCP(char *s)
 	return 0;
 }
 
-int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
-	if (MyUser(client) && !notice && IsNoCTCP(target) && !IsOper(client) && IsACTCP(*text))
+	if (MyUser(client) && (sendtype == SEND_TYPE_PRIVMSG) &&
+	    IsNoCTCP(target) && !IsOper(client) && IsACTCP(*text))
 	{
 		*errmsg = "User does not accept CTCPs";
 		return HOOK_DENY;
diff --git a/src/modules/usermodes/privdeaf.c b/src/modules/usermodes/privdeaf.c
@@ -17,7 +17,7 @@ ModuleHeader MOD_HEADER
 static long UMODE_PRIVDEAF = 0;
 static Umode *UmodePrivdeaf = NULL;
 
-int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
+int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
 
 MOD_INIT()
 {
@@ -47,7 +47,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	if ((target->umodes & UMODE_PRIVDEAF) && !IsOper(client) &&
 	    !IsULine(client) && !IsServer(client) && (client != target))
diff --git a/src/modules/usermodes/regonlymsg.c b/src/modules/usermodes/regonlymsg.c
@@ -35,7 +35,7 @@ ModuleHeader MOD_HEADER
 long UMODE_REGONLYMSG = 0L;
 
 /* Forward declarations */
-int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
+int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
                     
 MOD_INIT()
 {
@@ -57,7 +57,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	if (IsRegOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsLoggedIn(client))
 	{
diff --git a/src/modules/usermodes/secureonlymsg.c b/src/modules/usermodes/secureonlymsg.c
@@ -36,7 +36,7 @@ ModuleHeader MOD_HEADER
 long UMODE_SECUREONLYMSG = 0L;
 
 /* Forward declarations */
-int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice);
+int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
                     
 MOD_INIT()
 {
@@ -58,7 +58,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice)
+int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype)
 {
 	if (IsSecureOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsSecureConnect(client))
 	{
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -560,7 +560,7 @@ static void who_common_channel(Client *client, Channel *channel,
 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
 {
 	Client *acptr;
-	int maxmatches = WHOLIMIT ? WHOLIMIT : 100;
+	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
 
 	/* first, list all matching INvisible clients on common channels
 	 * if this is not an operspy who
diff --git a/src/send.c b/src/send.c
@@ -422,7 +422,7 @@ void sendto_channel(Channel *channel, Client *from, Client *skip,
 		continue;
 good:
 		/* Now deal with 'clicap' (if non-zero) */
-		if (clicap && MyUser(acptr) && !HasCapabilityFast(acptr, clicap))
+		if (clicap && MyUser(acptr) && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
 			continue;
 
 		if (MyUser(acptr))
@@ -558,7 +558,7 @@ void sendto_local_common_channels(Client *user, Client *skip, long clicap, Messa
 				if (acptr->local->serial == current_serial)
 					continue; /* message already sent to this client */
 
-				if (clicap && !HasCapabilityFast(acptr, clicap))
+				if (clicap && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
 					continue; /* client does not have the specified capability */
 
 				if (acptr == skip)
diff --git a/src/socket.c b/src/socket.c
@@ -616,6 +616,7 @@ void completed_connection(int fd, int revents, void *data)
  */
 void close_connection(Client *client)
 {
+	RunHook(HOOKTYPE_CLOSE_CONNECTION, client);
 	/* This function must make MyConnect(client) == FALSE,
 	 * and set client->direction == NULL.
 	 */
diff --git a/src/user.c b/src/user.c
@@ -124,7 +124,7 @@ long set_usermode(char *umode)
 /** Convert a target pointer to an 8 bit hash, used for target limiting. */
 unsigned char hash_target(void *target)
 {
-	unsigned long long v = (unsigned long long)target;
+	uintptr_t v = (uintptr_t)target;
 	/* ircu does >> 16 and 8 but since our sizeof(Client) is
 	 * towards 512 (and hence the alignment), that bit is useless.
 	 * So we do >> 17 and 9.
@@ -552,6 +552,7 @@ void set_targmax_defaults(void)
 	/* Set the defaults */
 	setmaxtargets("PRIVMSG", 4);
 	setmaxtargets("NOTICE", 1);
+	setmaxtargets("TAGMSG", 1);
 	setmaxtargets("NAMES", 1); // >1 is not supported
 	setmaxtargets("WHOIS", 1);
 	setmaxtargets("WHOWAS", 1); // >1 is not supported
diff --git a/src/version.c.SH b/src/version.c.SH
@@ -4,7 +4,7 @@ echo "Extracting src/version.c..."
 
 #id=`grep '$Id: Changes,v' ../Changes`
 #id=`echo $id |sed 's/.* Changes\,v \(.*\) .* Exp .*/\1/'`
-id="5.0.4"
+id="5.0.5"
 echo "$id"
 
 if test -r version.c
diff --git a/src/windows/UnrealIRCd.exe.manifest b/src/windows/UnrealIRCd.exe.manifest
@@ -3,7 +3,7 @@
 <assemblyIdentity
     processorArchitecture="amd64"
     name="UnrealIRCd.UnrealIRCd.5"
-    version="5.0.4.0"
+    version="5.0.5.0"
     type="win32"
 />
 <description>Internet Relay Chat Daemon</description>
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -6,7 +6,7 @@
 
 [Setup]
 AppName=UnrealIRCd 5
-AppVerName=UnrealIRCd 5.0.4
+AppVerName=UnrealIRCd 5.0.5
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org