unrealircd

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

commit 88a904d7f7913b6cce5f83385af67f240bf3f48e
parent b351238d427ef0254c712ef0e07ff8ef67a1d8f5
Author: acidvegas <acid.vegas@acid.vegas>
Date: Fri, 8 Jan 2021 18:15:08 -0500

Updated to 5.0.8

Diffstat:
MConfig | 510++++++++++++++++++++++++++++++++++++++++---------------------------------------
MMakefile.in | 84++++++++++++++++++++++++++++++++++++++++---------------------------------------
MMakefile.windows | 4++++
ASECURITY.md | 21+++++++++++++++++++++
Mautoconf/m4/unreal.m4 | 40++++++++++++++++++++++++++++++++++++++++
Mconfigure | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mconfigure.ac | 6++++--
Mdoc/Config.header | 2+-
Mdoc/RELEASE-NOTES.md | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mextras/build-tests/windows/build.bat | 5+++++
Mextras/doxygen/Developers.md | 5+++--
Mextras/doxygen/Doxyfile | 2+-
Minclude/h.h | 42++++++++++++++++++++++++++++++++++--------
Minclude/list.h | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Minclude/modules.h | 1192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Minclude/setup.h.in | 6++++++
Minclude/struct.h | 38+++++++++++++++++++++-----------------
Minclude/windows/setup.h | 2+-
Msrc/api-channelmode.c | 4+++-
Msrc/api-efunctions.c | 2+-
Msrc/api-event.c | 1+
Msrc/api-messagetag.c | 4+++-
Msrc/channel.c | 13+++++++++++--
Msrc/conf.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/crashreport.c | 24+-----------------------
Msrc/hash.c | 40++++++++++++++++++++++++++--------------
Msrc/ircd.c | 42++++++++++++------------------------------
Msrc/misc.c | 56+++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/modulemanager.c | 22++++++++++++++++++++++
Msrc/modules/chanmodes/secureonly.c | 11-----------
Msrc/modules/connthrottle.c | 2--
Msrc/modules/extbans/Makefile.in | 6+++++-
Asrc/modules/extbans/securitygroup.c | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/mode.c | 3+++
Msrc/modules/nick.c | 46+++++++++++++++++++++++++++++++++-------------
Msrc/modules/protoctl.c | 12++++++------
Msrc/modules/reputation.c | 135++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/modules/restrict-commands.c | 2--
Msrc/modules/stats.c | 31+++++++++++++++++++------------
Msrc/modules/targetfloodprot.c | 8++++----
Msrc/modules/tkl.c | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/modules/whox.c | 46++++++----------------------------------------
Msrc/parse.c | 5++++-
Msrc/send.c | 399+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/serv.c | 1-
Msrc/support.c | 11-----------
Msrc/tls.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/user.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/version.c.SH | 2+-
Msrc/windows/UnrealIRCd.exe.manifest | 2+-
Msrc/windows/unrealinst.iss | 3++-

51 files changed, 2916 insertions(+), 851 deletions(-)

diff --git a/Config b/Config
@@ -104,49 +104,59 @@ fi
 echo $CONF
 $CONF || exit 1
 cd "$UNREALCWD"
-if [  "$QUICK" != "1" ] ; then
-if [ ! -f $CONFDIR/tls/server.cert.pem -a ! -f $CONFDIR/ssl/server.cert.pem ]; then
-export OPENSSLPATH
-TEST=""
-while [ -z "$TEST" ] ; do
-    if [ "$GENCERTIFICATE" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to generate an SSL certificate for the IRCd?"
-    echo "Only answer No if you already have one."
-    echo $n "[$TEST] -> $c"
-	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
-	[Yy]*)
-	    GENCERTIFICATE="1"
-	    ;;
-	[Nn]*)
-	    GENCERTIFICATE=""
-	    ;;
-	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
-done
-if [ "$GENCERTIFICATE" = 1 ]; then
-	make pem
-	echo "Certificate created successfully."
-	sleep 1
-else
-	echo "Ok, not generating SSL certificate. Make sure that the certificate and key"
-	echo "are installed in conf/tls/server.crt.pem and conf/tls/server.key.pem prior to starting the IRCd."
-fi
-else
-echo "SSL certificate already exists in configuration directory, no need to regenerate."
-fi
+
+if [ "$QUICK" != "1" ] ; then
+	if [ ! -f $CONFDIR/tls/server.cert.pem -a ! -f $CONFDIR/ssl/server.cert.pem ]; then
+		export OPENSSLPATH
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			if [ "$GENCERTIFICATE" = "1" ] ; then
+				TEST="Yes"
+			else
+				TEST="No"
+			fi
+			echo ""
+			echo "UnrealIRCd requires an SSL certificate in order to work."
+			echo "Do you want to generate an SSL certificate for the IRCd?"
+			echo "Only answer No if you already have one."
+			echo $n "[$TEST] -> $c"
+			read cc
+			if [ -z "$cc" ] ; then
+			cc=$TEST
+			fi
+			case "$cc" in
+			[Yy]*)
+				GENCERTIFICATE="1"
+				;;
+			[Nn]*)
+				GENCERTIFICATE=""
+				;;
+			*)
+				echo ""
+				echo "You must enter either Yes or No"
+				TEST=""
+				;;
+			esac
+		done
+		if [ "$GENCERTIFICATE" = 1 ]; then
+			echo
+			echo "*******************************************************************************"
+			echo "Next you will be asked some questions in order to generate the SSL certificate."
+			echo "IMPORTANT: If you don't own a domain or don't know what to answer, then you can"
+			echo "           simply press ENTER or use fictional names for each question!"
+			echo "*******************************************************************************"
+			echo "Press ENTER to continue"
+			read cc
+			make pem
+			echo "Certificate created successfully."
+			sleep 1
+		else
+			echo "Ok, not generating SSL certificate. Make sure that the certificate and key"
+			echo "are installed in conf/tls/server.cert.pem and conf/tls/server.key.pem prior to starting the IRCd."
+		fi
+	else
+		echo "SSL certificate already exists in configuration directory, no need to regenerate."
+	fi
 fi
 
 # Silently force a 'make clean' as otherwise part (or whole) of the
@@ -158,89 +168,89 @@ make clean 1>/dev/null 2>&1
 RUN_ADVANCED () {
 TEST=""
 while [ -z "$TEST" ] ; do
-    if [ "$SHOWLISTMODES" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to show the modes a channel has set in the /list output?"
-    echo $n "[$TEST] -> $c"
+	if [ "$SHOWLISTMODES" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to show the modes a channel has set in the /list output?"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
 	[Yy]*)
-	    SHOWLISTMODES="1"
-	    ;;
+		SHOWLISTMODES="1"
+		;;
 	[Nn]*)
-	    SHOWLISTMODES=""
-	    ;;
+		SHOWLISTMODES=""
+		;;
 	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
 done
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    if [ "$NOOPEROVERRIDE" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to disable oper override?"
-    echo $n "[$TEST] -> $c"
+	if [ "$NOOPEROVERRIDE" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to disable oper override?"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
 	[Yy]*)
-	    NOOPEROVERRIDE="1"
-	    ;;
+		NOOPEROVERRIDE="1"
+		;;
 	[Nn]*)
-	    NOOPEROVERRIDE=""
-	    ;;
+		NOOPEROVERRIDE=""
+		;;
 	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
 done
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to require opers to /invite themselves into a +s or +p channel?"
-    echo $n "[$TEST] -> $c"
+	if [ "$OPEROVERRIDEVERIFY" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to require opers to /invite themselves into a +s or +p channel?"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
 	[Yy]*)
-	    OPEROVERRIDEVERIFY="1"
-	    ;;
+		OPEROVERRIDEVERIFY="1"
+		;;
 	[Nn]*)
-	    OPEROVERRIDEVERIFY=""
-	    ;;
+		OPEROVERRIDEVERIFY=""
+		;;
 	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
 done
 
 }
@@ -250,7 +260,7 @@ UNREALCWD="`pwd`"
 BASEPATH="$HOME/unrealircd"
 DEFPERM="0600"
 SSLDIR=""
-NICKNAMEHISTORYLENGTH="100"
+NICKNAMEHISTORYLENGTH="2000"
 MAXCONNECTIONS_REQUEST="auto"
 REMOTEINC="1"
 CURLDIR=""
@@ -261,9 +271,9 @@ OPEROVERRIDEVERIFY=""
 GENCERTIFICATE="1"
 EXTRAPARA=""
 if [ "`eval echo -n 'a'`" = "-n a" ] ; then
-        c="\c"
+	c="\c"
 else
-        n="-n"
+	n="-n"
 fi
 
 
@@ -314,19 +324,19 @@ fi
 
 clear
 
-    if [ -f "doc/Config.header" -a -z "$NOINTRO" ] ; then
-        more doc/Config.header
-        echo ""
-        echo $n "[Press Enter to continue]"
-        read cc
-        clear
-    fi
+if [ -f "doc/Config.header" -a -z "$NOINTRO" ] ; then
+	more doc/Config.header
+	echo ""
+	echo $n "[Press Enter to continue]"
+	read cc
+	clear
+fi
 
 echo "We will now ask you a number of questions. You can just press ENTER to accept the defaults!"
 echo ""
 
 # This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-5.0.7-rc1 unrealircd-5.0.6 unrealircd-5.0.5.1 unrealircd-5.0.5 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"
+UNREALRELEASES="unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6 unrealircd-5.0.5.1 unrealircd-5.0.5 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"
 if [ -f "config.settings" ]; then
 	. ./config.settings
 else
@@ -405,9 +415,9 @@ echo " If this directory does not exist it will be created.)"
 echo $n "[$TEST] -> $c"
 read cc
 if [ -z "$cc" ] ; then
-   BASEPATH=$TEST
+	BASEPATH=$TEST
 else
-   BASEPATH=`eval echo $cc` # modified
+	BASEPATH=`eval echo $cc` # modified
 fi
 if [ "$BASEPATH" = "$UNREALCWD" ]; then
 	echo ""
@@ -431,32 +441,28 @@ PRIVATELIBDIR="$BASEPATH/lib"
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    TEST="$DEFPERM"
-    echo ""
-    echo "What should the default permissions for your configuration files be? (Set this to 0 to disable)"
-    echo "It is strongly recommended that you use 0600 to prevent unwanted reading of the file"
-    echo $n "[$TEST] -> $c"
+	TEST="$DEFPERM"
+	echo ""
+	echo "What should the default permissions for your configuration files be? (Set this to 0 to disable)"
+	echo "It is strongly recommended that you use 0600 to prevent unwanted reading of the file"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	DEFPERM=$TEST
-	break
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		DEFPERM=$TEST
+		break
+	fi
+	case "$cc" in
 	[0-9]*)
-	    DEFPERM="$cc"
-	    ;;
+		DEFPERM="$cc"
+		;;
 	*)
-	    echo ""
-	    echo "You must enter a number"
-	    TEST=""
-	    ;;
-    esac
-
+		echo ""
+		echo "You must enter a number"
+		TEST=""
+		;;
+	esac
 done
 
-
-
-
 echo ""
 echo "If you want, you can manually enter the path to OpenSSL/LibreSSL here."
 echo "In most cases you can leave this blank and it will be detected automatically."
@@ -475,43 +481,43 @@ fi
 
 TEST="$SSLDIR"
 echo $n "[$TEST] -> $c"
-    read cc
+read cc
 if [ -z "$cc" ] ; then
-    SSLDIR="$TEST"
+	SSLDIR="$TEST"
 else 
-    SSLDIR=`eval echo $cc` # modified
+	SSLDIR=`eval echo $cc` # modified
 fi
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    if [ "$REMOTEINC" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to enable remote includes?"
-    echo "This allows stuff like this in your configuration file:"
-    echo "include \"https://www.somesite.org/files/opers.conf\";"
-    echo $n "[$TEST] -> $c"
+	if [ "$REMOTEINC" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to enable remote includes?"
+	echo "This allows stuff like this in your configuration file:"
+	echo "include \"https://www.somesite.org/files/opers.conf\";"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
 	[Yy]*)
-	    REMOTEINC="1"
-	    ;;
+		REMOTEINC="1"
+		;;
 	[Nn]*)
-	    REMOTEINC=""
-	    CURLDIR=""
-	    ;;
+		REMOTEINC=""
+		CURLDIR=""
+		;;
 	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
 done
 
 if [ "$REMOTEINC" = "1" ] ; then
@@ -589,108 +595,108 @@ if [ "$REMOTEINC" = "1" ] ; then
 		fi
 	fi
 
-        if [ "x$CURLDIR" = "x" ]; then
-        	# Still empty?
-        	TEST=""
-                while [ -z "$TEST" ] ; do
-                    TEST="Yes"
-                    echo ""
-        	    echo "Do you want me to automatically download and install curl for you?"
-                    echo $n "[$TEST] -> $c"
-                        read cc
-                    if [ -z "$cc" ] ; then
-                        cc=$TEST
-                    fi
-                    case "$cc" in
-                        [Yy]*)
-                            INSTALLCURL="1"
-                            CURLDIR="$UNREALCWD/extras/curl"
-                            ;;
-                        [Nn]*)
-                            INSTALLCURL="0"
-                            ;;
-                        *)
-                            echo ""
-                            echo "You must enter either Yes or No"
-                            TEST=""
-                            ;;
-                    esac
-                done
-        fi
+	if [ "x$CURLDIR" = "x" ]; then
+		# Still empty?
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			TEST="Yes"
+			echo ""
+			echo "Do you want me to automatically download and install curl for you?"
+			echo $n "[$TEST] -> $c"
+			read cc
+			if [ -z "$cc" ] ; then
+				cc=$TEST
+			fi
+			case "$cc" in
+			[Yy]*)
+				INSTALLCURL="1"
+				CURLDIR="$UNREALCWD/extras/curl"
+				;;
+			[Nn]*)
+				INSTALLCURL="0"
+				;;
+			*)
+				echo ""
+				echo "You must enter either Yes or No"
+				TEST=""
+				;;
+			esac
+		done
+	fi
 
 	if [ "$INSTALLCURL" != "1" ]; then
-	  TEST=""
-          while [ -z "$TEST" ] ; do
-                  TEST="$CURLDIR"
-                  echo ""
-                  echo "Specify the directory you installed libcurl to"
-                  echo $n "[$TEST] -> $c"
-                  read cc
-                  if [ -z "$cc" ] ; then
-                      cc=$TEST
-                  else
-                      TEST=$cc
-                      CURLDIR=`eval echo $cc` # modified
-                  fi
-          done
-        fi
+		TEST=""
+		while [ -z "$TEST" ] ; do
+			TEST="$CURLDIR"
+			echo ""
+			echo "Specify the directory you installed libcurl to"
+			echo $n "[$TEST] -> $c"
+			read cc
+			if [ -z "$cc" ] ; then
+				cc=$TEST
+			else
+				TEST=$cc
+				CURLDIR=`eval echo $cc` # modified
+			fi
+		done
+	fi
 fi
 
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    if [ "$PREFIXAQ" = "1" ] ; then
-	TEST="Yes"
-    else
-	TEST="No"
-    fi
-    echo ""
-    echo "Do you want to enable prefixes for chanadmin and chanowner?"
-    echo "This will give +a the & prefix and ~ for +q (just like +o is @)"
-    echo "Supported by the major clients (mIRC, xchat, epic, eggdrop, Klient,"
-    echo "PJIRC, irssi, CGI:IRC, etc.)"
-    echo "This feature should be enabled/disabled network-wide."
-    echo $n "[$TEST] -> $c"
+	if [ "$PREFIXAQ" = "1" ] ; then
+		TEST="Yes"
+	else
+		TEST="No"
+	fi
+	echo ""
+	echo "Do you want to enable prefixes for chanadmin and chanowner?"
+	echo "This will give +a the & prefix and ~ for +q (just like +o is @)"
+	echo "Supported by the major clients (mIRC, xchat, epic, eggdrop, Klient,"
+	echo "PJIRC, irssi, CGI:IRC, etc.)"
+	echo "This feature should be enabled/disabled network-wide."
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	cc=$TEST
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		cc=$TEST
+	fi
+	case "$cc" in
 	[Yy]*)
-	    PREFIXAQ="1"
-	    ;;
+		PREFIXAQ="1"
+		;;
 	[Nn]*)
-	    PREFIXAQ=""
-	    ;;
+		PREFIXAQ=""
+		;;
 	*)
-	    echo ""
-	    echo "You must enter either Yes or No"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter either Yes or No"
+		TEST=""
+		;;
+	esac
 done
 
 TEST=""
 while [ -z "$TEST" ] ; do
-    TEST="$NICKNAMEHISTORYLENGTH"
-    echo ""
-    echo "How far back do you want to keep the nickname history?"
-    echo $n "[$TEST] -> $c"
+	TEST="$NICKNAMEHISTORYLENGTH"
+	echo ""
+	echo "How far back do you want to keep the nickname history?"
+	echo $n "[$TEST] -> $c"
 	read cc
-    if [ -z "$cc" ] ; then
-	NICKNAMEHISTORYLENGTH=$TEST
-	break
-    fi
-    case "$cc" in
+	if [ -z "$cc" ] ; then
+		NICKNAMEHISTORYLENGTH=$TEST
+		break
+	fi
+	case "$cc" in
 	[1-9]*)
-	    NICKNAMEHISTORYLENGTH="$cc"
-	    ;;
+		NICKNAMEHISTORYLENGTH="$cc"
+		;;
 	*)
-	    echo ""
-	    echo "You must enter a number"
-	    TEST=""
-	    ;;
-    esac
+		echo ""
+		echo "You must enter a number"
+		TEST=""
+		;;
+	esac
 done
 
 echo ""
@@ -737,7 +743,7 @@ echo "Otherwise, see \`./configure --help' and write them here:"
 echo $n "[$TEST] -> $c"
 read EXTRAPARA
 if [ -z "$EXTRAPARA" ]; then
-    EXTRAPARA="$TEST"
+	EXTRAPARA="$TEST"
 fi
 
 rm -f config.settings
diff --git a/Makefile.in b/Makefile.in
@@ -164,54 +164,56 @@ depend:
 	done
 
 install: all
-	$(INSTALL) -m 0700 -d @BINDIR@
-	$(INSTALL) -m 0700 src/ircd @BINDIR@/unrealircd
-	$(INSTALL) -m 0700 -d @DOCDIR@
-	$(INSTALL) -m 0600 doc/Authors doc/coding-guidelines doc/tao.of.irc @DOCDIR@
-	$(INSTALL) -m 0700 -d @CONFDIR@
-	$(INSTALL) -m 0600 doc/conf/*.conf @CONFDIR@
-	$(INSTALL) -m 0600 doc/conf/*.motd @CONFDIR@
-	$(INSTALL) -m 0600 doc/conf/modules.sources.list @CONFDIR@ ; \
-	$(INSTALL) -m 0700 unrealircd @SCRIPTDIR@
-	$(INSTALL) -m 0700 -d @MODULESDIR@
-	@rm -f @MODULESDIR@/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/*.so @MODULESDIR@
-	$(INSTALL) -m 0700 -d @MODULESDIR@/usermodes
-	@rm -f @MODULESDIR@/usermodes/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/usermodes/*.so @MODULESDIR@/usermodes
-	$(INSTALL) -m 0700 -d @MODULESDIR@/chanmodes
-	@rm -f @MODULESDIR@/chanmodes/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/chanmodes/*.so @MODULESDIR@/chanmodes
-	$(INSTALL) -m 0700 -d @MODULESDIR@/snomasks
-	@rm -f @MODULESDIR@/snomasks/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/snomasks/*.so @MODULESDIR@/snomasks
-	$(INSTALL) -m 0700 -d @MODULESDIR@/extbans
-	@rm -f @MODULESDIR@/extbans/*.so 1>/dev/null 2>&1
-	$(INSTALL) -m 0700 src/modules/extbans/*.so @MODULESDIR@/extbans
+	$(INSTALL) -m 0700 -d $(DESTDIR)@BINDIR@
+	$(INSTALL) -m 0700 src/ircd $(DESTDIR)@BINDIR@/unrealircd
+	$(INSTALL) -m 0700 -d $(DESTDIR)@DOCDIR@
+	$(INSTALL) -m 0600 doc/Authors doc/coding-guidelines doc/tao.of.irc $(DESTDIR)@DOCDIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@CONFDIR@
+	$(INSTALL) -m 0600 doc/conf/*.conf $(DESTDIR)@CONFDIR@
+	$(INSTALL) -m 0600 doc/conf/*.motd $(DESTDIR)@CONFDIR@
+	$(INSTALL) -m 0600 doc/conf/modules.sources.list $(DESTDIR)@CONFDIR@ ; \
+	$(INSTALL) -m 0700 unrealircd $(DESTDIR)@SCRIPTDIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@
+	@rm -f $(DESTDIR)@MODULESDIR@/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/*.so $(DESTDIR)@MODULESDIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/usermodes
+	@rm -f $(DESTDIR)@MODULESDIR@/usermodes/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/usermodes/*.so $(DESTDIR)@MODULESDIR@/usermodes
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/chanmodes
+	@rm -f $(DESTDIR)@MODULESDIR@/chanmodes/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/chanmodes/*.so $(DESTDIR)@MODULESDIR@/chanmodes
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/snomasks
+	@rm -f $(DESTDIR)@MODULESDIR@/snomasks/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/snomasks/*.so $(DESTDIR)@MODULESDIR@/snomasks
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/extbans
+	@rm -f $(DESTDIR)@MODULESDIR@/extbans/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/extbans/*.so $(DESTDIR)@MODULESDIR@/extbans
 	@#If the conf/ssl directory exists then rename it here to conf/tls
 	@#and add a symlink for backwards compatibility (so that f.e. certbot
 	@#doesn't randomly fail after an upgrade to U5).
-	-@if [ -d "@CONFDIR@/ssl" ] ; then \
-		mv "@CONFDIR@/ssl" "@CONFDIR@/tls" ; \
-		ln -s "@CONFDIR@/tls" "@CONFDIR@/ssl" ; \
+	-@if [ -d "$(DESTDIR)@CONFDIR@/ssl" ] ; then \
+		mv "$(DESTDIR)@CONFDIR@/ssl" "$(DESTDIR)@CONFDIR@/tls" ; \
+		ln -s "$(DESTDIR)@CONFDIR@/tls" "$(DESTDIR)@CONFDIR@/ssl" ; \
 	fi
-	$(INSTALL) -m 0700 -d @CONFDIR@/tls
-	$(INSTALL) -m 0600 doc/conf/tls/curl-ca-bundle.crt @CONFDIR@/tls
+	$(INSTALL) -m 0700 -d $(DESTDIR)@CONFDIR@/tls
+	$(INSTALL) -m 0600 doc/conf/tls/curl-ca-bundle.crt $(DESTDIR)@CONFDIR@/tls
 	@# delete modules/cap directory, to avoid confusing with U4 to U5 upgrades:
-	rm -rf @MODULESDIR@/cap
-	$(INSTALL) -m 0700 -d @MODULESDIR@/third
-	@rm -f @MODULESDIR@/third/*.so 1>/dev/null 2>&1
+	rm -rf $(DESTDIR)@MODULESDIR@/cap
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/third
+	@rm -f $(DESTDIR)@MODULESDIR@/third/*.so 1>/dev/null 2>&1
 	@#This step can fail with zero files, so we ignore exit status:
-	-$(INSTALL) -m 0700 src/modules/third/*.so @MODULESDIR@/third
-	$(INSTALL) -m 0700 -d @TMPDIR@
-	$(INSTALL) -m 0700 -d @CACHEDIR@
-	$(INSTALL) -m 0700 -d @PERMDATADIR@
-	$(INSTALL) -m 0700 -d @LOGDIR@
-	-@if [ ! -f "@CONFDIR@/tls/server.cert.pem" ] ; then \
-		$(INSTALL) -m 0600 server.req.pem @CONFDIR@/tls ; \
-		$(INSTALL) -m 0600 server.key.pem @CONFDIR@/tls ; \
-		$(INSTALL) -m 0600 server.cert.pem @CONFDIR@/tls ; \
+	-$(INSTALL) -m 0700 src/modules/third/*.so $(DESTDIR)@MODULESDIR@/third
+	$(INSTALL) -m 0700 -d $(DESTDIR)@TMPDIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@CACHEDIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@PERMDATADIR@
+	$(INSTALL) -m 0700 -d $(DESTDIR)@LOGDIR@
+	-@if [ ! -f "$(DESTDIR)@CONFDIR@/tls/server.cert.pem" ] ; then \
+		$(INSTALL) -m 0600 server.req.pem $(DESTDIR)@CONFDIR@/tls ; \
+		$(INSTALL) -m 0600 server.key.pem $(DESTDIR)@CONFDIR@/tls ; \
+		$(INSTALL) -m 0600 server.cert.pem $(DESTDIR)@CONFDIR@/tls ; \
 	fi
+	@rm -f $(DESTDIR)@SCRIPTDIR@/source
+	ln -s @BUILDDIR@ $(DESTDIR)@SCRIPTDIR@/source
 	@echo ''
 	@echo '* UnrealIRCd is now installed.'
 	
diff --git a/Makefile.windows b/Makefile.windows
@@ -269,6 +269,7 @@ DLL_FILES=SRC/MODULES/CLOAK.DLL \
  SRC/MODULES/EXTBANS/MSGBYPASS.DLL \
  SRC/MODULES/EXTBANS/TIMEDBAN.DLL \
  SRC/MODULES/EXTBANS/PARTMSG.DLL \
+ SRC/MODULES/EXTBANS/SECURITYGROUP.DLL \
  SRC/MODULES/ACCOUNT-NOTIFY.DLL \
  SRC/MODULES/MESSAGE-TAGS.DLL \
  SRC/MODULES/BATCH.DLL \
@@ -1018,6 +1019,9 @@ src/modules/extbans/timedban.dll: src/modules/extbans/timedban.c $(INCLUDES)
 src/modules/extbans/partmsg.dll: src/modules/extbans/partmsg.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/partmsg.c $(MODLFLAGS)
 
+src/modules/extbans/securitygroup.dll: src/modules/extbans/securitygroup.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) /Fosrc/modules/extbans/ /Fesrc/modules/extbans/ src/modules/extbans/securitygroup.c $(MODLFLAGS)
+
 src/modules/account-notify.dll: src/modules/account-notify.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/account-notify.c $(MODLFLAGS)
 
diff --git a/SECURITY.md b/SECURITY.md
@@ -0,0 +1,21 @@
+# Security Policy
+
+## Supported Versions
+* The latest *stable* release of the 5.x branch
+
+See [UnrealIRCd releases](https://www.unrealircd.org/docs/UnrealIRCd_releases) for information on older versions and End Of Life dates.
+
+## Reporting a Vulnerability
+
+Please report issues on the [bug tracker](https://bugs.unrealircd.org) and in the bug submit form **set the 'View Status' to 'private'**.
+
+Do not report security issues on the forums or in a public IRC channel such as #unreal-support.
+If you insist on e-mail then you can use syzop@unrealircd.org or security@unrealircd.org. Again, the bug tracker is preferred.
+
+If you are *unsure* if something is a security issue, then report it at the bug tracker as a 'private' bug anyway. Better safe than sorry.
+Do not ask around in public channels or forums.
+
+You should get a response or at least an acknowledgement soon. If you don't hear back within 24 hours, then please try to contact us again.
+
+## Full policy
+See https://www.unrealircd.org/docs/Policy:_Handling_of_security_issues for full information.
diff --git a/autoconf/m4/unreal.m4 b/autoconf/m4/unreal.m4
@@ -272,3 +272,43 @@ else
 	AC_MSG_RESULT([no])
 fi
 ])
+
+AC_DEFUN([CHECK_ASN1_TIME_diff],
+[
+AC_MSG_CHECKING([for ASN1_TIME_diff in SSL library])
+AC_LANG_PUSH(C)
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+AC_TRY_LINK([#include <openssl/ssl.h>],
+	[int one, two; ASN1_TIME_diff(&one, &two, NULL, NULL);],
+	has_function=1,
+	has_function=0)
+LIBS="$SAVE_LIBS"
+AC_LANG_POP(C)
+if test $has_function = 1; then
+	AC_MSG_RESULT([yes])
+	AC_DEFINE([HAS_ASN1_TIME_diff], [], [Define if ssl library has ASN1_TIME_diff])
+else
+	AC_MSG_RESULT([no])
+fi
+])
+
+AC_DEFUN([CHECK_X509_get0_notAfter],
+[
+AC_MSG_CHECKING([for X509_get0_notAfter in SSL library])
+AC_LANG_PUSH(C)
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+AC_TRY_LINK([#include <openssl/ssl.h>],
+	[X509_get0_notAfter(NULL);],
+	has_function=1,
+	has_function=0)
+LIBS="$SAVE_LIBS"
+AC_LANG_POP(C)
+if test $has_function = 1; then
+	AC_MSG_RESULT([yes])
+	AC_DEFINE([HAS_X509_get0_notAfter], [], [Define if ssl library has X509_get0_notAfter])
+else
+	AC_MSG_RESULT([no])
+fi
+])
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.7.
+# Generated by GNU Autoconf 2.69 for unrealircd 5.0.8.
 #
 # 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.7'
-PACKAGE_STRING='unrealircd 5.0.7'
+PACKAGE_VERSION='5.0.8'
+PACKAGE_STRING='unrealircd 5.0.8'
 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.7 to adapt to many kinds of systems.
+\`configure' configures unrealircd 5.0.8 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.7:";;
+     short | recursive ) echo "Configuration of unrealircd 5.0.8:";;
    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.7
+unrealircd configure 5.0.8
 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.7, which was
+It was created by unrealircd $as_me 5.0.8, 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="7"
+UNREAL_VERSION_MINOR="8"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -6528,6 +6528,100 @@ else
 $as_echo "no" >&6; }
 fi
 
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ASN1_TIME_diff in SSL library" >&5
+$as_echo_n "checking for ASN1_TIME_diff in SSL library... " >&6; }
+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
+
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <openssl/ssl.h>
+int
+main ()
+{
+int one, two; ASN1_TIME_diff(&one, &two, NULL, NULL);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  has_function=1
+else
+  has_function=0
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS="$SAVE_LIBS"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test $has_function = 1; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAS_ASN1_TIME_diff /**/" >>confdefs.h
+
+else
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for X509_get0_notAfter in SSL library" >&5
+$as_echo_n "checking for X509_get0_notAfter in SSL library... " >&6; }
+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
+
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <openssl/ssl.h>
+int
+main ()
+{
+X509_get0_notAfter(NULL);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  has_function=1
+else
+  has_function=0
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS="$SAVE_LIBS"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test $has_function = 1; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAS_X509_get0_notAfter /**/" >>confdefs.h
+
+else
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
 # Check whether --enable-dynamic-linking was given.
 if test "${enable_dynamic_linking+set}" = set; then :
   enableval=$enable_dynamic_linking; enable_dynamic_linking=$enableval
@@ -8398,7 +8492,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.7, which was
+This file was extended by unrealircd $as_me 5.0.8, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -8461,7 +8555,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.7
+unrealircd config.status 5.0.8
 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.7], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [5.0.8], [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=["7"]
+UNREAL_VERSION_MINOR=["8"]
 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
@@ -504,6 +504,8 @@ CHECK_SSL
 CHECK_SSL_CTX_SET1_CURVES_LIST
 CHECK_SSL_CTX_SET_MIN_PROTO_VERSION
 CHECK_SSL_CTX_SET_SECURITY_LEVEL
+CHECK_ASN1_TIME_diff
+CHECK_X509_get0_notAfter
 AC_ARG_ENABLE(dynamic-linking, [AS_HELP_STRING([--disable-dynamic-linking], [Make the IRCd statically link with shared objects rather than dynamically (noone knows if disabling dynamic linking actually does anything or not)])],
 	[enable_dynamic_linking=$enableval], [enable_dynamic_linking="yes"])
 AS_IF([test $enable_dynamic_linking = "yes"],
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 5.0.7
+                                for UnrealIRCd 5.0.8
                                     
 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,72 @@
-UnrealIRCd 5.0.7 Release Notes
+UnrealIRCd 5.0.8 Release Notes
 ===============================
 
+The main purpose of this release is to enhance the
+[reputation](https://www.unrealircd.org/docs/Reputation_score)
+functionality. There have also been some other changes and minor
+bug fixes. For more information, see below.
+
+Enhancements:
+* Support for [security groups](https://www.unrealircd.org/docs/Security-group_block),
+  of which four groups always exist by default: known-users, unknown-users,
+  tls-users and tls-and-known-users.
+* New extended ban ```~G:securitygroupname```. Typical usage would be
+  ```MODE #chan +b ~G:unknown-users``` which will ban all users from the
+  channel that are not identified to services and have a reputation
+  score below 25 (by default). The exact settings can be tweaked in the
+  [security group block](https://www.unrealircd.org/docs/Security-group_block).
+* The reputation command (IRCOp-only) has been extended to make it
+  easier to look for potential troublemakers:
+  * ```REPUTATION Nick``` shows reputation about the nick name
+  * ```REPUTATION IP``` shows reputation about the IP address
+  * ```REPUTATION #channel``` lists users in channel with their reputation score
+  * ```REPUTATION <NN``` lists users with reputation scores below value NN
+* Only send the first 1000 matches on ```STATS gline``` or a
+  similar command. This to prevent the IRCOp from being flooded off.
+  This value can be changed via
+  [set::max-stats-matches](https://www.unrealircd.org/docs/Set_block#set::max-stats-matches)
+* Warn when the SSL/TLS server certificate is expired or expires soon
+  (within 7 days).
+* New option allow::options::reject-on-auth-failure if you want to
+  stop matching on a passworded allow block, see the
+  [allow password documentation](https://www.unrealircd.org/docs/Allow_block#password)
+  for more information. Note that most people won't use this.
+
+Fixes:
+* The ```WHO``` command searched on nick name even if it was told
+  to search on a specific account name via WHOX options.
+* Some typos in the Config script and a warning
+* Counting clients twice in some circumstances
+
+Changes:
+* Support for $(DESTDIR) in 'make install' if packaging for a distro
+* Mention the ban reason in Q-line server notices
+* Add self-test to module manager and improve the error message in case
+  the IRCd source directory does not exist.
+* Print out a more helpful error if you run the unrealircd binary
+  rather than the unrealircd script with an argument like 'mkpasswd' etc.
+* On *NIX create a symlink 'source' to the UnrealIRCd source
+
+Module coders / Developers:
+* The [Doxygen module API docs](https://www.unrealircd.org/api/5/index.html)
+  have been improved, in particular the 
+  [Hook API](https://www.unrealircd.org/api/5/group__HookAPI.html)
+  is now 100% documented.
+
+Reminder: UnrealIRCd 4 is no longer supported
+----------------------------------------------
+
+UnrealIRCd 4.x is [no longer supported](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
+Admins must upgrade to UnrealIRCd 5.
+
+Upgrading from 4.x to 5.x?
+Then check out the *UnrealIRCd 5* release notes [further down](#unrealircd-5).
+Or, at the very least, check out
+[Upgrading from 4.x](https://www.unrealircd.org/docs/Upgrading_from_4.x).
+
+UnrealIRCd 5.0.7
+-----------------
+
 UnrealIRCd 5.0.7 consists mainly of fixes for the 5.x stable series,
 with some minor enhancements.
 
@@ -34,17 +100,6 @@ Module coders / Developers:
 * No changes, only some small additions to the
 [Doxygen module API docs](https://www.unrealircd.org/api/5/index.html)
 
-Reminder: UnrealIRCd 4 is End Of Life
----------------------------------------
-
-All support for the previous series, UnrealIRCd 4.x, will stop after
-[December 31, 2020](https://www.unrealircd.org/docs/UnrealIRCd_4_EOL).
-If you haven't upgraded yet, do so soon!
-
-Upgrading from 4.x to 5.x?
-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.6
 -----------------
 
diff --git a/extras/build-tests/windows/build.bat b/extras/build-tests/windows/build.bat
@@ -37,6 +37,11 @@ rem And we re-run the exact same command:
 call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat
 if %ERRORLEVEL% NEQ 0 EXIT /B 1
 
+rem Compile dependencies for unrealircd-tests -- this doesn't belong here though..
+curl -fsS -o src\modules\third\fakereputation.c https://raw.githubusercontent.com/unrealircd/unrealircd-tests/master/serverconfig/unrealircd/modules/fakereputation.c
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat CUSTOMMODULE MODULEFILE=fakereputation
+if %ERRORLEVEL% NEQ 0 EXIT /B 1
+
 rem Convert c:\dev to c:\projects\unrealircd-5-libs
 rem TODO: should use environment variable in innosetup script?
 sed -i "s/c:\\dev\\unrealircd-5-libs/c:\\projects\\unrealircd-5-libs/gi" src\windows\unrealinst.iss
diff --git a/extras/doxygen/Developers.md b/extras/doxygen/Developers.md
@@ -8,10 +8,11 @@ Here you should be able to find a lot of information on the data structures
 and functions available to you when coding for UnrealIRCd.
 
 ## Wiki documentation ##
-* Be sure to check the [Module API](https://www.unrealircd.org/docs/Dev:Module_API) article on the wiki, which currently provides a better overview of the module API.
+* Be sure to check the [Module API](https://www.unrealircd.org/docs/Dev:Module_API) article on the wiki
+  as well, which provides a better *overview* of the module API
 
 ## Doxygen docs ##
+* [Functions and structs ordered by purpose](modules.html) - **this contains most of the module API!**
 * [The most common structs](group__CommonStructs.html) - like Client, User, Server, Channel, etc.
 * [All structs](classes.html) - in a simple alphabetical index
-* [Functions and structs ordered by purpose](modules.html) - this is work in progress and still needs expansion!
 * [Browse by source file](dir_68267d1309a1af8e8297ef4c3efbcdba.html) - see all src/*.c files and their (documented) functions.
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.7
+PROJECT_NUMBER         = 5.0.8
 
 # 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/include/h.h b/include/h.h
@@ -90,6 +90,7 @@ extern MODVAR ConfigItem_alias		*conf_alias;
 extern MODVAR ConfigItem_include	*conf_include;
 extern MODVAR ConfigItem_help		*conf_help;
 extern MODVAR ConfigItem_offchans	*conf_offchans;
+extern MODVAR SecurityGroup		*securitygroups;
 extern void		completed_connection(int, int, void *);
 extern void clear_unknown();
 extern EVENT(e_unload_module_delayed);
@@ -163,7 +164,6 @@ extern MODVAR struct list_head global_server_list;
 extern MODVAR struct list_head dead_list;
 extern RealCommand *find_command(char *cmd, int flags);
 extern RealCommand *find_command_simple(char *cmd);
-extern Channel *find_channel(char *, Channel *);
 extern Membership *find_membership_link(Membership *lp, Channel *ptr);
 extern Member *find_member_link(Member *, Client *);
 extern int remove_user_from_channel(Client *, Channel *);
@@ -336,7 +336,7 @@ extern void del_queries(char *);
 #define WATCH_HASH_TABLE_SIZE 32768
 #define WHOWAS_HASH_TABLE_SIZE 32768
 #define THROTTLING_HASH_TABLE_SIZE 8192
-#define find_channel hash_find_channel
+#define hash_find_channel find_channel
 extern uint64_t siphash(const char *in, const char *k);
 extern uint64_t siphash_raw(const char *in, size_t len, const char *k);
 extern uint64_t siphash_nocase(const char *in, const char *k);
@@ -359,7 +359,7 @@ extern Channel *hash_get_chan_bucket(uint64_t);
 extern Client *hash_find_client(const char *, Client *);
 extern Client *hash_find_id(const char *, Client *);
 extern Client *hash_find_nickatserver(const char *, Client *);
-extern Channel *hash_find_channel(char *name, Channel *channel);
+extern Channel *find_channel(char *name, Channel *channel);
 extern Client *hash_find_server(const char *, Client *);
 extern struct MODVAR ThrottlingBucket *ThrottlingHash[THROTTLING_HASH_TABLE_SIZE];
 
@@ -460,6 +460,7 @@ extern void count_memory(Client *cptr, char *nick);
 extern void list_scache(Client *client);
 extern char *oflagstr(long oflag);
 extern int rehash(Client *client, int sig);
+extern void s_die();
 extern int match_simple(const char *mask, const char *name);
 extern int match_esc(const char *mask, const char *name);
 extern int add_listener(ConfigItem_listen *conf);
@@ -549,10 +550,6 @@ extern void *safe_alloc(size_t size);
 extern char *our_strdup(const char *str);
 extern char *our_strldup(const char *str, size_t max);
 
-extern MODFUNC char  *tls_get_cipher(SSL *ssl);
-extern TLSOptions *get_tls_options_for_client(Client *acptr);
-extern int outdated_tls_client(Client *acptr);
-extern char *outdated_tls_client_build_string(char *pattern, Client *acptr);
 extern long config_checkval(char *value, unsigned short flags);
 extern void config_status(FORMAT_STRING(const char *format), ...) __attribute__((format(printf,1,2)));
 extern void init_random();
@@ -701,7 +698,7 @@ extern MODVAR int (*find_shun)(Client *cptr);
 extern MODVAR int (*find_spamfilter_user)(Client *client, int flags);
 extern MODVAR TKL *(*find_qline)(Client *cptr, char *nick, int *ishold);
 extern MODVAR TKL *(*find_tkline_match_zap)(Client *cptr);
-extern MODVAR void (*tkl_stats)(Client *cptr, int type, char *para);
+extern MODVAR void (*tkl_stats)(Client *cptr, int type, char *para, int *cnt);
 extern MODVAR void (*tkl_sync)(Client *client);
 extern MODVAR void (*cmd_tkl)(Client *client, MessageTag *recv_mtags, int parc, char *parv[]);
 extern MODVAR int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
@@ -753,6 +750,25 @@ extern MODVAR void (*labeled_response_force_end)(void);
 extern MODVAR void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment);
 /* /Efuncs */
 
+/* SSL/TLS functions */
+extern int early_init_ssl();
+extern int init_ssl();
+extern int ssl_handshake(Client *);   /* Handshake the accpeted con.*/
+extern int ssl_client_handshake(Client *, ConfigItem_link *); /* and the initiated con.*/
+extern int ircd_SSL_accept(Client *acptr, int fd);
+extern int ircd_SSL_connect(Client *acptr, int fd);
+extern int SSL_smart_shutdown(SSL *ssl);
+extern void ircd_SSL_client_handshake(int, int, void *);
+extern void SSL_set_nonblocking(SSL *s);
+extern SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server);
+extern MODFUNC char  *tls_get_cipher(SSL *ssl);
+extern TLSOptions *get_tls_options_for_client(Client *acptr);
+extern int outdated_tls_client(Client *acptr);
+extern char *outdated_tls_client_build_string(char *pattern, Client *acptr);
+extern int check_certificate_expiry_ctx(SSL_CTX *ctx, char **errstr);
+extern EVENT(tls_check_expiry);
+/* End of SSL/TLS functions */
+
 extern void parse_message_tags_default_handler(Client *client, char **str, MessageTag **mtag_list);
 extern char *mtags_to_string_default_handler(MessageTag *m, Client *client);
 extern void *labeled_response_save_context_default_handler(void);
@@ -793,6 +809,8 @@ extern char *cm_getparameter_ex(void **p, char mode);
 extern void cm_putparameter_ex(void **p, char mode, char *str);
 extern void cm_freeparameter_ex(void **p, char mode, char *str);
 extern int file_exists(char *file);
+extern time_t get_file_time(char *fname);
+extern long get_file_size(char *fname);
 extern void free_motd(MOTDFile *motd); /* s_serv.c */
 extern void fix_timers(void);
 extern char *chfl_to_sjoin_symbol(int s);
@@ -974,3 +992,11 @@ 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;
+extern int security_group_valid_name(char *name);
+extern int security_group_exists(char *name);
+extern SecurityGroup *add_security_group(char *name, int order);
+extern SecurityGroup *find_security_group(char *name);
+extern void free_security_group(SecurityGroup *s);
+extern void set_security_group_defaults(void);
+extern int user_allowed_by_security_group(Client *client, SecurityGroup *s);
+extern int user_allowed_by_security_group_name(Client *client, char *secgroupname);
diff --git a/include/list.h b/include/list.h
@@ -430,11 +430,62 @@ SINLINE void list_splice_tail_init(struct list_head *list,
 	     pos != (head); \
 	     pos = n, n = pos->prev)
 
-/**
- * list_for_each_entry	-	iterate over list of given type
- * @pos:	the type * to use as a loop cursor.
- * @head:	the head for your list.
- * @member:	the name of the list_struct within the struct.
+/** Walk through client lists (with examples).
+ * @param	pos	The variable to use as a loop cursor
+ * @param	head	The head of your list
+ * @param	member	The name of the list_struct within the struct.
+ * @ingroup ListFunctions
+ * @section Examples
+ * @subsection client_list List all clients
+ * @code
+ * CMD_FUNC(cmd_listallclients)
+ * {
+ *     Client *acptr;
+ *     sendnotice(client, "List of all clients:");
+ *     list_for_each_entry(acptr, &client_list, client_node)
+ *         sendnotice(client, "Client %s", acptr->name);
+ * }
+ * @endcode
+ * @subsection lclient_list List all LOCAL clients
+ * @code
+ * CMD_FUNC(cmd_listalllocalclients)
+ * {
+ *     Client *acptr;
+ *     sendnotice(client, "List of all local clients:");
+ *     list_for_each_entry(acptr, &lclient_list, lclient_node)
+ *         sendnotice(client, "Client %s", acptr->name);
+ * }
+ * @endcode
+ * @subsection global_server_list List all servers
+ * @code
+ * CMD_FUNC(cmd_listallservers)
+ * {
+ *     Client *acptr;
+ *     sendnotice(client, "List of all servers:");
+ *     list_for_each_entry(acptr, &global_server_list, client_node)
+ *         sendnotice(client, "Server %s", acptr->name);
+ * }
+ * @endcode
+ * @subsection server_list List all LOCALLY connected servers
+ * @code
+ * CMD_FUNC(cmd_listallservers)
+ * {
+ *     Client *acptr;
+ *     sendnotice(client, "List of all LOCAL servers:");
+ *     list_for_each_entry(acptr, &server_list, special_node)
+ *         sendnotice(client, "Server %s", acptr->name);
+ * }
+ * @endcode
+ * @subsection oper_list List all LOCALLY connected IRCOps
+ * @code
+ * CMD_FUNC(cmd_listlocalircops)
+ * {
+ *     Client *acptr;
+ *     sendnotice(client, "List of all LOCAL IRCOps:");
+ *     list_for_each_entry(acptr, &oper_list, special_node)
+ *         sendnotice(client, "User %s", acptr->name);
+ * }
+ * @endcode
  */
 #define list_for_each_entry(pos, head, member)				\
 	for (pos = list_entry((head)->next, typeof(*pos), member);	\
@@ -514,12 +565,16 @@ SINLINE void list_splice_tail_init(struct list_head *list,
 	for (; &pos->member != (head);	\
 	     pos = list_entry(pos->member.next, typeof(*pos), member))
 
-/**
- * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
- * @pos:	the type * to use as a loop cursor.
- * @n:		another type * to use as temporary storage
- * @head:	the head for your list.
- * @member:	the name of the list_struct within the struct.
+/** Walk through client lists - special 'safe' version.
+ * This is a special version, in case clients are removed from the list
+ * while the list is iterated. It is unlikely that you need to use this
+ * from modules, so use list_for_each_entry() instead.
+ * Examples are also in list_for_each_entry().
+ * @param	pos	The variable to use as a loop cursor
+ * @param	n	Variable to be used for temporary storage
+ * @param	head	The head of your list
+ * @param	member	The name of the list_struct within the struct.
+ * @ingroup ListFunctions
  */
 #define list_for_each_entry_safe(pos, n, head, member)			\
 	for (pos = list_entry((head)->next, typeof(*pos), member),	\
diff --git a/include/modules.h b/include/modules.h
@@ -206,7 +206,7 @@ typedef struct {
 	/** unique flag (like 0x10) */
 	Cmode_t		mode;
 
-	/** Number of paramters (1 or 0) */
+	/** Number of parameters (1 or 0) */
 	int			paracount;
 
 	/** Check access or parameter of the channel mode.
@@ -911,111 +911,219 @@ extern int LoadPersistentLongX(ModuleInfo *modinfo, char *varshortname, long *va
 extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long var);
 #define SavePersistentLong(modinfo, var) SavePersistentLongX(modinfo, #var, var)
 
+/** Hooks trigger on "events", such as a new user connecting or joining a channel,
+ * see https://www.unrealircd.org/docs/Dev:Hook_API for background info.
+ * You are suggested to use CTRL+F on this page to search for any useful hook,
+ * see also the example session on how to find and use a hook at
+ * https://www.unrealircd.org/docs/Dev:Hook_API#Example_session_finding_and_using_a_hook
+ *
+ * @defgroup HookAPI Hook API
+ * @{
+ */
+
 /* Hook types */
-#define HOOKTYPE_LOCAL_QUIT	1
-#define HOOKTYPE_LOCAL_NICKCHANGE 2
-#define HOOKTYPE_LOCAL_CONNECT 3
-#define HOOKTYPE_REHASHFLAG 4
-#define HOOKTYPE_PRE_LOCAL_PART 5
-#define HOOKTYPE_CONFIGPOSTTEST 6
-#define HOOKTYPE_REHASH 7
-#define HOOKTYPE_PRE_LOCAL_CONNECT 8
-#define HOOKTYPE_PRE_LOCAL_QUIT 9
-#define HOOKTYPE_SERVER_CONNECT 11
-#define HOOKTYPE_SERVER_QUIT 12
-#define HOOKTYPE_STATS 13
-#define HOOKTYPE_LOCAL_JOIN 14
-#define HOOKTYPE_CONFIGTEST 15
-#define HOOKTYPE_CONFIGRUN 16
-/* If you ever change the number of usermsg & chanmsg, notify Syzop first, kthx! ;p */
-#define HOOKTYPE_USERMSG 17
-#define HOOKTYPE_CHANMSG 18
-#define HOOKTYPE_LOCAL_PART 19
-#define HOOKTYPE_LOCAL_KICK 20
-#define HOOKTYPE_LOCAL_CHANMODE 21
-#define HOOKTYPE_LOCAL_TOPIC 22
-#define HOOKTYPE_LOCAL_OPER 23
-#define HOOKTYPE_UNKUSER_QUIT 24
-#define HOOKTYPE_LOCAL_PASS 25
-#define HOOKTYPE_REMOTE_CONNECT 26
-#define HOOKTYPE_REMOTE_QUIT 27
-#define HOOKTYPE_PRE_LOCAL_JOIN 28
-#define HOOKTYPE_PRE_LOCAL_KICK 29
-#define HOOKTYPE_PRE_LOCAL_TOPIC 30
-#define HOOKTYPE_REMOTE_NICKCHANGE 31
-#define HOOKTYPE_CHANNEL_CREATE 32
-#define HOOKTYPE_CHANNEL_DESTROY 33
-#define HOOKTYPE_REMOTE_CHANMODE 34
-#define HOOKTYPE_TKL_EXCEPT 35
-#define HOOKTYPE_UMODE_CHANGE 36
-#define HOOKTYPE_TOPIC 37
-#define HOOKTYPE_REHASH_COMPLETE 38
-#define HOOKTYPE_TKL_ADD 39
-#define HOOKTYPE_TKL_DEL 40
-#define HOOKTYPE_LOCAL_KILL 41
-#define HOOKTYPE_LOG 42
-#define HOOKTYPE_REMOTE_JOIN 43
-#define HOOKTYPE_REMOTE_PART 44
-#define HOOKTYPE_REMOTE_KICK 45
-#define HOOKTYPE_LOCAL_SPAMFILTER 46
-#define HOOKTYPE_SILENCED 47
-#define HOOKTYPE_POST_SERVER_CONNECT 48
-#define HOOKTYPE_RAWPACKET_IN 49
-#define HOOKTYPE_PACKET 51
-#define HOOKTYPE_HANDSHAKE 52
-#define HOOKTYPE_AWAY 53
-#define HOOKTYPE_INVITE 55
-#define HOOKTYPE_CAN_JOIN 56
-#define HOOKTYPE_CAN_SEND_TO_CHANNEL 57
-#define HOOKTYPE_CAN_KICK 58
-#define HOOKTYPE_FREE_CLIENT 59
-#define HOOKTYPE_FREE_USER 60
-#define HOOKTYPE_PRE_CHANMSG 61
-#define HOOKTYPE_KNOCK 63
-#define HOOKTYPE_MODECHAR_ADD 64
-#define HOOKTYPE_MODECHAR_DEL 65
-#define HOOKTYPE_CAN_JOIN_LIMITEXCEEDED 67
-#define HOOKTYPE_VISIBLE_IN_CHANNEL 68
-#define HOOKTYPE_PRE_LOCAL_CHANMODE 69
-#define HOOKTYPE_PRE_REMOTE_CHANMODE 70
-#define HOOKTYPE_JOIN_DATA 71
-#define HOOKTYPE_PRE_KNOCK 72
-#define HOOKTYPE_PRE_INVITE 73
-#define HOOKTYPE_OPER_INVITE_BAN 74
-#define HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL 75
-#define HOOKTYPE_CHAN_PERMIT_NICK_CHANGE 76
-#define HOOKTYPE_IS_CHANNEL_SECURE 77
-#define HOOKTYPE_SEND_CHANNEL 78
-#define HOOKTYPE_CHANNEL_SYNCED 79
-#define HOOKTYPE_CAN_SAJOIN 80
-#define HOOKTYPE_WHOIS 81
-#define HOOKTYPE_CHECK_INIT 82
-#define HOOKTYPE_WHO_STATUS 83
-#define HOOKTYPE_MODE_DEOP 84
-#define HOOKTYPE_PRE_KILL 85
-#define HOOKTYPE_SEE_CHANNEL_IN_WHOIS 86
-#define HOOKTYPE_DCC_DENIED 87
-#define HOOKTYPE_SERVER_HANDSHAKE_OUT 88
-#define HOOKTYPE_SERVER_SYNCED	89
-#define HOOKTYPE_SECURE_CONNECT 90
-#define HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION 91
-#define HOOKTYPE_REQUIRE_SASL 92
-#define HOOKTYPE_SASL_CONTINUATION 93
-#define HOOKTYPE_SASL_RESULT 94
-#define HOOKTYPE_PLACE_HOST_BAN 95
-#define HOOKTYPE_FIND_TKLINE_MATCH 96
-#define HOOKTYPE_WELCOME 97
-#define HOOKTYPE_PRE_COMMAND 98
-#define HOOKTYPE_POST_COMMAND 99
-#define HOOKTYPE_NEW_MESSAGE 100
-#define HOOKTYPE_IS_HANDSHAKE_FINISHED 101
-#define HOOKTYPE_PRE_LOCAL_QUIT_CHAN 102
-#define HOOKTYPE_IDENT_LOOKUP 103
-#define HOOKTYPE_CONFIGRUN_EX 104
-#define HOOKTYPE_CAN_SEND_TO_USER 105
-#define HOOKTYPE_SERVER_SYNC 106
-#define HOOKTYPE_ACCOUNT_LOGIN 107
-#define HOOKTYPE_CLOSE_CONNECTION 108
+/** See hooktype_pre_local_connect() */
+#define HOOKTYPE_PRE_LOCAL_CONNECT	1
+/** See hooktype_local_connect() */
+#define HOOKTYPE_LOCAL_CONNECT	2
+/** See hooktype_remote_connect() */
+#define HOOKTYPE_REMOTE_CONNECT	3
+/** See hooktype_pre_local_quit() */
+#define HOOKTYPE_PRE_LOCAL_QUIT	4
+/** See hooktype_local_quit() */
+#define HOOKTYPE_LOCAL_QUIT	5
+/** See hooktype_remote_quit() */
+#define HOOKTYPE_REMOTE_QUIT	6
+/** See hooktype_unkuser_quit() */
+#define HOOKTYPE_UNKUSER_QUIT	7
+/** See hooktype_server_connect() */
+#define HOOKTYPE_SERVER_CONNECT	8
+/** See hooktype_server_handshake_out() */
+#define HOOKTYPE_SERVER_HANDSHAKE_OUT	9
+/** See hooktype_server_sync() */
+#define HOOKTYPE_SERVER_SYNC	10
+/** See hooktype_post_server_connect() */
+#define HOOKTYPE_POST_SERVER_CONNECT	11
+/** See hooktype_server_synced() */
+#define HOOKTYPE_SERVER_SYNCED	12
+/** See hooktype_server_quit() */
+#define HOOKTYPE_SERVER_QUIT	13
+/** See hooktype_local_nickchange() */
+#define HOOKTYPE_LOCAL_NICKCHANGE	14
+/** See hooktype_remote_nickchange() */
+#define HOOKTYPE_REMOTE_NICKCHANGE	15
+/** See hooktype_can_join() */
+#define HOOKTYPE_CAN_JOIN	16
+/** See hooktype_pre_local_join() */
+#define HOOKTYPE_PRE_LOCAL_JOIN	17
+/** See hooktype_local_join() */
+#define HOOKTYPE_LOCAL_JOIN	18
+/** See hooktype_remote_join() */
+#define HOOKTYPE_REMOTE_JOIN	19
+/** See hooktype_pre_local_part() */
+#define HOOKTYPE_PRE_LOCAL_PART	20
+/** See hooktype_local_part() */
+#define HOOKTYPE_LOCAL_PART	21
+/** See hooktype_remote_part() */
+#define HOOKTYPE_REMOTE_PART	22
+/** See hooktype_pre_local_kick() */
+#define HOOKTYPE_PRE_LOCAL_KICK	23
+/** See hooktype_can_kick() */
+#define HOOKTYPE_CAN_KICK	24
+/** See hooktype_local_kick() */
+#define HOOKTYPE_LOCAL_KICK	25
+/** See hooktype_remote_kick() */
+#define HOOKTYPE_REMOTE_KICK	26
+/** See hooktype_pre_chanmsg() */
+#define HOOKTYPE_PRE_CHANMSG	28
+/** See hooktype_can_send_to_user() */
+#define HOOKTYPE_CAN_SEND_TO_USER	29
+/** See hooktype_can_send_to_channel() */
+#define HOOKTYPE_CAN_SEND_TO_CHANNEL	30
+/** See hooktype_usermsg() */
+#define HOOKTYPE_USERMSG	31
+/** See hooktype_chanmsg() */
+#define HOOKTYPE_CHANMSG	32
+/** See hooktype_pre_local_topic() */
+#define HOOKTYPE_PRE_LOCAL_TOPIC	33
+/** See hooktype_topic() */
+#define HOOKTYPE_TOPIC	35
+/** See hooktype_pre_local_chanmode() */
+#define HOOKTYPE_PRE_LOCAL_CHANMODE	36
+/** See hooktype_pre_remote_chanmode() */
+#define HOOKTYPE_PRE_REMOTE_CHANMODE	37
+/** See hooktype_local_chanmode() */
+#define HOOKTYPE_LOCAL_CHANMODE	38
+/** See hooktype_remote_chanmode() */
+#define HOOKTYPE_REMOTE_CHANMODE	39
+/** See hooktype_modechar_del() */
+#define HOOKTYPE_MODECHAR_DEL	40
+/** See hooktype_modechar_add() */
+#define HOOKTYPE_MODECHAR_ADD	41
+/** See hooktype_away() */
+#define HOOKTYPE_AWAY	42
+/** See hooktype_pre_invite() */
+#define HOOKTYPE_PRE_INVITE	43
+/** See hooktype_invite() */
+#define HOOKTYPE_INVITE	44
+/** See hooktype_pre_knock() */
+#define HOOKTYPE_PRE_KNOCK	45
+/** See hooktype_knock() */
+#define HOOKTYPE_KNOCK	46
+/** See hooktype_whois() */
+#define HOOKTYPE_WHOIS	47
+/** See hooktype_who_status() */
+#define HOOKTYPE_WHO_STATUS	48
+/** See hooktype_pre_kill() */
+#define HOOKTYPE_PRE_KILL	49
+/** See hooktype_local_kill() */
+#define HOOKTYPE_LOCAL_KILL	50
+/** See hooktype_rehashflag() */
+#define HOOKTYPE_REHASHFLAG	51
+/** See hooktype_configposttest() */
+#define HOOKTYPE_CONFIGPOSTTEST	52
+/** See hooktype_rehash() */
+#define HOOKTYPE_REHASH	53
+/** See hooktype_rehash_complete() */
+#define HOOKTYPE_REHASH_COMPLETE	54
+/** See hooktype_configtest() */
+#define HOOKTYPE_CONFIGTEST	55
+/** See hooktype_configrun() */
+#define HOOKTYPE_CONFIGRUN	56
+/** See hooktype_configrun_ex() */
+#define HOOKTYPE_CONFIGRUN_EX	57
+/** See hooktype_stats() */
+#define HOOKTYPE_STATS	58
+/** See hooktype_local_oper() */
+#define HOOKTYPE_LOCAL_OPER	59
+/** See hooktype_local_pass() */
+#define HOOKTYPE_LOCAL_PASS	60
+/** See hooktype_channel_create() */
+#define HOOKTYPE_CHANNEL_CREATE	61
+/** See hooktype_channel_destroy() */
+#define HOOKTYPE_CHANNEL_DESTROY	62
+/** See hooktype_tkl_except() */
+#define HOOKTYPE_TKL_EXCEPT	63
+/** See hooktype_umode_change() */
+#define HOOKTYPE_UMODE_CHANGE	64
+/** See hooktype_tkl_add() */
+#define HOOKTYPE_TKL_ADD	65
+/** See hooktype_tkl_del() */
+#define HOOKTYPE_TKL_DEL	66
+/** See hooktype_log() */
+#define HOOKTYPE_LOG	67
+/** See hooktype_local_spamfilter() */
+#define HOOKTYPE_LOCAL_SPAMFILTER	68
+/** See hooktype_silenced() */
+#define HOOKTYPE_SILENCED	69
+/** See hooktype_rawpacket_in() */
+#define HOOKTYPE_RAWPACKET_IN	70
+/** See hooktype_packet() */
+#define HOOKTYPE_PACKET	71
+/** See hooktype_handshake() */
+#define HOOKTYPE_HANDSHAKE	72
+/** See hooktype_free_client() */
+#define HOOKTYPE_FREE_CLIENT	73
+/** See hooktype_free_user() */
+#define HOOKTYPE_FREE_USER	74
+/** See hooktype_can_join_limitexceeded() */
+#define HOOKTYPE_CAN_JOIN_LIMITEXCEEDED	75
+/** See hooktype_visible_in_channel() */
+#define HOOKTYPE_VISIBLE_IN_CHANNEL	76
+/** See hooktype_see_channel_in_whois() */
+#define HOOKTYPE_SEE_CHANNEL_IN_WHOIS	77
+/** See hooktype_join_data() */
+#define HOOKTYPE_JOIN_DATA	78
+/** See hooktype_oper_invite_ban() */
+#define HOOKTYPE_OPER_INVITE_BAN	79
+/** See hooktype_view_topic_outside_channel() */
+#define HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL	80
+/** See hooktype_chan_permit_nick_change() */
+#define HOOKTYPE_CHAN_PERMIT_NICK_CHANGE	81
+/** See hooktype_is_channel_secure() */
+#define HOOKTYPE_IS_CHANNEL_SECURE	82
+/** See hooktype_channel_synced() */
+#define HOOKTYPE_CHANNEL_SYNCED	83
+/** See hooktype_can_sajoin() */
+#define HOOKTYPE_CAN_SAJOIN	84
+/** See hooktype_check_init() */
+#define HOOKTYPE_CHECK_INIT	85
+/** See hooktype_mode_deop() */
+#define HOOKTYPE_MODE_DEOP	86
+/** See hooktype_dcc_denied() */
+#define HOOKTYPE_DCC_DENIED	87
+/** See hooktype_secure_connect() */
+#define HOOKTYPE_SECURE_CONNECT	88
+/** See hooktype_can_bypass_channel_message_restriction() */
+#define HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION	89
+/** See hooktype_require_sasl() */
+#define HOOKTYPE_REQUIRE_SASL	90
+/** See hooktype_sasl_continuation() */
+#define HOOKTYPE_SASL_CONTINUATION	91
+/** See hooktype_sasl_result() */
+#define HOOKTYPE_SASL_RESULT	92
+/** See hooktype_place_host_ban() */
+#define HOOKTYPE_PLACE_HOST_BAN	93
+/** See hooktype_find_tkline_match() */
+#define HOOKTYPE_FIND_TKLINE_MATCH	94
+/** See hooktype_welcome() */
+#define HOOKTYPE_WELCOME	95
+/** See hooktype_pre_command() */
+#define HOOKTYPE_PRE_COMMAND	96
+/** See hooktype_post_command() */
+#define HOOKTYPE_POST_COMMAND	97
+/** See hooktype_new_message() */
+#define HOOKTYPE_NEW_MESSAGE	98
+/** See hooktype_is_handshake_finished() */
+#define HOOKTYPE_IS_HANDSHAKE_FINISHED	99
+/** See hooktype_pre_local_quit_chan() */
+#define HOOKTYPE_PRE_LOCAL_QUIT_CHAN	100
+/** See hooktype_ident_lookup() */
+#define HOOKTYPE_IDENT_LOOKUP	101
+/** See hooktype_account_login() */
+#define HOOKTYPE_ACCOUNT_LOGIN	102
+/** See hooktype_close_connection() */
+#define HOOKTYPE_CLOSE_CONNECTION	103
 
 /* Adding a new hook here?
  * 1) Add the #define HOOKTYPE_.... with a new number
@@ -1025,111 +1133,931 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va
  */
 
 /* Hook prototypes */
+/** Called when a local user connects, allows pausing or rejecting the user (function prototype for HOOKTYPE_PRE_LOCAL_CONNECT).
+ * @param client		The client
+ * @retval HOOK_DENY		Stop the connection (hold/pause it).
+ * @retval HOOK_ALLOW		Allow the connection (stop processing other modules)
+ * @retval HOOK_CONTINUE	Allow the connection, unless another module blocks it.
+ */
+int hooktype_pre_local_connect(Client *client);
+
+/** Called when a local user connects (function prototype for HOOKTYPE_LOCAL_CONNECT).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_connect(Client *client);
+
+/** Called when a remote user connects (function prototype for HOOKTYPE_REMOTE_CONNECT).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_connect(Client *client);
-int hooktype_server_connect(Client *client);
-int hooktype_server_sync(Client *client);
-int hooktype_post_server_connect(Client *client);
+
+/** Called when a local user disconnects, allows changing the quit/disconnect reason (function prototype for HOOKTYPE_PRE_LOCAL_QUIT).
+ * @param client		The client
+ * @param client		The quit/disconnect reason
+ * @return The quit reason (you may also return 'comment' if it should be unchanged) or NULL for an empty reason.
+ */
 char *hooktype_pre_local_quit(Client *client, char *comment);
+
+/** Called when a local user quits or otherwise disconnects (function prototype for HOOKTYPE_PRE_LOCAL_QUIT).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the quit
+ * @param comment       	The quit/exit reason
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_quit(Client *client, MessageTag *mtags, char *comment);
+
+/** Called when a remote user qutis or otherwise disconnects (function prototype for HOOKTYPE_REMOTE_QUIT).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the quit
+ * @param comment       	The quit/exit reason
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_quit(Client *client, MessageTag *mtags, char *comment);
+
+/** Called when an unregistered user disconnects, so before the user was fully online (function prototype for HOOKTYPE_UNKUSER_QUIT).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the quit
+ * @param comment       	The quit/exit reason
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_unkuser_quit(Client *client, MessageTag *mtags, char *comment);
-int hooktype_pre_local_connect(Client *client);
+
+/** Called when a local or remote server connects / links in (function prototype for HOOKTYPE_SERVER_CONNECT).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_server_connect(Client *client);
+
+/** Called very early when doing an outgoing server connect (function prototype for HOOKTYPE_SERVER_HANDSHAKE_OUT).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_server_handshake_out(Client *client);
+
+/** Called on new locally connected server, in or out, after all users/channels/TKLs/etc have been synced, but before EOS (function prototype for HOOKTYPE_SERVER_SYNC).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_server_sync(Client *client);
+
+/** Called when a local or remote server connects / links in, but only after EOS (End Of Sync) has been received or sent (function prototype for HOOKTYPE_POST_SERVER_CONNECT).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_post_server_connect(Client *client);
+
+/** Called when a local or remote server is linked in and fully synced, after EOS / End Of Sync (function prototype for HOOKTYPE_SERVER_SYNCED).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_server_synced(Client *client);
+
+/** Called when a local or remote server disconnects (function prototype for HOOKTYPE_SERVER_QUIT).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the disconnect
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_server_quit(Client *client, MessageTag *mtags);
+
+/** Called when a local user changes the nick name (function prototype for HOOKTYPE_LOCAL_NICKCHANGE).
+ * @param client		The client
+ * @param newnick		The new nick name
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_nickchange(Client *client, char *newnick);
+
+/** Called when a remote user changes the nick name (function prototype for HOOKTYPE_REMOTE_NICKCHANGE).
+ * @param client		The client
+ * @param newnick		The new nick name
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_nickchange(Client *client, char *newnick);
+
+/** Called when a user wants to join a channel, may the user join? (function prototype for HOOKTYPE_CAN_JOIN).
+ * @param client		The client
+ * @param channel		The channel the user wants to join
+ * @param key			The key supplied by the client
+ * @param parv			The parameters from the JOIN. Normally you should not use this.
+ * @return Return 0 to allow the user, any other value should be an IRC numeric (eg: ERR_BANNEDFROMCHAN).
+ */
 int hooktype_can_join(Client *client, Channel *channel, char *key, char *parv[]);
+
+/** Called when a user wants to join a channel, may the user join? (function prototype for HOOKTYPE_PRE_LOCAL_JOIN).
+ * FIXME: It's not entirely clear why we have both hooktype_can_join() and hooktype_pre_local_join().
+ * @param client		The client
+ * @param channel		The channel the user wants to join
+ * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
+ * @retval HOOK_DENY		Deny the join.
+ * @retval HOOK_ALLOW		Allow the join (stop processing other modules)
+ * @retval HOOK_CONTINUE	Allow the join, unless another module blocks it.
+ */
 int hooktype_pre_local_join(Client *client, Channel *channel, char *parv[]);
+
+/** Called when a local user joins a channel (function prototype for HOOKTYPE_LOCAL_JOIN).
+ * @param client		The client
+ * @param channel		The channel the user wants to join
+ * @param mtags         	Message tags associated with the event
+ * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+
+/** Called when a remote user joins a channel (function prototype for HOOKTYPE_REMOTE_JOIN).
+ * @param client		The client
+ * @param channel		The channel the user wants to join
+ * @param mtags         	Message tags associated with the event
+ * @param parv			The parameters from the JOIN. May contain channel key in parv[2].
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_join(Client *client, Channel *channel, MessageTag *mtags, char *parv[]);
+
+/** Called when a local user wants to part a channel (function prototype for HOOKTYPE_PRE_LOCAL_PART).
+ * @param client		The client
+ * @param channel		The channel the user wants to part
+ * @param comment		The PART reason, this may be NULL.
+ * @return The part reason (you may also return 'comment' if it should be unchanged) or NULL for an empty reason.
+ */
 char *hooktype_pre_local_part(Client *client, Channel *channel, char *comment);
+
+/** Called when a local user parts a channel (function prototype for HOOKTYPE_LOCAL_PART).
+ * @param client		The client
+ * @param channel		The channel the user is leaving
+ * @param mtags         	Message tags associated with the event
+ * @param comment		The PART reason, this may be NULL.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+
+/** Called when a remote user parts a channel (function prototype for HOOKTYPE_REMOTE_PART).
+ * @param client		The client
+ * @param channel		The channel the user is leaving
+ * @param mtags         	Message tags associated with the event
+ * @param comment		The PART reason, this may be NULL.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_part(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+
+/** Do not use this function, use hooktype_can_kick() instead!
+ */
 char *hooktype_pre_local_kick(Client *client, Client *victim, Channel *channel, char *comment);
+
+/** Called when a local user wants to kick another user from a channel (function prototype for HOOKTYPE_CAN_KICK).
+ * @param client		The client issuing the command
+ * @param victim		The victim that should be kicked
+ * @param channel		The channel the user should be kicked from
+ * @param comment		The KICK reason, this may be NULL.
+ * @param client_flags		The access flags of 'client', one of CHFL_*, eg CHFL_CHANOP.
+ * @param victim_flags		The access flags of 'victim', one of CHFL_*, eg CHFL_VOICE.
+ * @param error			The error message that should be shown to the user (full IRC protocol line).
+ * @retval EX_DENY		Deny the KICK (unless IRCOp with sufficient override rights).
+ * @retval EX_ALWAYS_DENY	Deny the KICK always (even if IRCOp).
+ * @retval EX_ALLOW		Allow the kick, unless another module blocks it.
+ */
 int hooktype_can_kick(Client *client, Client *victim, Channel *channel, char *comment, long client_flags, long victim_flags, char **error);
+
+/** Called when a local user is kicked (function prototype for HOOKTYPE_LOCAL_KICK).
+ * @param client		The client issuing the command
+ * @param victim		The victim that should be kicked
+ * @param channel		The channel the user should be kicked from
+ * @param mtags         	Message tags associated with the event
+ * @param comment		The KICK reason, this may be NULL.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
+
+/** Called when a remote user is kicked (function prototype for HOOKTYPE_REMOTE_KICK).
+ * @param client		The client issuing the command
+ * @param victim		The victim that should be kicked
+ * @param channel		The channel the user should be kicked from
+ * @param mtags         	Message tags associated with the event
+ * @param comment		The KICK reason, this may be NULL.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, MessageTag *mtags, char *comment);
-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);
+
+/** Called right before a message is sent to the channel (function prototype for HOOKTYPE_PRE_CHANMSG).
+ * This function is only used by delayjoin. It cannot block a message. See hooktype_can_send_to_user() instead!
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param text			The text that will be sent
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, SendType sendtype);
+
+/** Called when a user wants to send a message to another user (function prototype for HOOKTYPE_CAN_SEND_TO_USER).
+ * @param client		The sender
+ * @param target		The recipient
+ * @param text			The text to be sent (double pointer!)
+ * @param errmsg		The error message. If you block the message (HOOK_DENY) then you MUST set this!
+ * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
+ * @retval HOOK_DENY		Deny the message. The 'errmsg' will be sent to the user.
+ * @retval HOOK_CONTINUE	Allow the message, unless other modules block it.
+ */
+int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, SendType sendtype);
+
+/** Called when a user wants to send a message to a channel (function prototype for HOOKTYPE_CAN_SEND_TO_CHANNEL).
+ * @param client		The sender
+ * @param channel		The channel to send to
+ * @param member		The membership struct, so you can see for example op status.
+ * @param text			The text to be sent (double pointer!)
+ * @param errmsg		The error message. If you block the message (HOOK_DENY) then you MUST set this!
+ * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
+ * @retval HOOK_DENY		Deny the message. The 'errmsg' will be sent to the user.
+ * @retval HOOK_CONTINUE	Allow the message, unless other modules block it.
+ */
+int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, char **text, char **errmsg, SendType sendtype);
+
+/** Called when a message is sent from one user to another user (function prototype for HOOKTYPE_USERMSG).
+ * @param client		The sender
+ * @param to			The recipient
+ * @param mtags         	Message tags associated with the event
+ * @param text			The text
+ * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, SendType sendtype);
+
+/** Called when a message is sent to a channel (function prototype for HOOKTYPE_CHANMSG).
+ * @param client		The sender
+ * @param channel		The channel
+ * @param sendflags		One of SEND_* (eg SEND_ALL, SKIP_DEAF).
+ * @param prefix		Either zero, one or a combination of PREFIX_*.
+ * @param target		Target string, usually this is "#channel", but it can also contain prefixes like "@#channel"
+ * @param mtags         	Message tags associated with the event
+ * @param text			The text
+ * @param sendtype		The message type, for example SEND_TYPE_PRIVMSG.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, SendType sendtype);
+
+/** Called when a local user wants to change the channel topic (function prototype for HOOKTYPE_PRE_LOCAL_TOPIC).
+ * @param client		The client
+ * @param channel		The channel
+ * @param topic			The new requested topic
+ * @return The new topic (you may also return 'topic'), or NULL if the topic change request should be rejected.
+ */
 char *hooktype_pre_local_topic(Client *client, Channel *channel, char *topic);
-int hooktype_local_topic(Client *client, Channel *channel, char *topic);
+
+/** Called when the channel topic is changed (function prototype for HOOKTYPE_TOPIC).
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param topic			The new topic
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_topic(Client *client, Channel *channel, MessageTag *mtags, char *topic);
+
+/** Called when a local user changes channel modes, called early (function prototype for HOOKTYPE_PRE_LOCAL_CHANMODE).
+ * WARNING: This does not allow you to stop or reject the channel modes. It only allows you to do stuff -before- the
+ * mode is changed. It is currently only used by the delayjoin module.
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param modebuf		The mode buffer, for example "+o"
+ * @param parabuf		The parameter buffer, for example "NiceOp"
+ * @param sendts		Send timestamp
+ * @param samode		Is this an SAMODE?
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_pre_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+
+/** Called when a remote user changes channel modes, called early (function prototype for HOOKTYPE_PRE_REMOTE_CHANMODE).
+ * WARNING: This does not allow you to stop or reject the channel modes. It only allows you to do stuff -before- the
+ * mode is changed. It is currently only used by the delayjoin module.
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param modebuf		The mode buffer, for example "+o"
+ * @param parabuf		The parameter buffer, for example "NiceOp"
+ * @param sendts		Send timestamp
+ * @param samode		Is this an SAMODE?
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_pre_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+
+/** Called when a local user changes channel modes (function prototype for HOOKTYPE_LOCAL_CHANMODE).
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param modebuf		The mode buffer, for example "+o"
+ * @param parabuf		The parameter buffer, for example "NiceOp"
+ * @param sendts		Send timestamp
+ * @param samode		Is this an SAMODE?
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+
+/** Called when a remote user changes channel modes (function prototype for HOOKTYPE_REMOTE_CHANMODE).
+ * @param client		The client
+ * @param channel		The channel
+ * @param mtags         	Message tags associated with the event
+ * @param modebuf		The mode buffer, for example "+o"
+ * @param parabuf		The parameter buffer, for example "NiceOp"
+ * @param sendts		Send timestamp
+ * @param samode		Is this an SAMODE?
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_remote_chanmode(Client *client, Channel *channel, MessageTag *mtags, char *modebuf, char *parabuf, time_t sendts, int samode);
+
+/** Called when a channel mode is removed by a local or remote user (function prototype for HOOKTYPE_MODECHAR_DEL).
+ * NOTE: This is currently not terribly useful for most modules. It is used by by the floodprot and noknock modules.
+ * @param channel		The channel
+ * @param modechar		The mode character, eg 'k'
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_modechar_del(Channel *channel, int modechar);
+
+/** Called when a channel mode is set by a local or remote user (function prototype for HOOKTYPE_MODECHAR_ADD).
+ * NOTE: This is currently not terribly useful for most modules. It is used by by the floodprot and noknock modules.
+ * @param channel		The channel
+ * @param modechar		The mode character, eg 'k'
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_modechar_add(Channel *channel, int modechar);
+
+/** Called when a user sets away status or unsets away status (function prototype for HOOKTYPE_AWAY).
+ * @param client		The client
+ * @param mtags         	Message tags associated with the event
+ * @param reason		The away reason, or NULL if away is unset.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_away(Client *client, MessageTag *mtags, char *reason);
+
+/** Called when a user wants to invite another user to a channel (function prototype for HOOKTYPE_PRE_INVITE).
+ * @param client		The client
+ * @param acptr			The user who is invited (victim)
+ * @param channel		The channel the user is invited to
+ * @param override		If this was an override (1) or not. Note: pointer to an int!
+ * @retval HOOK_DENY		Deny the invite.
+ * @retval HOOK_ALLOW		Allow the invite (stop processing other modules)
+ * @retval HOOK_CONTINUE	Allow the invite, unless another module blocks it.
+ */
 int hooktype_pre_invite(Client *client, Client *acptr, Channel *channel, int *override);
-int hooktype_invite(Client *from, Client *to, Channel *channel, MessageTag *mtags);
+
+/** Called when a user invites another user to a channel (function prototype for HOOKTYPE_INVITE).
+ * @param client		The client
+ * @param acptr			The user who is invited (victim)
+ * @param channel		The channel the user is invited to
+ * @param mtags			Message tags associated with the event
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_invite(Client *client, Client *acptr, Channel *channel, MessageTag *mtags);
+
+/** Called when a user wants to knock on a channel (function prototype for HOOKTYPE_PRE_KNOCK).
+ * FIXME: where is the knock reason ?
+ * @param client		The client
+ * @param channel		The channel to knock on
+ * @retval HOOK_DENY		Deny the knock.
+ * @retval HOOK_ALLOW		Allow the knock (stop processing other modules)
+ * @retval HOOK_CONTINUE	Allow the knock, unless another module blocks it.
+ */
 int hooktype_pre_knock(Client *client, Channel *channel);
+
+/** Called when a user knocks on a channel (function prototype for HOOKTYPE_KNOCK).
+ * @param client		The client
+ * @param channel		The channel to knock on
+ * @param mtags			Message tags associated with the event
+ * @param comment		The knock reason
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_knock(Client *client, Channel *channel, MessageTag *mtags, char *comment);
+
+/** Called when a user whoises someone (function prototype for HOOKTYPE_WHOIS).
+ * @param client		The client issuing the command
+ * @param target		The user who is the target of the /WHOIS.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_whois(Client *client, Client *target);
+
+/** Called to add letters to the WHO status column (function prototype for HOOKTYPE_WHO_STATUS).
+ * If a user does a /WHO request, then WHO will show a number of status flags
+ * such as B to show the user is a bot (see "HELPOP WHO" for the full list).
+ * @param client		The client
+ * @param target		The target, the user for which we should display WHO status flags
+ * @param channel		The channel if a channel WHO, or NULL
+ * @param member		The membership information, or NULL
+ * @param status		The current status flags, so far
+ * @param cansee		If 'client' can see 'target' (eg: in same channel or -i)
+ * @return Return 0 if no WHO status flags need to be added, otherwise return the ascii character (eg: return 'B').
+ */
 int hooktype_who_status(Client *client, Client *target, Channel *channel, Member *member, char *status, int cansee);
-int hooktype_pre_kill(Client *client, Client *victim, char *killpath);
+
+/** Called when an IRCOp wants to kill another user (function prototype for HOOKTYPE_PRE_KILL).
+ * @param client		The client
+ * @param victim		The user who should be killed
+ * @param reason		The kill reason
+ * @retval EX_DENY		Deny the KICK (unless IRCOp with sufficient override rights).
+ * @retval EX_ALWAYS_DENY	Deny the KICK always (even if IRCOp).
+ * @retval EX_ALLOW		Allow the kick, unless another module blocks it.
+ */
+int hooktype_pre_kill(Client *client, Client *victim, char *reason);
+
+/** Called when a local user kills another user (function prototype for HOOKTYPE_LOCAL_KILL).
+ * Note that kills from remote IRCOps will show up as regular quits, so use hooktype_remote_quit() and hooktype_local_quit().
+ * @param client		The client
+ * @param victim		The victim
+ * @param comment		The kill reason
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_kill(Client *client, Client *victim, char *comment);
+
+/** Called when an IRCOp /REHASH'es, and passes the parameters (function prototype for HOOKTYPE_REHASHFLAG).
+ * FIXME: shouldn't this be merged with hooktype_rehash() ?
+ * @param client		The client issuing the command, or NULL if rehashing due to system signal.
+ * @param str			The rehash flag (eg: "-all")
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_rehashflag(Client *client, char *str);
-int hooktype_configposttest(int *errors);
+
+/** Called when the server is rehashing (function prototype for HOOKTYPE_REHASH).
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_rehash(void);
-int hooktype_stats(Client *client, char *str);
+
+/** Called when the server has completed rehashing (function prototype for HOOKTYPE_REHASH_COMPLETE).
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_rehash_complete(void);
+
+/** Called when searching for a test function for a specific configuration item (function prototype for HOOKTYPE_CONFIGTEST).
+ * This is part of the configuration API, which is better documented at the
+ * wiki at https://www.unrealircd.org/docs/Dev:Configuration_API
+ * @param cfptr			Configuration file
+ * @param ce			Configuration entry
+ * @param section		One of CONFIG_*, eg: CONFIG_MAIN.
+ * @param errors		Counter for errors
+ * @retval 0			This entry is not for us, we don't know anything about it.
+ * @retval -1			Errors encountered (the number of errors is stored in *errors)
+ * @retval 1			This entry is handled and is without any errors.
+ */
 int hooktype_configtest(ConfigFile *cfptr, ConfigEntry *ce, int section, int *errors);
+
+/** Called after all hooktype_configtest() have run, to check for missing config items (function prototype for HOOKTYPE_CONFIGPOSTTEST).
+ * @param errors		The number of errors
+ * @returns In case of errors, return -1.
+ */
+int hooktype_configposttest(int *errors);
+
+/** Called to run/do the active configuration for this configuration item (function prototype for HOOKTYPE_CONFIGRUN).
+ * This is part of the configuration API, which is better documented at the
+ * wiki at https://www.unrealircd.org/docs/Dev:Configuration_API
+ * @param cfptr			Configuration file
+ * @param ce			Configuration entry
+ * @param section		One of CONFIG_*, eg: CONFIG_MAIN.
+ * @retval 0			This entry is not for us, we don't know anything about it.
+ * @retval 1			This entry is for us, it is now handled, don't call any other modules for it anymore.
+ */
 int hooktype_configrun(ConfigFile *cfptr, ConfigEntry *ce, int section);
+
+/** Called to run/do the active configuration for this configuration item - extended version (function prototype for HOOKTYPE_CONFIGRUN_EX).
+ * This particular "extended version" is only used for extending listen { } options, so you probably don't need this one.
+ * Use hooktype_configrun() instead!
+ * @param cfptr			Configuration file
+ * @param ce			Configuration entry
+ * @param section		One of CONFIG_*, eg: CONFIG_MAIN.
+ * @param ptr			Pointer to something
+ * @retval 0			This entry is not for us, we don't know anything about it.
+ * @retval 1			This entry is for us, it is now handled, don't call any other modules for it anymore.
+ */
 int hooktype_configrun_ex(ConfigFile *cfptr, ConfigEntry *ce, int section, void *ptr);
+
+/** Called when a user types /STATS <something> (function prototype for HOOKTYPE_STATS).
+ * This way a module can add a new STATS item, eg 'STATS something'
+ * @param client		The client issuing the command
+ * @param str			The parameter to the STATS command, eg 'something'.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_stats(Client *client, char *str);
+
+/** Called when a user becomes IRCOp or is no longer an IRCOp (function prototype for HOOKTYPE_LOCAL_OPER).
+ * @param client		The client
+ * @param add			1 if the user becomes IRCOp, 0 if the user is no longer IRCOp
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_oper(Client *client, int add);
+
+/** Called when a client sends a PASS command (function prototype for HOOKTYPE_LOCAL_PASS).
+ * @param client		The client
+ * @param password		The password supplied by the client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_local_pass(Client *client, char *password);
+
+/** Called when a channel is created (function prototype for HOOKTYPE_CHANNEL_CREATE).
+ * @param client		The client
+ * @param channel		The channel that just got created
+ * @note This function is not used much, use hooktype_local_join() and hooktype_remote_join() instead.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_channel_create(Client *client, Channel *channel);
+
+/** Called when a channel is completely destroyed (function prototype for HOOKTYPE_CHANNEL_DESTROY).
+ * @param channel		The channel that is about to be destroyed
+ * @param should_destroy	Module can set this to 1 to prevent destriction
+ * @note A channel is usually destroyed due to the last user leaving. But in some cases
+ *       a channel is created and then immediately destroyed within nanoseconds. Just so you know.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_channel_destroy(Channel *channel, int *should_destroy);
-int hooktype_tkl_except(Client *cptr, int ban_type);
+
+/** Called when a user matches a TKL and is pending to be killed (function prototype for HOOKTYPE_TKL_EXCEPT).
+ * @param client		The client
+ * @param ban_type		The TKL type, one of TKL_*. For example TKL_GLOBAL|TKL_KILL for a gline.
+ * @retval 0 Ban/kill the user.
+ * @retval 1 User is exempt, do NOT kill or ban.
+ */
+int hooktype_tkl_except(Client *client, int ban_type);
+
+/** Called when the user modes of a user change (function prototype for HOOKTYPE_UMODE_CHANGE).
+ * @param client		The client
+ * @param setflags		The current user modes
+ * @param newflags		The new user modes
+ * @note The user mode can be changed due to a MODE by the user itself, by a server, or by SVSMODE/SVS2MODE from Services.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_umode_change(Client *client, long setflags, long newflags);
-int hooktype_rehash_complete(void);
+
+/** Called when a new TKL is added (function prototype for HOOKTYPE_TKL_ADD).
+ * @param client		The client adding the TKL (this can be &me)
+ * @param tkl			The TKL entry
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_tkl_add(Client *client, TKL *tkl);
+
+/** Called when removing an existing TKL (function prototype for HOOKTYPE_TKL_DEL).
+ * @param client		The client removing the TKL (this can be &me)
+ * @param tkl			The TKL entry
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_tkl_del(Client *client, TKL *tkl);
+
+/** Called when something is logged via the ircd_log() function (function prototype for HOOKTYPE_LOG).
+ * @param flags			One of LOG_*, such as LOG_ERROR.
+ * @param timebuf		The time buffer, such as "[2030-01-01 12:00:00]"
+ * @param buf			The text to be logged
+ * @return The return value is ignored (use return 0)
+ */
 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, SendType sendtype);
+
+/** Called when a local user matches a spamfilter (function prototype for HOOKTYPE_LOCAL_SPAMFILTER).
+ * @param client		The client
+ * @param str			The text that matched, this may be stripped from color and control codes.
+ * @param str_in		The original text
+ * @param target		The spamfilter type, one of SPAMF_*, such as SPAMF_CHANMSG.
+ * @param destination		The destination, such as the name of another client or channel
+ * @param tkl			The spamfilter TKL entry that matched
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_local_spamfilter(Client *client, char *str, char *str_in, int type, char *target, TKL *tkl);
+
+/** Called when a user sends something to a user that has the sender silenced (function prototype for HOOKTYPE_SILENCED).
+ * UnrealIRCd support a SILENCE list. If the target user has added someone on the silence list, eg via SILENCE +BadUser,
+ * and then 'BadUser' tries to send a message to this user, this hook will be triggered.
+ * @param client		The client trying to send a message/notice
+ * @param target		The intended recipient of the message
+ * @param sendtype		Indicating if it is a PRIVMSG, NOTICE or something else.
+ * @note This function is rarely used.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_silenced(Client *client, Client *target, SendType sendtype);
+
+/** Called on every incoming packet (function prototype for HOOKTYPE_RAWPACKET_IN).
+ * This is quite invasive, so only use this if you cannot do the same via some other means (eg overrides or hooks).
+ * The typical use cases are things like: handling an entirely different protocol (eg: websocket module),
+ * or old stuff like codepage conversions, basically: things that work on entire packets.
+ * @param client		The client
+ * @param readbuf		The buffer
+ * @param length		The length of the buffer
+ * @note If you want to alter the buffer contents then replace 'readbuf' with your own buffer and set 'length' appropriately.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_rawpacket_in(Client *client, char *readbuf, int *length);
+
+/** Called when a packet is received or sent (function prototype for HOOKTYPE_PACKET).
+ * @param client		The locally connected sender, this can be &me
+ * @param to			The locally connected recipient, this can be &me
+ * @param intended_to		The originally intended recipient, this could be a remote user
+ * @param msg			The buffer
+ * @param length		The length of the buffer
+ * @note When reading a packet, 'client' will indicate the locally connected user and 'to' will be &me.
+ *       When sending a pcket, 'client' will be &me and 'to' will be the locally connected user.
+ *       If you want to alter the buffer contents then replace 'msg' with your own buffer and set 'length' appropriately.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length);
+
+/** Called very early when a client connects (function prototype for HOOKTYPE_HANDSHAKE).
+ * This is called as soon as the socket is connected and the client is being set up,
+ * so before the client has sent any application data, and certainly before it is
+ * known whether this client will become a user or a server.
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_handshake(Client *client);
-int hooktype_free_client(Client *acptr);
-int hooktype_free_user(Client *acptr);
+
+/** Called when a client structure is freed (function prototype for HOOKTYPE_FREE_CLIENT).
+ * @param client		The client
+ * @note Normally you use hooktype_local_quit(), hooktype_remote_quit() and hooktype_unkuser_quit() for this.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_free_client(Client *client);
+
+/** Called when the user structure, client->user, is being freed (function prototype for HOOKTYPE_FREE_USER).
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_free_user(Client *client);
+
+/** Called when +l limit is exceeded when joining (function prototype for HOOKTYPE_CAN_JOIN_LIMITEXCEEDED).
+ * @param client		The client
+ * @param channel		The channel
+ * @param key			The channel key
+ * @param parv			The join parameters
+ * @note I don't think this works?
+ * @return Unclear..
+ */
 int hooktype_can_join_limitexceeded(Client *client, Channel *channel, char *key, char *parv[]);
+
+/** Called to check if the user is visible in the channel (function prototype for HOOKTYPE_VISIBLE_IN_CHANNEL).
+ * For example, the delayjoin module (+d/+D) will 'return 0' here if the user is hidden due to delayed join.
+ * @param client		The client
+ * @param channel		The channel
+ * @retval 0 The user is NOT visible
+ * @retval 1 The user is visible
+ */
 int hooktype_visible_in_channel(Client *client, Channel *channel);
+
+/** Called to check if the channel of a user should be shown in WHOIS/WHO (function prototype for HOOKTYPE_SEE_CHANNEL_IN_WHOIS).
+ * @param client		The client ASKING, eg doing the /WHOIS.
+ * @param target		The client who is being interrogated
+ * @param channel		The channel that 'client' is in
+ * @retval 0 The channel should NOT be visible
+ * @retval 1 Show the channel
+ */
+int hooktype_see_channel_in_whois(Client *client, Client *target, Channel *channel);
+
+/** Called when a user is added to a channel (function prototype for HOOKTYPE_JOIN_DATA).
+ * Note that normally you use hooktype_local_join() and hooktype_remote_join() for this.
+ * This function only exists so it is easy to work with dynamic data, and even
+ * that is an old idea now that we have the moddata system.
+ * @param client		The client joining
+ * @param channel		The channel the client joined to
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_join_data(Client *who, Channel *channel);
+
+/** Should the user be able to bypass bans? (function prototype for HOOKTYPE_OPER_INVITE_BAN).
+ * @param client		The client
+ * @param channel		The channel
+ * @note The actual meaning of this hook is more complex, you are unlikely to use it, anyway.
+ * @retval HOOK_DENY		Deny the join if the user is also banned
+ * @retval HOOK_CONTINUE	Obey the normal rules
+ */
 int hooktype_oper_invite_ban(Client *client, Channel *channel);
+
+/** Should a user be able to view the topic when not in the channel? (function prototype for HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL).
+ * @param client		The client requesting the topic
+ * @param channel		The channel
+ * @note This visibility check is only partially implemented. Do not count on it.
+ * @retval HOOK_DENY		Deny the topic request
+ * @retval HOOK_CONTINUE	Obey the normal rules
+ */
 int hooktype_view_topic_outside_channel(Client *client, Channel *channel);
+
+/** Is a user permitted to change its nickname? (function prototype for HOOKTYPE_CHAN_PERMIT_NICK_CHANGE).
+ * This is called for each channel the user is in. This is used by the +N (nonickchange) channel mode.
+ * @param client		The client
+ * @param channel		The channel the user is in
+ * @retval HOOK_DENY		Deny the nick change
+ * @retval HOOK_CONTINUE	Obey the normal rules (allow it, unless denied by something else)
+ */
 int hooktype_chan_permit_nick_change(Client *client, Channel *channel);
+
+/** Is the channel considered "secure"? (function prototype for HOOKTYPE_IS_CHANNEL_SECURE).
+ * This is used by the +z/+Z modules.
+ * @param channel		The channel
+ * @retval 0			No, the channel is not secure
+ * @retval 1			Yes, the channel is secure
+ */
 int hooktype_is_channel_secure(Channel *channel);
-int hooktype_can_send_to_channel_secure(Client *client, Channel *channel);
+
+/** Called after a channel is synced due to netmerge (function prototype for HOOKTYPE_CHANNEL_SYNCED).
+ * When a server connects channel status is exchanged in order to synchronize the two sides of channels.
+ * After each SJOIN command this function is called to check if anything special
+ * needs to be join. At the moment this function is only used by channel mode +z
+ * which will kick out any insecure users if we are the "loosing" side of a split.
+ * @param channel		The channel
+ * @param merge			Set to 1 if merging due to equal timestamps on both sides, 0 otherwise
+ * @param removetheirs		Set to 1 if the other side is the loosing side and we are the winning side.
+ * @param nomode		Set to 1 if this is a SJOIN without modes (rare? services?)
+ * @retval HOOK_DENY		Deny the channel merge. Important: only return this after you have destroyed the channel!
+ * @retval HOOK_CONTINUE	Continue normally
+ */
 int hooktype_channel_synced(Channel *channel, int merge, int removetheirs, int nomode);
+
+/** Can the target client be SAJOIN'ed to a particular channel? (function prototype for HOOKTYPE_CAN_SAJOIN).
+ * @param target		The client that should be joined
+ * @param channel		The channel that the client should be joined to
+ * @param client		The client issuing the request (usually IRCOp)
+ * @retval HOOK_DENY		Deny the SAJOIN
+ * @retval HOOK_CONTINUE	Allow the SAJOIN, unless blocked by something else
+ */
 int hooktype_can_sajoin(Client *target, Channel *channel, Client *client);
-int hooktype_check_init(Client *cptr, char *sockname, size_t size);
+
+/** Called when the hostname is initialized for a client (function prototype for HOOKTYPE_CHECK_INIT).
+ * This is a very specific call, it is only meant for the WEBIRC module.
+ * @param client		The client
+ * @param sockname		The socket name
+ * @param size			The size of the socket name? :D
+ * @retval HOOK_CONTINUE	Proceed normally
+ * @retval HOOK_DENY		Reject the connection(?)
+ */
+int hooktype_check_init(Client *client, char *sockname, size_t size);
+
+/** May the target user be deoped? (function prototype for HOOKTYPE_MODE_DEOP).
+ * This is for example used by the +S (Services bot) user mode to block deop requests to services bots.
+ * @param client		The client issuing the command
+ * @param victim		The victim that should be deoped (MODE -o)
+ * @param channel		The channel
+ * @param what			Always MODE_DEL at the moment
+ * @param modechar		The mode character: q/a/o/h/v
+ * @param my_access		Cached result of get_access(), so one of CHFL_*, for example CHFL_CHANOP.
+ * @param badmode		The error string that should be sent to the client
+ * @retval HOOK_CONTINUE	Proceed normally (allow it)
+ * @retval HOOK_DENY		Reject the mode change
+ * @retval HOOK_ALWAYS_DENY	Reject the mode change, even if IRCOp/Services/..
+ */
 int hooktype_mode_deop(Client *client, Client *victim, Channel *channel, u_int what, int modechar, long my_access, char **badmode);
-int hooktype_see_channel_in_whois(Client *client, Client *target, Channel *channel);
+
+/** Called when a DCC request was denied by the IRCd (function prototype for HOOKTYPE_DCC_DENIED).
+ * @param client		The client who tried to send a file
+ * @param target		The intended recipient
+ * @param realfile		The original file name, may contain strange characters or be very long
+ * @param displayfile		The file name for displaying purposes, properly filtered.
+ * @param denydcc		The deny dcc { ] rule that triggered.
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_dcc_denied(Client *client, char *target, char *realfile, char *displayfile, ConfigItem_deny_dcc *denydcc);
-int hooktype_server_handshake_out(Client *client);
-int hooktype_server_synced(Client *client);
+
+/** Called in the user accept procedure, when setting the +z user mode (function prototype for HOOKTYPE_SECURE_CONNECT).
+ * This is only meant to be used by the WEBIRC module, so it can do -z for fake secure users.
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_secure_connect(Client *client);
+
+/** Can the user bypass a particular channel message restriction? (function prototype for HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION).
+ * This is for example used to bypass +S (stripcolor) via ~m:color:*!*@a.b.c.d if the user matches that extban.
+ * @param client		The client (sender)
+ * @param channel		The channel
+ * @param bypass_type		The restriction to bypass, for example BYPASS_CHANMSG_COLOR
+ * @retval HOOK_ALLOW		Allow to bypass the restriction
+ * @retval HOOK_CONTINUE	Continue as normal, obey normal rules, deny bypassing the restriction.
+ */
 int hooktype_can_bypass_channel_message_restriction(Client *client, Channel *channel, BypassChannelMessageRestrictionType bypass_type);
+
+/** Called when xxxx (function prototype for HOOKTYPE_REQUIRE_SASL).
+ * FIXME: this hook is never called!?
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_require_sasl(Client *client, char *reason);
+
+/** Called when a SASL continuation response is received (function prototype for HOOKTYPE_SASL_CONTINUATION).
+ * This is only used by the authprompt module, it unlikely that you need it.
+ * @param client		The client for which the SASL authentication is taking place
+ * @param buf			The AUTHENTICATE buffer
+ * @retval HOOK_CONTINUE	Continue as normal
+ * @retval HOOK_DENY		Do not handle the SASL request, or at least don't show the response to the client.
+ */
 int hooktype_sasl_continuation(Client *client, char *buf);
+
+/** Called when a SASL result response is received (function prototype for HOOKTYPE_SASL_RESULT).
+ * This is only used by the authprompt module.
+ * @param client		The client for which the SASL authentication is taking place
+ * @param successs		Whether the SASL authentication was successful (1) or not (0)
+ * @retval HOOK_CONTINUE	Continue as normal
+ * @retval HOOK_DENY		Do not handle the SASL response, or at least don't show the response to the client.
+ */
 int hooktype_sasl_result(Client *client, int success);
+
+/** Called when a TKL ban should be added on the host (function prototype for HOOKTYPE_PLACE_HOST_BAN).
+ * This is called for automated bans such as spamfilter hits, flooding, etc.
+ * This hook can be used to prevent the ban, or as used by the authprompt to delay it.
+ * @param client		The client that should be banned
+ * @param action		The TKL type, such as BAN_ACT_GLINE
+ * @param reason		The ban reason
+ * @param duration		The duration of the ban, 0 for permanent ban
+ * @return The magic value 99 is used to exempt the user (=do not ban!), otherwise the ban is added.
+ */
 int hooktype_place_host_ban(Client *client, int action, char *reason, long duration);
+
+/** Called when a TKL ban is hit by this user (function prototype for HOOKTYPE_FIND_TKLINE_MATCH).
+ * This is called when an existing TKL entry is hit by the user.
+ * To prevent an automated ban to be added on a host/ip, see hooktype_place_host_ban().
+ * @param client		The client
+ * @param tkl			The TKL entry
+ * @return The magic value 99 is used to exempt the user (=do not kill!), otherwise the ban is executed.
+ */
 int hooktype_find_tkline_match(Client *client, TKL *tk);
+
+/** Called when the user connects for each welcome numeric (function prototype for HOOKTYPE_WELCOME).
+ * This can be used to send some additional notice or data to the user at a step of your choosing.
+ * This is called before all numerics with 'after_numeric' set to 0, and then after numeric
+ * 001, 002, 003, 005, 396, 266, 376. In the last call, 'after_numeric' is 999 when all initial
+ * numerics have been sent but before the user is auto-joined to channels (if any).
+ * @param client		The client
+ * @param after_numeric		Which numeric has just been sent
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_welcome(Client *client, int after_numeric);
+
+/** Called right before parsing a line and client command (function prototype for HOOKTYPE_PRE_COMMAND).
+ * This is only used by labeled-reponse. If you think this hook is useful then you
+ * should probably use the CommandOverride API instead!
+ * @param client		The direct local client connection from which the line is received.
+ * @param mtags			Message tags, if any.
+ * @param buf			The buffer (without message tags)
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_pre_command(Client *from, MessageTag *mtags, char *buf);
+
+/** Called right after finishing a client command (function prototype for HOOKTYPE_POST_COMMAND).
+ * This is only used by labeled-reponse. If you think this hook is useful then you
+ * should probably use the CommandOverride API instead!
+ * @param client		The direct local client connection from which the line is received.
+ * @param mtags			Message tags, if any.
+ * @param buf			The buffer (without message tags)
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_post_command(Client *from, MessageTag *mtags, char *buf);
+
+/** Called when new_message() is executed (function prototype for HOOKTYPE_NEW_MESSAGE).
+ * When a new message with message tags is prepared, code in UnrealIRCd
+ * and in modules will call new_message(). From that function this hook
+ * is also called. The purpose of this hook is so you can add additional
+ * message tags that belong the user. For example it is used
+ * by the account-tag module to add account=xyz information, see that module for a good example.
+ * @param sender		The client from which the message will be sent
+ * @param recv_mtags		The message tags as originally received before, or NULL if completely new.
+ * @param mtag_list		The newly created message tag list that we are building
+ * @param signature		Special signature when used through new_message_special()
+ * @return The return value is ignored (use return 0)
+ */
 void hooktype_new_message(Client *sender, MessageTag *recv_mtags, MessageTag **mtag_list, char *signature);
-int hooktype_is_handshake_finished(Client *acptr);
+
+/** Is the client handshake finished? (function prototype for HOOKTYPE_IS_HANDSHAKE_FINISHED).
+ * This is called by the is_handshake_finished() function to check if the user
+ * can be accepted on IRC, or if there are still other checks/input pending.
+ * This can be used to "hold" a user temporarily until something happens, such
+ * as the user typing a password or waiting for a remote access check to return a result.
+ * For an example usage, see the cap module, which uses it to "hold" the connection
+ * if a "CAP LS" has been sent and no "CAP END" has been received yet.
+ * @param client		The client
+ * @retval 1			Yes, the handshake is finished, as far as we are concerned.
+ * @retval 0			No, the handshake is not yet finished, do not allow the user in yet.
+ */
+int hooktype_is_handshake_finished(Client *client);
+
+/** Called upon a local client quit, allows altering the quit message on a per-channel basis (function prototype for HOOKTYPE_PRE_LOCAL_QUIT_CHAN).
+ * If you don't need to change the quit message on a per-channel basis, but want to change it regardless of channels, then use hooktype_pre_local_quit().
+ * If you don't need to change the quit message at all, then use hooktype_local_quit() and hooktype_remote_quit() instead.
+ * @param client		The client
+ * @param channel		The channel
+ * @param comment		The quit message
+ * @return The original quit message (comment), the new quit message (pointing to your own static buffer), or NULL (no quit message)
+ */
 char *hooktype_pre_local_quit_chan(Client *client, Channel *channel, char *comment);
-int hooktype_ident_lookup(Client *acptr);
+
+/** Called when an ident lookup should be made (function prototype for HOOKTYPE_IDENT_LOOKUP).
+ * This is used by the ident_lookup module.
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_ident_lookup(Client *client);
+
+/** Called when someone logs in/out a services account (function prototype for HOOKTYPE_ACCOUNT_LOGIN).
+ * The account name can be found in client->user->svid. It will be the string "0" if the user is logged out.
+ * @param client		The client
+ * @param mtags         	Message tags associated with the event
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_account_login(Client *client, MessageTag *mtags);
+
+/** Called when closing the connection of a local user (function prototype for HOOKTYPE_CLOSE_CONNECTION).
+ * This is called from close_connection(). Note that a lot of client information
+ * has already been freed, so normally you should use the quit/exit functions instead:
+ * hooktype_local_quit(), hooktype_remote_quit() and hooktype_unkuser_quit().
+ * @param client		The client
+ * @return The return value is ignored (use return 0)
+ */
 int hooktype_close_connection(Client *client);
 
+/** @} */
+
 #ifdef GCC_TYPECHECKING
 #define ValidateHook(validatefunc, func) __builtin_types_compatible_p(__typeof__(func), __typeof__(validatefunc))
 
@@ -1157,7 +2085,6 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_LOCAL_PART) && !ValidateHook(hooktype_local_part, func)) || \
         ((hooktype == HOOKTYPE_LOCAL_KICK) && !ValidateHook(hooktype_local_kick, func)) || \
         ((hooktype == HOOKTYPE_LOCAL_CHANMODE) && !ValidateHook(hooktype_local_chanmode, func)) || \
-        ((hooktype == HOOKTYPE_LOCAL_TOPIC) && !ValidateHook(hooktype_local_topic, func)) || \
         ((hooktype == HOOKTYPE_LOCAL_OPER) && !ValidateHook(hooktype_local_oper, func)) || \
         ((hooktype == HOOKTYPE_UNKUSER_QUIT) && !ValidateHook(hooktype_unkuser_quit, func)) || \
         ((hooktype == HOOKTYPE_LOCAL_PASS) && !ValidateHook(hooktype_local_pass, func)) || \
@@ -1210,7 +2137,6 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_VIEW_TOPIC_OUTSIDE_CHANNEL) && !ValidateHook(hooktype_view_topic_outside_channel, func)) || \
         ((hooktype == HOOKTYPE_CHAN_PERMIT_NICK_CHANGE) && !ValidateHook(hooktype_chan_permit_nick_change, func)) || \
         ((hooktype == HOOKTYPE_IS_CHANNEL_SECURE) && !ValidateHook(hooktype_is_channel_secure, func)) || \
-        ((hooktype == HOOKTYPE_SEND_CHANNEL) && !ValidateHook(hooktype_can_send_to_channel_secure, func)) || \
         ((hooktype == HOOKTYPE_CHANNEL_SYNCED) && !ValidateHook(hooktype_channel_synced, func)) || \
         ((hooktype == HOOKTYPE_CAN_SAJOIN) && !ValidateHook(hooktype_can_sajoin, func)) || \
         ((hooktype == HOOKTYPE_WHOIS) && !ValidateHook(hooktype_whois, func)) || \
diff --git a/include/setup.h.in b/include/setup.h.in
@@ -28,6 +28,9 @@
 /* Define if you have the <glob.h> header file. */
 #undef GLOBH
 
+/* Define if ssl library has ASN1_TIME_diff */
+#undef HAS_ASN1_TIME_diff
+
 /* Define if ssl library has SSL_CTX_set1_curves_list */
 #undef HAS_SSL_CTX_SET1_CURVES_LIST
 
@@ -37,6 +40,9 @@
 /* Define if ssl library has SSL_CTX_set_security_level */
 #undef HAS_SSL_CTX_SET_SECURITY_LEVEL
 
+/* Define if ssl library has X509_get0_notAfter */
+#undef HAS_X509_get0_notAfter
+
 /* Define if you have crypt */
 #undef HAVE_CRYPT
 
diff --git a/include/struct.h b/include/struct.h
@@ -108,6 +108,7 @@ typedef struct ConfigItem_include ConfigItem_include;
 typedef struct ConfigItem_blacklist_module ConfigItem_blacklist_module;
 typedef struct ConfigItem_help ConfigItem_help;
 typedef struct ConfigItem_offchans ConfigItem_offchans;
+typedef struct SecurityGroup SecurityGroup;
 typedef struct ListStruct ListStruct;
 typedef struct ListStructPrio ListStructPrio;
 
@@ -285,7 +286,7 @@ typedef enum ClientStatus {
 #define	SetUser(x)		((x)->status = CLIENT_STATUS_USER)
 #define	SetLog(x)		((x)->status = CLIENT_STATUS_LOG)
 
-/* @} */
+/** @} */
 
 /** Used for checking certain properties of clients, such as IsSecure() and IsULine().
  * @defgroup ClientFlags Client flags
@@ -487,7 +488,7 @@ typedef enum ClientStatus {
 #define ClearULine(x)			do { (x)->flags &= ~CLIENT_FLAG_ULINE; } while(0)
 #define ClearVirus(x)			do { (x)->flags &= ~CLIENT_FLAG_VIRUS; } while(0)
 #define ClearIdentLookupSent(x)		do { (x)->flags &= ~CLIENT_FLAG_IDENTLOOKUPSENT; } while(0)
-/* @} */
+/** @} */
 
 
 /* Others that access client structs: */
@@ -498,6 +499,9 @@ typedef enum ClientStatus {
 #define IsSynched(x)	(x->serv->flags.synced)
 #define IsServerSent(x) (x->serv && x->serv->flags.server_sent)
 
+/* And more that access client stuff - but actually modularized */
+#define GetReputation(client) (moddata_client_get(client, "reputation") ? atoi(moddata_client_get(client, "reputation")) : 0) /**< Get reputation value for a client */
+
 /* PROTOCTL (Server protocol) stuff */
 #ifndef DEBUGMODE
 #define CHECKSERVERPROTO(x,y)	(((x)->local->proto & y) == y)
@@ -792,7 +796,8 @@ struct SWhois {
 	char *setby;
 };
 
-/** The command API - used by modules and the core.
+/** The command API - used by modules and the core to add commands, overrides, etc.
+ * See also https://www.unrealircd.org/docs/Dev:Command_API for a higher level overview and example.
  * @defgroup CommandAPI Command API
  * @{
  */
@@ -829,7 +834,7 @@ struct SWhois {
  *        E.g. parv[3] in the above example is out of bounds.
  */
 #define CMD_FUNC(x) void (x) (Client *client, MessageTag *recv_mtags, int parc, char *parv[])
-/* @} */
+/** @} */
 
 /** Command override function - used by all command override handlers.
  * This is used in the code like <pre>CMD_OVERRIDE_FUNC(ovr_somecmd)</pre> as a function definition.
@@ -1217,7 +1222,7 @@ struct Server {
 	} features;
 };
 
-/* @} */
+/** @} */
 
 struct MessageTag {
 	MessageTag *prev, *next;
@@ -1379,6 +1384,7 @@ struct ConfigFlag_allow {
 	unsigned	noident :1;
 	unsigned	useip :1;
 	unsigned	tls :1;
+	unsigned	reject_on_auth_failure :1;
 };
 
 struct ConfigItem_allow {
@@ -1715,6 +1721,16 @@ struct ConfigItem_offchans {
 	char *topic;
 };
 
+#define SECURITYGROUPLEN 48
+struct SecurityGroup {
+	SecurityGroup *prev, *next;
+	int priority;
+	char name[SECURITYGROUPLEN+1];
+	int identified;
+	int reputation_score;
+	int webirc;
+	int tls;
+};
 
 #define HM_HOST 1
 #define HM_IPV4 2
@@ -2010,18 +2026,6 @@ extern MODVAR SSL_CTX *ctx;
 extern MODVAR SSL_CTX *ctx_server;
 extern MODVAR SSL_CTX *ctx_client;
 
-extern SSL_METHOD *meth;
-extern int early_init_ssl();
-extern int init_ssl();
-extern int ssl_handshake(Client *);   /* Handshake the accpeted con.*/
-extern int ssl_client_handshake(Client *, ConfigItem_link *); /* and the initiated con.*/
-extern int ircd_SSL_accept(Client *acptr, int fd);
-extern int ircd_SSL_connect(Client *acptr, int fd);
-extern int SSL_smart_shutdown(SSL *ssl);
-extern void ircd_SSL_client_handshake(int, int, void *);
-extern void SSL_set_nonblocking(SSL *s);
-extern SSL_CTX *init_ctx(TLSOptions *tlsoptions, int server);
-
 #define TLS_PROTOCOL_TLSV1		0x0001
 #define TLS_PROTOCOL_TLSV1_1	0x0002
 #define TLS_PROTOCOL_TLSV1_2	0x0004
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 7
+#define UNREAL_VERSION_MINOR 8
 
 /* 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-channelmode.c b/src/api-channelmode.c
@@ -26,7 +26,9 @@
 
 #include "unrealircd.h"
 
-/** This is the extended channel mode API
+/** This is the extended channel mode API,
+ * see also https://www.unrealircd.org/docs/Dev:Channel_Mode_API
+ * for more information.
  * @defgroup ChannelModeAPI Channel mode API
  * @{
  */
diff --git a/src/api-efunctions.c b/src/api-efunctions.c
@@ -63,7 +63,7 @@ int (*find_shun)(Client *client);
 int(*find_spamfilter_user)(Client *client, int flags);
 TKL *(*find_qline)(Client *client, char *nick, int *ishold);
 TKL *(*find_tkline_match_zap)(Client *client);
-void (*tkl_stats)(Client *client, int type, char *para);
+void (*tkl_stats)(Client *client, int type, char *para, int *cnt);
 void (*tkl_sync)(Client *client);
 void (*cmd_tkl)(Client *client, MessageTag *mtags, int parc, char *parv[]);
 int (*place_host_ban)(Client *client, BanAction action, char *reason, long duration);
diff --git a/src/api-event.c b/src/api-event.c
@@ -240,4 +240,5 @@ void SetupEvents(void)
 	EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0);
 	EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0);
 	EventAdd(NULL, "try_connections", try_connections, NULL, 2000, 0);
+	EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0);
 }
diff --git a/src/api-messagetag.c b/src/api-messagetag.c
@@ -25,7 +25,9 @@
  */
 #include "unrealircd.h"
 
-/** This is the message tags API (message-tags)
+/** This is the message tags API (message-tags).
+ * For an overview of message tags in general (not the API)
+ * see https://www.unrealircd.org/docs/Message_tags
  * @defgroup MessagetagAPI Message tag API
  * @{
  */
diff --git a/src/channel.c b/src/channel.c
@@ -28,7 +28,16 @@
 long opermode = 0;
 /** Lazy way to signal an SAJOIN MODE */
 long sajoinmode = 0;
-/** List of all channels on the server */
+/** List of all channels on the server.
+ * @ingroup ListFunctions
+ * @section channels_example Example
+ * This code will list all channels on the network.
+ * @code
+ * sendnotice(client, "List of all channels:");
+ * for (channel = channels; channel; channel=channel->nextch)
+ *     sendnotice(client, "Channel %s", channel->name);
+ * @endcode
+ */
 Channel *channels = NULL;
 
 /* some buffers for rebuilding channel/nick lists with comma's */
@@ -1144,7 +1153,7 @@ void set_channel_mlock(Client *client, Channel *channel, const char *newmlock, i
  * @in modebuf_in Buffer pointing to mode characters (eg: +snk-l)
  * @in parabuf_in Buffer pointing to all parameters (eg: key 123)
  * @retval Returns 1 if we have valid data to return, 0 if at end of mode line.
- * @section ex1 Example:
+ * @section parse_chanmode_example Example:
  * @code
  * ParseMode pm;
  * int ret;
diff --git a/src/conf.c b/src/conf.c
@@ -69,7 +69,8 @@ static int	_conf_log		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_alias		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_help		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_offchans		(ConfigFile *conf, ConfigEntry *ce);
-static int	_conf_sni			(ConfigFile *conf, ConfigEntry *ce);
+static int	_conf_sni		(ConfigFile *conf, ConfigEntry *ce);
+static int	_conf_security_group	(ConfigFile *conf, ConfigEntry *ce);
 
 /*
  * Validation commands
@@ -101,7 +102,8 @@ static int	_test_log		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_alias		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_help		(ConfigFile *conf, ConfigEntry *ce);
 static int	_test_offchans		(ConfigFile *conf, ConfigEntry *ce);
-static int	_test_sni			(ConfigFile *conf, ConfigEntry *ce);
+static int	_test_sni		(ConfigFile *conf, ConfigEntry *ce);
+static int	_test_security_group	(ConfigFile *conf, ConfigEntry *ce);
 
 /* This MUST be alphabetized */
 static ConfigCommand _ConfigCommands[] = {
@@ -126,6 +128,7 @@ static ConfigCommand _ConfigCommands[] = {
 	{ "oper", 		_conf_oper,		_test_oper	},
 	{ "operclass",		_conf_operclass,	_test_operclass	},
 	{ "require", 		_conf_require,		_test_require	},
+	{ "security-group",	_conf_security_group,	_test_security_group	},
 	{ "set",		_conf_set,		_test_set	},
 	{ "sni",		_conf_sni,		_test_sni	},
 	{ "tld",		_conf_tld,		_test_tld	},
@@ -254,6 +257,7 @@ ConfigItem_include	*conf_include = NULL;
 ConfigItem_blacklist_module	*conf_blacklist_module = NULL;
 ConfigItem_help		*conf_help = NULL;
 ConfigItem_offchans	*conf_offchans = NULL;
+SecurityGroup		*securitygroups = NULL;
 
 MODVAR Configuration		iConf;
 MODVAR Configuration		tempiConf;
@@ -1902,6 +1906,7 @@ void postconf(void)
 	postconf_fixes();
 	do_weird_shun_stuff();
 	isupport_init(); /* for all the 005 values that changed.. */
+	tls_check_expiry(NULL);
 }
 
 int isanyserverlinked(void)
@@ -2072,6 +2077,7 @@ int	init_conf(char *rootconf, int rehash)
 		callbacks_switchover();
 		efunctions_switchover();
 		set_targmax_defaults();
+		set_security_group_defaults();
 		if (rehash)
 		{
 			Hook *h;
@@ -5350,6 +5356,8 @@ int	_conf_allow(ConfigFile *conf, ConfigEntry *ce)
 					allow->flags.useip = 1;
 				else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
 					allow->flags.tls = 1;
+				else if (!strcmp(cepp->ce_varname, "reject-on-auth-failure"))
+					allow->flags.reject_on_auth_failure = 1;
 			}
 		}
 	}
@@ -5545,6 +5553,8 @@ int	_test_allow(ConfigFile *conf, ConfigEntry *ce)
 				{}
 				else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls"))
 				{}
+				else if (!strcmp(cepp->ce_varname, "reject-on-auth-failure"))
+				{}
 				else if (!strcmp(cepp->ce_varname, "sasl"))
 				{
 					config_error("%s:%d: The option allow::options::sasl no longer exists. "
@@ -9317,7 +9327,7 @@ int	_test_offchans(ConfigFile *conf, ConfigEntry *ce)
 		return 1;
 	}
 
-	config_warn("set::oficial-channels is deprecated. It often does not do what you want. "
+	config_warn("set::official-channels is deprecated. It often does not do what you want. "
 	            "You're better of creating a channel, setting all modes, topic, etc. to your liking "
 	            "and then making the channel permanent (MODE #channel +P). "
 	            "The channel will then be stored in a database to preserve it between restarts.");
@@ -10044,6 +10054,91 @@ int     _test_deny(ConfigFile *conf, ConfigEntry *ce)
 	return errors;
 }
 
+int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	if (!ce->ce_vardata)
+	{
+		config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
+			ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+		errors++;
+	} else {
+		if (!strcasecmp(ce->ce_vardata, "unknown-users"))
+		{
+			config_error("%s:%i: The 'unknown-users' group is a special group that is the "
+			             "inverse of 'known-users', you cannot create or adjust it in the "
+			             "config file, as it is created automatically by UnrealIRCd.",
+			             ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			errors++;
+			return errors;
+		}
+		if (!security_group_valid_name(ce->ce_vardata))
+		{
+			config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. "
+			             "Only letters, numbers, underscore and hyphen are allowed.",
+			             ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata);
+			errors++;
+		}
+	}
+
+	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	{
+		if (!strcmp(cep->ce_varname, "webirc"))
+		{
+			CheckNull(cep);
+		} else
+		if (!strcmp(cep->ce_varname, "identified"))
+		{
+			CheckNull(cep);
+		} else
+		if (!strcmp(cep->ce_varname, "reputation-score"))
+		{
+			int v;
+			CheckNull(cep);
+			v = atoi(cep->ce_vardata);
+			if ((v < 1) || (v > 10000))
+			{
+				config_error("%s:%i: security-group::reputation-score needs to be a value of 1-10000",
+					cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
+				errors++;
+			}
+		} else
+		{
+			config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
+				"security-group", cep->ce_varname);
+			errors++;
+			continue;
+		}
+	}
+
+	return errors;
+}
+
+int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
+{
+	ConfigEntry *cep;
+	SecurityGroup *s = add_security_group(ce->ce_vardata, 1);
+
+	for (cep = ce->ce_entries; cep; cep = cep->ce_next)
+	{
+		if (!strcmp(cep->ce_varname, "webirc"))
+			s->webirc = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->ce_varname, "identified"))
+			s->identified = config_checkval(cep->ce_vardata, CFG_YESNO);
+		else if (!strcmp(cep->ce_varname, "reputation-score"))
+			s->reputation_score = atoi(cep->ce_vardata);
+		else if (!strcmp(cep->ce_varname, "priority"))
+		{
+			s->priority = atoi(cep->ce_vardata);
+			DelListItem(s, securitygroups);
+			AddListItemPrio(s, securitygroups, s->priority);
+		}
+	}
+	return 1;
+}
+
 #ifdef USE_LIBCURL
 static void conf_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *inc_key)
 {
@@ -10641,6 +10736,7 @@ void link_generator(void)
 	       "    outgoing {\n"
 	       "        hostname %s;\n"
 	       "        port %d;\n"
+	       "        options { tls; autoconnect; }\n"
 	       "    }\n"
 	       "    password \"%s\" { spkifp; }\n"
 	       "    class servers;\n"
diff --git a/src/crashreport.c b/src/crashreport.c
@@ -13,17 +13,6 @@ extern void StartUnrealAgain(void);
 
 extern char *getosname(void);
 
-
-time_t get_file_time(char *fname)
-{
-	struct stat st;
-	
-	if (stat(fname, &st) != 0)
-		return 0;
-
-	return (time_t)st.st_ctime;
-}
-
 char *find_best_coredump(void)
 {
 	static char best_fname[512];
@@ -560,17 +549,6 @@ int running_interactive(void)
 #define REPORT_ASK		0
 #define REPORT_AUTO		1
 
-
-int getfilesize(char *fname)
-{
-	struct stat st;
-	
-	if (stat(fname, &st) != 0)
-		return -1;
-	
-	return (int)st.st_size;
-}
-
 #define CRASH_REPORT_HOST "crash.unrealircd.org"
 
 SSL_CTX *crashreport_init_ssl(void)
@@ -614,7 +592,7 @@ int crashreport_send(char *fname)
 	int xfr = 0;
 	char *errstr = NULL;
 	
-	filesize = getfilesize(fname);
+	filesize = get_file_size(fname);
 	if (filesize < 0)
 		return 0;
 	
diff --git a/src/hash.c b/src/hash.c
@@ -500,11 +500,21 @@ Client *hash_find_server(const char *server, Client *def)
 	return def;
 }
 
+/** Find a client, user (person), server or channel by name.
+ * If you are looking for "other find functions", then the alphabetical index of functions
+ * at 'f' is your best bet: https://www.unrealircd.org/api/5/globals_func_f.html#index_f
+ * @defgroup FindFunctions Find functions
+ * @{
+ */
+
 /** Find a client by name.
+ * This searches in the list of all types of clients, user/person, servers or an unregistered clients.
+ * If you know what type of client to search for, then use find_server() or find_person() instead!
  * @param name        The name to search for (eg: "nick" or "irc.example.net")
  * @param requester   The client that is searching for this name
  * @note  If 'requester' is a server or NULL, then we also check
  *        the ID table, otherwise not.
+ * @returns If the client is found then the Client is returned, otherwise NULL.
  */
 Client *find_client(char *name, Client *requester)
 {
@@ -525,6 +535,7 @@ Client *find_client(char *name, Client *requester)
  * @param requester   The client searching for the name.
  * @note  If 'requester' is a server or NULL, then we also check
  *        the ID table, otherwise not.
+ * @returns If the server is found then the Client is returned, otherwise NULL.
  */
 Client *find_server(char *name, Client *requester)
 {
@@ -539,13 +550,14 @@ Client *find_server(char *name, Client *requester)
 	return NULL;
 }
 
-/** Find a person.
+/** Find a person (a user).
  * @param name        The name to search for (eg: "nick" or "001ABCDEFG")
  * @param requester   The client that is searching for this name
  * @note  If 'requester' is a server or NULL, then we also check
  *        the ID table, otherwise not.
+ * @returns If the user is found then the Client is returned, otherwise NULL.
  */
-Client *find_person(char *name, Client *requester)
+Client *find_person(char *name, Client *requester) /* TODO: this should have been called find_user() to be consistent */
 {
 	Client *c2ptr;
 
@@ -558,10 +570,12 @@ Client *find_person(char *name, Client *requester)
 }
 
 
-/*
- * hash_find_channel
+/** Find a channel by name.
+ * @param name			The channel name to search for
+ * @param default_result	If the channel is not found, this value is returned.
+ * @returns If the channel exists then the Channel is returned, otherwise default_result is returned.
  */
-Channel *hash_find_channel(char *name, Channel *channel)
+Channel *find_channel(char *name, Channel *default_result)
 {
 	unsigned int hashv;
 	Channel *tmp;
@@ -573,9 +587,11 @@ Channel *hash_find_channel(char *name, Channel *channel)
 		if (smycmp(name, tmp->chname) == 0)
 			return tmp;
 	}
-	return channel;
+	return default_result;
 }
 
+/** @} */
+
 Channel *hash_get_chan_bucket(uint64_t hashv)
 {
 	if (hashv > CHAN_HASH_TABLE_SIZE)
@@ -963,18 +979,14 @@ EVENT(throttling_check_expire)
 		char *p = serveropts + strlen(serveropts);
 		Module *mi;
 		t = TStime();
-		if (!Hooks[17] && strchr(serveropts, 'm'))
+		if (!Hooks[HOOKTYPE_USERMSG] && strchr(serveropts, 'm'))
 		{ p = strchr(serveropts, 'm'); *p = '\0'; }
-		if (!Hooks[18] && strchr(serveropts, 'M'))
+		if (!Hooks[HOOKTYPE_CHANMSG] && strchr(serveropts, 'M'))
 		{ p = strchr(serveropts, 'M'); *p = '\0'; }
-		if (!Hooks[49] && !Hooks[51] && strchr(serveropts, 'R'))
-		{ p = strchr(serveropts, 'R'); *p = '\0'; }
-		if (Hooks[17] && !strchr(serveropts, 'm'))
+		if (Hooks[HOOKTYPE_USERMSG] && !strchr(serveropts, 'm'))
 			*p++ = 'm';
-		if (Hooks[18] && !strchr(serveropts, 'M'))
+		if (Hooks[HOOKTYPE_CHANMSG] && !strchr(serveropts, 'M'))
 			*p++ = 'M';
-		if ((Hooks[49] || Hooks[51]) && !strchr(serveropts, 'R'))
-			*p++ = 'R';
 		*p = '\0';
 		for (mi = Modules; mi; mi = mi->next)
 			if (!(mi->options & MOD_OPT_OFFICIAL))
diff --git a/src/ircd.c b/src/ircd.c
@@ -96,7 +96,7 @@ void s_die()
 #else
 	unload_all_modules();
 	unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
-	exit(-1);
+	exit(0);
 #endif
 }
 
@@ -269,14 +269,7 @@ EVENT(garbage_collect)
 		loop.do_garbage_collect = 0;
 }
 
-/*
-** try_connections
-**
-**	Scan through configuration and try new connections.
-**	Returns the calendar time when the next call to this
-**	function should be made latest. (No harm done if this
-**	is called earlier or later...)
-*/
+/** Perform autoconnect to servers that are not linked yet. */
 EVENT(try_connections)
 {
 	ConfigItem_link *aconf;
@@ -287,7 +280,7 @@ EVENT(try_connections)
 
 	for (aconf = conf_link; aconf; aconf = aconf->next)
 	{
-		/* We're only interested in autoconnect blocks that are valid (and ignore temporary link blocks) */
+		/* We're only interested in autoconnect blocks that are valid. Also, we ignore temporary link blocks. */
 		if (!(aconf->outgoing.options & CONNECT_AUTO) || !aconf->outgoing.hostname || (aconf->flag.temporary == 1))
 			continue;
 
@@ -296,6 +289,7 @@ EVENT(try_connections)
 		/* Only do one connection attempt per <connfreq> seconds (for the same server) */
 		if ((aconf->hold > TStime()))
 			continue;
+
 		confrq = class->connfreq;
 		aconf->hold = TStime() + confrq;
 
@@ -380,8 +374,7 @@ int match_tkls(Client *client)
 	return 0;
 }
 
-/** Time out connections that are still in handshake.
- */
+/** Time out connections that are still in handshake. */
 EVENT(handshake_timeout)
 {
 	Client *client, *next;
@@ -466,11 +459,7 @@ void check_ping(Client *client)
 	return;
 }
 
-/*
- * Check registered connections for PING timeout.
- * XXX: also does some other stuff still, need to sort this.  --nenolod
- * Perhaps it would be wise to ping servers as well mr nenolod, just an idea -- Syzop
- */
+/** Check registered connections for ping timeout. Also, check for server bans. */
 EVENT(check_pings)
 {
 	Client *client, *next;
@@ -493,6 +482,7 @@ EVENT(check_pings)
 	/* done */
 }
 
+/** Check for clients that are pending to be terminated */
 EVENT(check_deadsockets)
 {
 	Client *client, *next;
@@ -550,18 +540,10 @@ static int bad_command(const char *argv0)
 	if (!argv0)
 		argv0 = "unrealircd";
 
-	(void)printf
-	    ("Usage: %s [-f <config>] [-F]\n"
-	     "\n"
-	     "UnrealIRCd\n"
-	     " -f <config>     Load configuration from <config> instead of the default\n"
-	     "                 (%s).\n"
-	     " -F              Don't fork() when starting up. Use this when running\n"
-	     "                 UnrealIRCd under gdb or when playing around with settings\n"
-	     "                 on a non-production setup.\n"
-	     "\n",
-	     argv0, CONFIGFILE);
-	(void)printf("Server not started\n\n");
+	printf("ERROR: Incorrect command line argument encountered.\n"
+	       "This is the unrealircd BINARY. End-users should NOT call this binary directly.\n"
+	       "Please run the SCRIPT instead: %s/unrealircd\n", SCRIPTDIR);
+	printf("Server not started\n\n");
 #else
 	if (!IsService) {
 		MessageBox(NULL,
@@ -1098,7 +1080,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 			  bootopt |= BOOT_TTY;
 			  break;
 		  case 'v':
-			  (void)printf("%s build %s\n", version, buildid);
+			  (void)printf("%s\n", version);
 #else
 		  case 'v':
 			  if (!IsService) {
diff --git a/src/misc.c b/src/misc.c
@@ -810,7 +810,6 @@ void exit_client(Client *client, MessageTag *recv_mtags, char *comment)
 	exit_one_client(client, recv_mtags, comment);
 
 	free_message_tags(mtags_generated);
-	
 }
 
 /** Initialize the (quite useless) IRC statistics */
@@ -851,7 +850,7 @@ void verify_opercount(Client *orig, char *tag)
 int valid_host(char *host)
 {
 	char *p;
-	
+
 	if (strlen(host) > HOSTLEN)
 		return 0; /* too long hosts are invalid too */
 
@@ -1032,7 +1031,7 @@ int is_autojoin_chan(char *chname)
 			if (!strcasecmp(name, chname))
 				return 1;
 	}
-	
+
 	if (AUTO_JOIN_CHANS)
 	{
 		strlcpy(buf, AUTO_JOIN_CHANS, sizeof(buf));
@@ -1069,7 +1068,7 @@ int char_to_channelflag(char c)
 int mixed_network(void)
 {
 	Client *client;
-	
+
 	list_for_each_entry(client, &server_list, special_node)
 	{
 		if (!IsServer(client) || IsULine(client))
@@ -1083,7 +1082,7 @@ int mixed_network(void)
 void unreal_delete_masks(ConfigItem_mask *m)
 {
 	ConfigItem_mask *m_next;
-	
+
 	for (; m; m = m_next)
 	{
 		m_next = m->next;
@@ -1104,7 +1103,7 @@ static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce)
 		safe_strdup(m->mask, ce->ce_vardata);
 	else
 		safe_strdup(m->mask, ce->ce_varname);
-	
+
 	add_ListItem((ListStruct *)m, (ListStruct **)head);
 }
 
@@ -1137,7 +1136,7 @@ int unreal_mask_match(Client *client, ConfigItem_mask *m)
 				return 1;
 		}
 	}
-	
+
 	return 0;
 }
 
@@ -1191,7 +1190,7 @@ int swhois_add(Client *client, char *tag, int priority, char *swhois, Client *fr
 	safe_strdup(s->setby, tag);
 	s->priority = priority;
 	AddListItemPrio(s, client->user->swhois, s->priority);
-	
+
 	sendto_server(skip, 0, PROTO_EXTSWHOIS, NULL, ":%s SWHOIS %s :%s",
 		from->id, client->id, swhois);
 
@@ -1215,11 +1214,11 @@ int swhois_delete(Client *client, char *tag, char *swhois, Client *from, Client 
 {
 	SWhois *s, *s_next;
 	int ret = -1; /* default to 'not found' */
-	
+
 	for (s = client->user->swhois; s; s = s_next)
 	{
 		s_next = s->next;
-		
+
 		/* If ( same swhois or "*" ) AND same tag */
 		if ( ((!strcmp(s->line, swhois) || !strcmp(swhois, "*")) &&
 		    !strcmp(s->setby, tag)))
@@ -1234,7 +1233,7 @@ int swhois_delete(Client *client, char *tag, char *swhois, Client *from, Client 
 
 			sendto_server(skip, PROTO_EXTSWHOIS, 0, NULL, ":%s SWHOIS %s - %s %d :%s",
 				from->id, client->id, tag, 0, swhois);
-			
+
 			ret = 0;
 		}
 	}
@@ -1893,6 +1892,41 @@ int filename_has_suffix(const char *fname, const char *suffix)
 	return 0;
 }
 
+/** Check if the specified file exists */
+int file_exists(char *file)
+{
+	FILE *fd;
+
+	fd = fopen(file, "r");
+	if (!fd)
+		return 0;
+
+	fclose(fd);
+	return 1;
+}
+
+/** Get the file creation time */
+time_t get_file_time(char *fname)
+{
+	struct stat st;
+
+	if (stat(fname, &st) != 0)
+		return 0;
+
+	return (time_t)st.st_ctime;
+}
+
+/** Get the size of a file */
+long get_file_size(char *fname)
+{
+	struct stat st;
+
+	if (stat(fname, &st) != 0)
+		return -1;
+
+	return (long)st.st_size;
+}
+
 /** Add a line to a MultiLine list */
 void addmultiline(MultiLine **l, char *line)
 {
diff --git a/src/modulemanager.c b/src/modulemanager.c
@@ -1643,11 +1643,33 @@ void mm_parse_c_file(int argc, char *args[])
 	exit(0);
 }
 
+void mm_self_test(void)
+{
+	char name[512];
+	snprintf(name, sizeof(name), "%s/src/modules/third", BUILDDIR);
+	if (file_exists(name))
+		return;
+	if (!file_exists(BUILDDIR))
+	{
+		fprintf(stderr, "ERROR: Directory %s does not exist.\n"
+				"The UnrealIRCd source is required for the module manager to work!\n",
+				BUILDDIR);
+	} else {
+		fprintf(stderr, "ERROR: Directory %s exists, but %s does not.\n"
+		                "The UnrealIRCd source is required for the module manager to work.\n"
+		                "It seems you only have a partial build directory??\n",
+		                BUILDDIR, name);
+	}
+	exit(-1);
+}
+
 void modulemanager(int argc, char *args[])
 {
 	if (!args[0])
 		mm_usage();
 
+	mm_self_test();
+
 	/* The following operations do not require reading
 	 * of the repository list and are always available:
 	 */
diff --git a/src/modules/chanmodes/secureonly.c b/src/modules/chanmodes/secureonly.c
@@ -34,7 +34,6 @@ Cmode_t EXTCMODE_SECUREONLY;
 
 int secureonly_check_join(Client *client, Channel *channel, char *key, char *parv[]);
 int secureonly_channel_sync (Channel *channel, int merge, int removetheirs, int nomode);
-int secureonly_send_channel(Client *client, Channel *channel);
 int secureonly_check_secure(Channel *channel);
 int secureonly_check_sajoin(Client *target, Channel *channel, Client *requester);
 int secureonly_specialcheck(Client *client, Channel *channel, char *parv[]);
@@ -58,7 +57,6 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, secureonly_check_join);
 	HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_SYNCED, 0, secureonly_channel_sync);
 	HookAdd(modinfo->handle, HOOKTYPE_IS_CHANNEL_SECURE, 0, secureonly_check_secure);
-	HookAdd(modinfo->handle, HOOKTYPE_SEND_CHANNEL, 0, secureonly_send_channel);
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SAJOIN, 0, secureonly_check_sajoin);
 
 
@@ -165,15 +163,6 @@ int secureonly_channel_sync(Channel *channel, int merge, int removetheirs, int n
 	return 0;
 }
 
-int secureonly_send_channel(Client *client, Channel *channel)
-{
-	if (IsSecureOnly(channel))
-		if (!IsSecure(client))
-			return HOOK_DENY;
-
-	return HOOK_CONTINUE;
-}
-
 int secureonly_check_sajoin(Client *target, Channel *channel, Client *requester)
 {
 	if (IsSecureOnly(channel) && !IsSecure(target))
diff --git a/src/modules/connthrottle.c b/src/modules/connthrottle.c
@@ -67,8 +67,6 @@ UCounter *ucounter = NULL;
 
 #define MSG_THROTTLE "THROTTLE"
 
-#define GetReputation(client)     (moddata_client_get(client, "reputation") ? atoi(moddata_client_get(client, "reputation")) : 0)
-
 /* Forward declarations */
 int ct_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int ct_config_posttest(int *errs);
diff --git a/src/modules/extbans/Makefile.in b/src/modules/extbans/Makefile.in
@@ -34,7 +34,7 @@ INCLUDES = ../../include/channel.h \
 R_MODULES= \
 	join.so quiet.so nickchange.so inchannel.so realname.so \
 	account.so operclass.so certfp.so textban.so msgbypass.so \
-	timedban.so partmsg.so
+	timedban.so partmsg.so securitygroup.so
 
 MODULES=$(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
@@ -102,3 +102,7 @@ timedban.so: timedban.c $(INCLUDES)
 partmsg.so: partmsg.c $(INCLUDES)
 	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
 		-o partmsg.so partmsg.c
+
+securitygroup.so: securitygroup.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o securitygroup.so securitygroup.c
diff --git a/src/modules/extbans/securitygroup.c b/src/modules/extbans/securitygroup.c
@@ -0,0 +1,141 @@
+/*
+ * Extended ban to ban based on security groups such as "unknown-users"
+ * (C) Copyright 2020 Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * 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
+= {
+	"extbans/securitygroup",
+	"4.2",
+	"ExtBan ~G - Ban based on security-group",
+	"UnrealIRCd Team",
+	"unrealircd-5",
+};
+
+/* Forward declarations */
+char *extban_securitygroup_conv_param(char *para);
+int extban_securitygroup_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2);
+int extban_securitygroup_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+	
+	req.flag = 'G';
+	req.conv_param = extban_securitygroup_conv_param;
+	req.is_ok = extban_securitygroup_is_ok;
+	req.is_banned = extban_securitygroup_is_banned;
+	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("could not register extended ban type ~G");
+		return MOD_FAILED;
+	}
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Helper function for extban_securitygroup_is_ok() and extban_securitygroup_conv_param()
+ * to do ban validation.
+ */
+int extban_securitygroup_generic(char *para, int strict)
+{
+	char *mask;
+
+	mask = para+3;
+
+	/* ! at the start means negative match */
+	if (*mask == '!')
+		mask++;
+
+	/* Check if the rest of the security group name is valid */
+	if (strict)
+	{
+		if (!security_group_exists(mask))
+			return 0; /* security group does not exist */
+	} else {
+		if (!security_group_valid_name(mask))
+			return 0; /* invalid characters or too long */
+	}
+
+	if (!*mask)
+		return 0; /* don't allow "~G:" nor "~G:!" */
+
+	if (strlen(mask) > SECURITYGROUPLEN + 3)
+		mask[SECURITYGROUPLEN + 3] = '\0';
+
+	return 1;
+}
+
+int extban_securitygroup_is_ok(Client *client, Channel *channel, char *para, int checkt, int what, int what2)
+{
+	if (MyUser(client) && (what == MODE_ADD) && (checkt == EXBCHK_PARAM))
+	{
+		char banbuf[SECURITYGROUPLEN+8];
+		strlcpy(banbuf, para, sizeof(banbuf));
+		if (!extban_securitygroup_generic(banbuf, 1))
+		{
+			SecurityGroup *s;
+			sendnotice(client, "ERROR: Unknown security-group '%s'. Syntax: +b ~G:securitygroup or +b ~G:!securitygroup", para+3);
+			sendnotice(client, "Available security groups:");
+			for (s = securitygroups; s; s = s->next)
+				sendnotice(client, "%s", s->name);
+			sendnotice(client, "unknown-users");
+			sendnotice(client, "End of security group list.");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+/** Security group extban - conv_param */
+char *extban_securitygroup_conv_param(char *para)
+{
+	static char retbuf[SECURITYGROUPLEN + 8];
+
+	strlcpy(retbuf, para, sizeof(retbuf));
+	if (!extban_securitygroup_generic(retbuf, 0))
+		return NULL;
+
+	return retbuf;
+}
+
+/** Is the user banned by ~G:something ? */
+int extban_securitygroup_is_banned(Client *client, Channel *channel, char *banin, int type, char **msg, char **errmsg)
+{
+	char *ban = banin+3;
+
+	if (*ban == '!')
+		return !user_allowed_by_security_group_name(client, ban+1);
+	return user_allowed_by_security_group_name(client, ban);
+}
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -1367,6 +1367,9 @@ int paracount_for_chanmode_from_server(Client *client, u_int what, char mode)
 	if (mode == '&')
 		return 0; /* & indicates bounce, it is not an actual mode character */
 
+	if (mode == 'F')
+		return (what == MODE_ADD) ? 1 : 0; /* Future compatibility */
+
 	/* If we end up here it means we have no idea if it is a parameter-eating or paramless
 	 * channel mode. That's actually pretty bad. This shouldn't happen since CHANMODES=
 	 * is sent since 2003 and the (often also required) EAUTH PROTOCTL is in there since 2010.
diff --git a/src/modules/nick.c b/src/modules/nick.c
@@ -315,8 +315,8 @@ CMD_FUNC(cmd_nick_local)
 		{
 			client->local->since += 4; /* lag them up */
 			sendnumeric(client, ERR_ERRONEUSNICKNAME, nick, tklban->ptr.nameban->reason);
-			sendto_snomask(SNO_QLINE, "Forbidding Q-lined nick %s from %s.",
-			    nick, get_client_name(cptr, FALSE));
+			sendto_snomask(SNO_QLINE, "Forbidding Q-lined nick %s from %s (%s)",
+			    nick, get_client_name(cptr, FALSE), tklban->ptr.nameban->reason);
 			return;	/* NICK message ignored */
 		}
 		/* fallthrough for ircops that have sufficient privileges */
@@ -866,6 +866,12 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char
 		/* Check G/Z lines before shuns -- kill before quite -- codemastr */
 		if (find_tkline_match(client, 0))
 		{
+			if (!IsDead(client) && client->local->class)
+			{
+				/* Fix client count bug, in case that it was a hold such as via authprompt */
+				client->local->class->clients--;
+				client->local->class = NULL;
+			}
 			ircstats.is_ref++;
 			return 0;
 		}
@@ -892,7 +898,17 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char
 		{
 			i = (*(h->func.intfunc))(client);
 			if (i == HOOK_DENY)
+			{
+				if (!IsDead(client) && client->local->class)
+				{
+					/* Fix client count bug, in case that
+					 * the HOOK_DENY was only meant temporarily.
+					 */
+					client->local->class->clients--;
+					client->local->class = NULL;
+				}
 				return 0;
+			}
 			if (i == HOOK_ALLOW)
 				break;
 		}
@@ -1316,12 +1332,9 @@ int AllowClient(Client *client, char *username)
 
 	for (aconf = conf_allow; aconf; aconf = aconf->next)
 	{
-		if (!aconf->hostname || !aconf->ip)
-			goto attach;
-		if (aconf->auth && !client->local->passwd && !moddata_client_get(client, "certfp"))
-			continue;
 		if (aconf->flags.tls && !IsSecure(client))
 			continue;
+
 		if (hp && hp->h_name)
 		{
 			hname = hp->h_name;
@@ -1376,8 +1389,21 @@ int AllowClient(Client *client, char *username)
 				goto attach;
 		}
 
-		continue;
+		continue; /* No match */
 	attach:
+		/* Check authentication */
+		if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
+		{
+			/* Incorrect password/authentication - but was is it required? */
+			if (aconf->flags.reject_on_auth_failure)
+			{
+				exit_client(client, NULL, iConf.reject_message_unauthorized);
+				return 0;
+			} else {
+				continue; /* Continue (this is the default behavior) */
+			}
+		}
+
 		if (!aconf->flags.noident)
 			SetUseIdent(client);
 		if (!aconf->flags.useip && hp)
@@ -1393,12 +1419,6 @@ int AllowClient(Client *client, char *username)
 			return 0;
 		}
 
-		if (aconf->auth && !Auth_Check(client, aconf->auth, client->local->passwd))
-		{
-			/* Always continue if password was wrong. */
-			continue;
-		}
-
 		if (!((aconf->class->clients + 1) > aconf->class->maxclients))
 		{
 			client->local->class = aconf->class;
diff --git a/src/modules/protoctl.c b/src/modules/protoctl.c
@@ -343,9 +343,9 @@ CMD_FUNC(cmd_protoctl)
 				         (long long)(TStime() - t));
 				snprintf(msg, sizeof(msg),
 				         "Rejecting link %s: our clock is %lld seconds ahead. "
-				         "Correct time is very important in IRC. Please "
-				         "verify the clock on both %s (them) and %s (us), "
-				         "fix it and then try linking again",
+				         "Please verify the clock on both %s (them) and %s (us). "
+				         "Correct time is very important for IRC servers, "
+				         "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
 				         get_client_name(client, TRUE),
 				         (long long)(TStime() - t),
 				         client->name, me.name);
@@ -359,9 +359,9 @@ CMD_FUNC(cmd_protoctl)
 				         (long long)(t - TStime()));
 				snprintf(msg, sizeof(msg),
 				         "Rejecting link %s: our clock is %lld seconds behind. "
-				         "Correct time is very important in IRC. Please "
-				         "verify the clock on both %s (them) and %s (us), "
-				         "fix it and then try linking again",
+				         "Please verify the clock on both %s (them) and %s (us). "
+				         "Correct time is very important for IRC servers, "
+				         "see https://www.unrealircd.org/docs/FAQ#fix-your-clock",
 					get_client_name(client, TRUE),
 					(long long)(t - TStime()),
 					client->name, me.name);
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -682,6 +682,107 @@ int count_reputation_records(void)
 	return total;
 }
 
+void reputation_channel_query(Client *client, Channel *channel)
+{
+	Member *m;
+	char buf[512];
+	char tbuf[256];
+	char **nicks;
+	int *scores;
+	int cnt = 0, i, j;
+	ReputationEntry *e;
+
+	sendtxtnumeric(client, "Users and reputation scores for %s:", channel->chname);
+
+	/* Step 1: build a list of nicks and their reputation */
+	nicks = safe_alloc((channel->users+1) * sizeof(char *));
+	scores = safe_alloc((channel->users+1) * sizeof(int));
+	for (m = channel->members; m; m = m->next)
+	{
+		nicks[cnt] = m->client->name;
+		if (m->client->ip)
+		{
+			e = find_reputation_entry(m->client->ip);
+			if (e)
+				scores[cnt] = e->score;
+		}
+		if (++cnt > channel->users)
+		{
+			sendto_ops("[BUG] reputation_channel_query() expected %d users but %d (or more) were present in %s",
+				channel->users, cnt, channel->chname);
+#ifdef DEBUGMODE
+			abort();
+#endif
+			break; /* safety net */
+		}
+	}
+
+	/* Step 2: lazy selection sort */
+	for (i = 0; i < cnt && nicks[i]; i++)
+	{
+		for (j = i+1; j < cnt && nicks[j]; j++)
+		{
+			if (scores[i] < scores[j])
+			{
+				char *nick_tmp;
+				int score_tmp;
+				nick_tmp = nicks[i];
+				score_tmp = scores[i];
+				nicks[i] = nicks[j];
+				scores[i] = scores[j];
+				nicks[j] = nick_tmp;
+				scores[j] = score_tmp;
+			}
+		}
+	}
+
+	/* Step 3: send the (ordered) list to the user */
+	*buf = '\0';
+	for (i = 0; i < cnt && nicks[i]; i++)
+	{
+		snprintf(tbuf, sizeof(tbuf), "%s\00314(%d)\003 ", nicks[i], scores[i]);
+		if ((strlen(tbuf)+strlen(buf) > 400) || !nicks[i+1])
+		{
+			sendtxtnumeric(client, "%s%s", buf, tbuf);
+			*buf = '\0';
+		} else {
+			strlcat(buf, tbuf, sizeof(buf));
+		}
+	}
+	sendtxtnumeric(client, "End of list.");
+	safe_free(nicks);
+	safe_free(scores);
+}
+
+void reputation_list_query(Client *client, int maxscore)
+{
+	Client *target;
+	ReputationEntry *e;
+
+	sendtxtnumeric(client, "Users and reputation scores <%d:", maxscore);
+
+	list_for_each_entry(target, &client_list, client_node)
+	{
+		int score = 0;
+
+		if (!IsUser(target) || IsULine(target) || !target->ip)
+			continue;
+
+		e = find_reputation_entry(target->ip);
+		if (e)
+			score = e->score;
+		if (score >= maxscore)
+			continue;
+		sendtxtnumeric(client, "%s!%s@%s [%s] \017(score: %d)",
+			target->name,
+			target->user->username,
+			target->user->realhost,
+			target->ip,
+			score);
+	}
+	sendtxtnumeric(client, "End of list.");
+}
+
 CMD_FUNC(reputation_user_cmd)
 {
 	ReputationEntry *e;
@@ -709,13 +810,45 @@ CMD_FUNC(reputation_user_cmd)
 		}
 		sendnotice(client, "Current number of records (IP's): %d", count_reputation_records());
 		sendnotice(client, "-");
-		sendnotice(client, "For more specific information, use: /REPUTATION [nick|IP-address]");
+		sendnotice(client, "Available commands:");
+		sendnotice(client, "/REPUTATION [nick]     Show reputation info about nick name");
+		sendnotice(client, "/REPUTATION [ip]       Show reputation info about IP address");
+		sendnotice(client, "/REPUTATION [channel]  List users in channel along with their reputation score");
+		sendnotice(client, "/REPUTATION <NN        List users with reputation score below value NN");
 		return;
 	}
 	
 	if (strchr(parv[1], '.') || strchr(parv[1], ':'))
 	{
 		ip = parv[1];
+	} else
+	if (parv[1][0] == '#')
+	{
+		Channel *channel = find_channel(parv[1], NULL);
+		if (!channel)
+		{
+			sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+			return;
+		}
+		/* corner case: ircop without proper permissions and not in channel */
+		if (!ValidatePermissionsForPath("channel:see:names:invisible",client,NULL,NULL,NULL) && !get_access(client,channel))
+		{
+			sendnumeric(client, ERR_NOTONCHANNEL, channel->chname);
+			return;
+		}
+		reputation_channel_query(client, channel);
+		return;
+	} else
+	if (parv[1][0] == '<')
+	{
+		int max = atoi(parv[1] + 1);
+		if (max < 1)
+		{
+			sendnotice(client, "REPUTATION: Invalid search value specified. Use for example '/REPUTATION <5' to search on less-than-five");
+			return;
+		}
+		reputation_list_query(client, max);
+		return;
 	} else {
 		Client *target = find_person(parv[1], NULL);
 		if (!target)
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
@@ -27,8 +27,6 @@ ModuleHeader MOD_HEADER = {
 	"unrealircd-5",
 };
 
-#define GetReputation(client) (moddata_client_get(client, "reputation") ? atoi(moddata_client_get(client, "reputation")) : 0)
-
 typedef struct RestrictedCommand RestrictedCommand;
 struct RestrictedCommand {
 	RestrictedCommand *prev, *next;
diff --git a/src/modules/stats.c b/src/modules/stats.c
@@ -462,22 +462,25 @@ int stats_denylinkall(Client *client, char *para)
 
 int stats_gline(Client *client, char *para)
 {
-	tkl_stats(client, TKL_GLOBAL|TKL_KILL, para);
-	tkl_stats(client, TKL_GLOBAL|TKL_ZAP, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_GLOBAL|TKL_KILL, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_ZAP, para, &cnt);
 	return 0;
 }
 
 int stats_spamfilter(Client *client, char *para)
 {
-	tkl_stats(client, TKL_SPAMF, para);
-	tkl_stats(client, TKL_GLOBAL|TKL_SPAMF, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_SPAMF, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_SPAMF, para, &cnt);
 	return 0;
 }
 
 int stats_except(Client *client, char *para)
 {
-	tkl_stats(client, TKL_EXCEPTION, para);
-	tkl_stats(client, TKL_EXCEPTION|TKL_GLOBAL, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_EXCEPTION, para, &cnt);
+	tkl_stats(client, TKL_EXCEPTION|TKL_GLOBAL, para, &cnt);
 	return 0;
 }
 
@@ -564,8 +567,9 @@ int stats_port(Client *client, char *para)
 
 int stats_bannick(Client *client, char *para)
 {
-	tkl_stats(client, TKL_NAME, para);
-	tkl_stats(client, TKL_GLOBAL|TKL_NAME, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_NAME, para, &cnt);
+	tkl_stats(client, TKL_GLOBAL|TKL_NAME, para, &cnt);
 	return 0;
 }
 
@@ -699,8 +703,9 @@ int stats_denylinkauto(Client *client, char *para)
 
 int stats_kline(Client *client, char *para)
 {
-	tkl_stats(client, TKL_KILL, NULL);
-	tkl_stats(client, TKL_ZAP, NULL);
+	int cnt = 0;
+	tkl_stats(client, TKL_KILL, NULL, &cnt);
+	tkl_stats(client, TKL_ZAP, NULL, &cnt);
 	return 0;
 }
 
@@ -720,7 +725,8 @@ int stats_banrealname(Client *client, char *para)
 
 int stats_sqline(Client *client, char *para)
 {
-	tkl_stats(client, TKL_NAME|TKL_GLOBAL, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_NAME|TKL_GLOBAL, para, &cnt);
 	return 0;
 }
 
@@ -741,7 +747,8 @@ int stats_chanrestrict(Client *client, char *para)
 
 int stats_shun(Client *client, char *para)
 {
-	tkl_stats(client, TKL_GLOBAL|TKL_SHUN, para);
+	int cnt = 0;
+	tkl_stats(client, TKL_GLOBAL|TKL_SHUN, para, &cnt);
 	return 0;
 }
 
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
@@ -234,8 +234,8 @@ int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Member
 	if (!MyUser(client))
 		return HOOK_CONTINUE;
 
-	/* Really, only IRCOps override */
-	if (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL))
+	/* IRCOps and U-Lines override */
+	if (IsULine(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
 		return HOOK_CONTINUE;
 
 	what = sendtypetowhat(sendtype);
@@ -280,8 +280,8 @@ int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text
 	if (!MyUser(target))
 		return HOOK_CONTINUE;
 
-	/* Really, only IRCOps override */
-	if (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL))
+	/* IRCOps and U-Lines override */
+	if (IsULine(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
 		return HOOK_CONTINUE;
 
 	what = sendtypetowhat(sendtype);
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -39,6 +39,8 @@ int tkl_config_test_ban(ConfigFile *, ConfigEntry *, int, int *);
 int tkl_config_run_ban(ConfigFile *, ConfigEntry *, int);
 int tkl_config_test_except(ConfigFile *, ConfigEntry *, int, int *);
 int tkl_config_run_except(ConfigFile *, ConfigEntry *, int);
+int tkl_config_test_set(ConfigFile *, ConfigEntry *, int, int *);
+int tkl_config_run_set(ConfigFile *, ConfigEntry *, int);
 CMD_FUNC(cmd_gline);
 CMD_FUNC(cmd_shun);
 CMD_FUNC(cmd_tempshun);
@@ -76,7 +78,7 @@ int _find_shun(Client *client);
 int _find_spamfilter_user(Client *client, int flags);
 TKL *_find_qline(Client *client, char *nick, int *ishold);
 TKL *_find_tkline_match_zap(Client *client);
-void _tkl_stats(Client *client, int type, char *para);
+void _tkl_stats(Client *client, int type, char *para, int *cnt);
 void _tkl_sync(Client *client);
 CMD_FUNC(_cmd_tkl);
 int _place_host_ban(Client *client, BanAction action, char *reason, long duration);
@@ -142,12 +144,15 @@ TKLTypeTable tkl_types[] = {
 };
 #define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, unknown-data-flood, antirandom, antimixedutf8, ban-version"
 
+int max_stats_matches = 1000;
+
 MOD_TEST()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_spamfilter);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_ban);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_except);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_set);
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_TYPETOCHAR, TO_INTFUNC(_tkl_typetochar));
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_CHARTOTYPE, TO_INTFUNC(_tkl_chartotype));
@@ -190,6 +195,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_match_spamfilter);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_set);
 	CommandAdd(modinfo->handle, "GLINE", cmd_gline, 3, CMD_OPER);
 	CommandAdd(modinfo->handle, "SHUN", cmd_shun, 3, CMD_OPER);
 	CommandAdd(modinfo->handle, "TEMPSHUN", cmd_tempshun, 2, CMD_OPER);
@@ -902,6 +908,44 @@ int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	return 1;
 }
 
+int tkl_config_test_set(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
+{
+	int errors = 0;
+
+	/* We are only interested in set { } blocks */
+	if (configtype != CONFIG_SET)
+		return 0;
+
+	if (!strcmp(ce->ce_varname, "max-stats-matches"))
+	{
+		if (!ce->ce_vardata)
+		{
+			config_error("%s:%i: set::max-stats-matches: no value specified",
+				ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
+			errors++;
+		}
+		// allow any other value, including 0 and negative.
+		*errs = errors;
+		return errors ? -1 : 1;
+	}
+	return 0;
+}
+
+int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
+{
+	/* We are only interested in set { } blocks */
+	if (configtype != CONFIG_SET)
+		return 0;
+
+	if (!strcmp(ce->ce_varname, "max-stats-matches"))
+	{
+		max_stats_matches = atoi(ce->ce_vardata);
+		return 1;
+	}
+
+	return 0;
+}
+
 /** Return unique spamfilter id for TKL */
 char *spamfilter_id(TKL *tk)
 {
@@ -1269,7 +1313,7 @@ void cmd_tkl_line(Client *client, int parc, char *parv[], char *type)
 		mask++;
 	}
 
-	if (strchr(mask, '!'))
+	if ((*mask != '~') && strchr(mask, '!'))
 	{
 		sendnotice(client, "[error] Cannot have '!' in masks.");
 		return;
@@ -1590,7 +1634,7 @@ CMD_FUNC(cmd_eline)
 		reason = parv[4];
 	}
 
-	if (strchr(mask, '!'))
+	if ((*mask != '~') && strchr(mask, '!'))
 	{
 		sendnotice(client, "[error] Cannot have '!' in masks.");
 		return;
@@ -3298,7 +3342,7 @@ static void parse_stats_params(char *para, TKLFlag *flag)
 /** Does this TKL entry match the search terms?
  * This is a helper function for tkl_stats().
  */
-void tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, TKL *tkl)
+int tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, TKL *tkl)
 {
 	/***** First, handle the selection ******/
 
@@ -3306,66 +3350,66 @@ void tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, 
 	{
 		if (tklflags->flags & BY_SETBY)
 			if (!match_simple(tklflags->set_by, tkl->set_by))
-				return;
+				return 0;
 		if (tklflags->flags & NOT_BY_SETBY)
 			if (match_simple(tklflags->set_by, tkl->set_by))
-				return;
+				return 0;
 		if (TKLIsServerBan(tkl))
 		{
 			if (tklflags->flags & BY_MASK)
 			{
 				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & NOT_BY_MASK)
 			{
 				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & BY_REASON)
 				if (!match_simple(tklflags->reason, tkl->ptr.serverban->reason))
-					return;
+					return 0;
 			if (tklflags->flags & NOT_BY_REASON)
 				if (match_simple(tklflags->reason, tkl->ptr.serverban->reason))
-					return;
+					return 0;
 		} else
 		if (TKLIsNameBan(tkl))
 		{
 			if (tklflags->flags & BY_MASK)
 			{
 				if (!match_simple(tklflags->mask, tkl->ptr.nameban->name))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & NOT_BY_MASK)
 			{
 				if (match_simple(tklflags->mask, tkl->ptr.nameban->name))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & BY_REASON)
 				if (!match_simple(tklflags->reason, tkl->ptr.nameban->reason))
-					return;
+					return 0;
 			if (tklflags->flags & NOT_BY_REASON)
 				if (match_simple(tklflags->reason, tkl->ptr.nameban->reason))
-					return;
+					return 0;
 		} else
 		if (TKLIsBanException(tkl))
 		{
 			if (tklflags->flags & BY_MASK)
 			{
 				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & NOT_BY_MASK)
 			{
 				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
-					return;
+					return 0;
 			}
 			if (tklflags->flags & BY_REASON)
 				if (!match_simple(tklflags->reason, tkl->ptr.banexception->reason))
-					return;
+					return 0;
 			if (tklflags->flags & NOT_BY_REASON)
 				if (match_simple(tklflags->reason, tkl->ptr.banexception->reason))
-					return;
+					return 0;
 		}
 	}
 
@@ -3444,16 +3488,24 @@ void tkl_stats_matcher(Client *client, int type, char *para, TKLFlag *tklflags, 
 			   tkl->ptr.banexception->bantypes,
 			   (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
 			   (TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
+	} else
+	{
+		/* That's weird, unknown TKL type */
+		return 0;
 	}
+	return 1;
 }
 
 /* TKL Stats. This is used by /STATS gline and all the others */
-void _tkl_stats(Client *client, int type, char *para)
+void _tkl_stats(Client *client, int type, char *para, int *cnt)
 {
 	TKL *tk;
 	TKLFlag tklflags;
 	int index, index2;
 
+	if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+		return;
+
 	if (!BadPtr(para))
 		parse_stats_params(para, &tklflags);
 
@@ -3467,7 +3519,16 @@ void _tkl_stats(Client *client, int type, char *para)
 			{
 				if (type && tk->type != type)
 					continue;
-				tkl_stats_matcher(client, type, para, &tklflags, tk);
+				if (tkl_stats_matcher(client, type, para, &tklflags, tk))
+				{
+					*cnt += 1;
+					if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+					{
+						sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
+						sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
+						return;
+					}
+				}
 			}
 		}
 	}
@@ -3479,7 +3540,16 @@ void _tkl_stats(Client *client, int type, char *para)
 		{
 			if (type && tk->type != type)
 				continue;
-			tkl_stats_matcher(client, type, para, &tklflags, tk);
+			if (tkl_stats_matcher(client, type, para, &tklflags, tk))
+			{
+				*cnt += 1;
+				if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
+				{
+					sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
+					sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
+					return;
+				}
+			}
 		}
 	}
 
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -351,45 +351,6 @@ CMD_FUNC(cmd_whox)
 		return;
 	}
 
-	/* '/who nick' */
-	if (((acptr = find_person(mask, NULL)) != NULL) &&
-		(!(fmt.matchsel & WMATCH_MODES)) &&
-		(!(fmt.matchsel & WMATCH_OPER) || IsOper(acptr)))
-	{
-		int isinvis = 0;
-		int i = 0;
-		Hook *h;
-
-		isinvis = IsInvisible(acptr);
-		for (lp = acptr->user->channel; lp; lp = lp->next)
-		{
-			member = IsMember(client, lp->channel);
-
-			if (isinvis && !member)
-				continue;
-
-			for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
-			{
-				i = (*(h->func.intfunc))(acptr,lp->channel);
-				if (i != 0)
-					break;
-			}
-
-			if (i != 0 && !(is_skochanop(client, lp->channel)) && !(is_skochanop(acptr, lp->channel) || has_voice(acptr,lp->channel)))
-				continue;
-
-			if (member || (!isinvis && PubChannel(lp->channel)))
-				break;
-		}
-		if (lp != NULL)
-			do_who(client, acptr, lp->channel, &fmt);
-		else
-			do_who(client, acptr, NULL, &fmt);
-
-		sendnumeric(client, RPL_ENDOFWHO, orig_mask);
-		return;
-	}
-
 	if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) ||
                 ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))
 	{
@@ -561,9 +522,14 @@ static void who_common_channel(Client *client, Channel *channel,
 
 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
 {
+	Client *hunted = NULL;
 	Client *acptr;
 	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
 
+	/* If searching for a nick explicitly, then include it later on in the result: */
+	if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
+		hunted = find_person(mask, NULL);
+
 	/* Initialize the markers to zero */
 	list_for_each_entry(acptr, &client_list, client_node)
 		ClearMark(acptr);
@@ -583,7 +549,7 @@ static void who_global(Client *client, char *mask, int operspy, struct who_forma
 		if (!IsUser(acptr))
 			continue;
 
-		if (IsInvisible(acptr) && !operspy && (client != acptr))
+		if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted))
 			continue;
 
 		if (IsMarked(acptr))
diff --git a/src/parse.c b/src/parse.c
@@ -417,7 +417,10 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch)
 		/* If you're a user, and this command does not permit users or opers, deny */
 		if ((flags & CMD_USER) && !(cmptr->flags & CMD_USER) && !(cmptr->flags & CMD_OPER))
 		{
-			sendnumeric(cptr, ERR_NOTFORUSERS, cmptr->cmd);
+			if (cmptr->flags & CMD_UNREGISTERED)
+				sendnumeric(cptr, ERR_ALREADYREGISTRED); /* only for unregistered phase */
+			else
+				sendnumeric(cptr, ERR_NOTFORUSERS, cmptr->cmd); /* really never for users */
 			return;
 		}
 
diff --git a/src/send.c b/src/send.c
@@ -164,11 +164,29 @@ void mark_data_to_send(Client *to)
 	}
 }
 
+/** Send data to clients, servers, channels, IRCOps, etc.
+ * There are a lot of send functions. The most commonly functions
+ * are: sendto_one() to send to an individual user,
+ * sendnumeric() to send a numeric to an individual user
+ * and sendto_channel() to send a message to a channel.
+ * @defgroup SendFunctions Send functions
+ * @{
+ */
+
 /** Send a message to a single client.
+ * This function is used quite a lot, after sendnumeric() it is the most-used send function.
  * @param to		The client to send to
  * @param mtags		Any message tags associated with this message (can be NULL)
  * @param pattern	The format string / pattern to use.
  * @param ...		Format string parameters.
+ * @section sendto_one_examples Examples
+ * @subsection sendto_one_mode_r Send "MODE -r"
+ * This will send the `:serv.er.name MODE yournick -r` message.
+ * Note that it will send only this message to illustrate the sendto_one() function.
+ * It does *not* set anyone actually -r.
+ * @code
+ * sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
+ * @endcode
  */
 void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
 {
@@ -180,8 +198,8 @@ void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern
 
 /** Send a message to a single client - va_list variant.
  * This function is similar to sendto_one() except that it
- * doesn't use varargs but a va_list instead.
- * Generally this is NOT used outside send.c, so not by modules.
+ * doesn't use varargs but uses a va_list instead.
+ * Generally this function is NOT used outside send.c, so not by modules.
  * @param to		The client to send to
  * @param mtags		Any message tags associated with this message (can be NULL)
  * @param pattern	The format string / pattern to use.
@@ -363,9 +381,9 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 }
 
 /** A single function to send data to a channel.
- * Previously there were 6, now there is 1. This means there
- * are likely some parameters that you will pass as NULL or 0
- * but at least we can all use one single function.
+ * Previously there were 6 different functions to send channel data,
+ * now there is 1 single function. This also means that you most
+ * likely will pass NULL or 0 as some parameters.
  * @param channel       The channel to send to
  * @param from        The source of the message
  * @param skip        The client to skip (can be NULL).
@@ -380,6 +398,36 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
  * @param mtags       The message tags to attach to this message
  * @param pattern     The pattern (eg: ":%s PRIVMSG %s :%s")
  * @param ...         The parameters for the pattern.
+ * @note For all channel messages, it is important to attach the correct
+ *       message tags (mtags) via a new_message() call, as can be seen
+ *       in the example.
+ * @section sendto_channel_examples Examples
+ * @subsection sendto_channel_privmsg Send a PRIVMSG to a channel
+ * This command will send the message "Hello everyone!!!" to the channel when executed.
+ * @code
+ * CMD_FUNC(cmd_sayhello)
+ * {
+ *     MessageTag *mtags = NULL;
+ *     Channel *channel = NULL;
+ *     if ((parc < 2) || BadPtr(parv[1]))
+ *     {
+ *         sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO");
+ *         return;
+ *     }
+ *     channel = find_channel(parv[1], NULL);
+ *     if (!channel)
+ *     {
+ *         sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+ *         return;
+ *     }
+ *     new_message(client, recv_mtags, &mtags);
+ *     sendto_channel(channel, client, client->direction, 0, 0,
+ *                    SEND_LOCAL|SEND_REMOTE, mtags,
+ *                    ":%s PRIVMSG %s :Hello everyone!!!",
+ *                    client->name, channel->name);
+ *     free_message_tags(mtags);
+ * }
+ * @endcode
  */
 void sendto_channel(Channel *channel, Client *from, Client *skip,
                     int prefix, long clicap, int sendflags,
@@ -593,11 +641,15 @@ static int match_it(Client *one, char *mask, int what)
 	}
 }
 
-/*
- * sendto_match_butone
- *
- * Send to all clients which match the mask in a way defined on 'what';
- * either by user hostname or user servername.
+/** Send to all clients which match the mask.
+ * This function is rarely used.
+ * @param one		The client to skip
+ * @param from		The sender
+ * @param mask		The mask
+ * @param what		One of MATCH_HOST or MATCH_SERVER
+ * @param mtags		Message tags associated with the message
+ * @param pattern	Format string
+ * @param ...		Parameters to the format string
  */
 void sendto_match_butone(Client *one, Client *from, char *mask, int what,
                          MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
@@ -641,10 +693,9 @@ void sendto_match_butone(Client *one, Client *from, char *mask, int what,
 	}
 }
 
-/*
- * sendto_ops
- *
- *	Send to *local* ops only.
+/** Send a message to all locally connected IRCOps
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
  */
 void sendto_ops(FORMAT_STRING(const char *pattern), ...)
 {
@@ -664,10 +715,98 @@ void sendto_ops(FORMAT_STRING(const char *pattern), ...)
 		}
 }
 
-/*
- * sendto_umode
- *
- *  Send to specified umode
+/* Hmm.. so local sending is called sendto_ops() and local+remote is sendto_ops_butone(),
+ * that is weird naming... (TODO fix some day in a new major series)
+ */
+
+/** Send a message to all IRCOps (local and remote), except one.
+ * @param one		Skip sending the message to this client/direction
+ * @param from		The sender (can not be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_ops_butone(Client *one, Client *from, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+
+	++current_serial;
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (!SendWallops(acptr))
+			continue;
+		if (acptr->direction->local->serial == current_serial)	/* sent message along it already ? */
+			continue;
+		if (acptr->direction == one)
+			continue;	/* ...was the one I should skip */
+		acptr->direction->local->serial = current_serial;
+
+		va_start(vl, pattern);
+		vsendto_prefix_one(acptr->direction, from, NULL, pattern, vl);
+		va_end(vl);
+	}
+}
+
+/** This function does exactly the same as sendto_ops() in practice in 5.x.
+ * There used to be a difference between sendto_ops() and sendto_realops()
+ * with regards to user-settable snomasks, but this is no longer the case.
+ * TODO: remove this function in some future cleanup
+ */
+void sendto_realops(FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	Client *acptr;
+	char nbuf[1024];
+
+	list_for_each_entry(acptr, &oper_list, special_node)
+	{
+		ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :*** ", me.name, acptr->name);
+		strlcat(nbuf, pattern, sizeof nbuf);
+
+		va_start(vl, pattern);
+		vsendto_one(acptr, NULL, nbuf, vl);
+		va_end(vl);
+	}
+}
+
+/** Send a message to all locally connected IRCOps and also log the error.
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	char buf[1024];
+
+	va_start(vl, pattern);
+	ircvsnprintf(buf, sizeof(buf), pattern, vl);
+	va_end(vl);
+
+	ircd_log(LOG_ERROR, "%s", buf);
+	sendto_umode(UMODE_OPER, "%s", buf);
+}
+
+/** This function does exactly the same as sendto_ops_and_log()
+ * TODO: remove this function in some future cleanup
+ */
+void sendto_realops_and_log(FORMAT_STRING(const char *fmt), ...)
+{
+	va_list vl;
+	static char buf[2048];
+
+	va_start(vl, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, vl);
+	va_end(vl);
+
+	sendto_realops("%s", buf);
+	ircd_log(LOG_ERROR, "%s", buf);
+}
+
+
+/** Send a message to all locally connected users with specified user mode.
+ * @param umodes	The umode that the recipient should have set (one of UMODE_)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
  */
 void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...)
 {
@@ -687,10 +826,10 @@ void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...)
 		}
 }
 
-/*
- * sendto_umode_global
- *
- * Send to specified umode *GLOBALLY* (on all servers)
+/** Send a message to all users with specified user mode (local & remote users).
+ * @param umodes	The umode that the recipient should have set (one of UMODE_*)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
  */
 void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
 {
@@ -734,10 +873,10 @@ void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
 	}
 }
 
-/** Send to specified snomask - local / operonly.
- * @param snomask Snomask to send to (can be a bitmask [AND])
- * @param pattern printf-style pattern, followed by parameters.
- * This function does not send snomasks to non-opers.
+/** Send a message to all locally connected users with specified snomask.
+ * @param snomask	The snomask that the recipient should have set (one of SNO_*)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
  */
 void sendto_snomask(int snomask, FORMAT_STRING(const char *pattern), ...)
 {
@@ -756,10 +895,10 @@ void sendto_snomask(int snomask, FORMAT_STRING(const char *pattern), ...)
 	}
 }
 
-/** Send to specified snomask - global / operonly.
- * @param snomask Snomask to send to (can be a bitmask [AND])
- * @param pattern printf-style pattern, followed by parameters
- * This function does not send snomasks to non-opers.
+/** Send a message to all users with specified snomask (local and remote users).
+ * @param snomask	The snomask that the recipient should have set (one of SNO_*)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
  */
 void sendto_snomask_global(int snomask, FORMAT_STRING(const char *pattern), ...)
 {
@@ -788,10 +927,10 @@ void sendto_snomask_global(int snomask, FORMAT_STRING(const char *pattern), ...)
 	sendto_server(NULL, 0, 0, NULL, ":%s SENDSNO %s :%s", me.id, snobuf, nbuf);
 }
 
-/*
- * send_cap_notify
- *
- * Send CAP DEL or CAP NEW to clients supporting this.
+/** Send CAP DEL and CAP NEW notification to clients supporting it.
+ * This function is mostly meant to be used by the CAP and SASL modules.
+ * @param add		Whether the CAP token is added (1) or removed (0)
+ * @param token		The CAP token
  */
 void send_cap_notify(int add, char *token)
 {
@@ -829,33 +968,6 @@ void send_cap_notify(int add, char *token)
 	}
 }
 
-/* ** sendto_ops_butone
-**	Send message to all operators.
-** one - client not to send message to
-** from- client which message is from *NEVER* NULL!!
-*/
-void sendto_ops_butone(Client *one, Client *from, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-
-	++current_serial;
-	list_for_each_entry(acptr, &client_list, client_node)
-	{
-		if (!SendWallops(acptr))
-			continue;
-		if (acptr->direction->local->serial == current_serial)	/* sent message along it already ? */
-			continue;
-		if (acptr->direction == one)
-			continue;	/* ...was the one I should skip */
-		acptr->direction->local->serial = current_serial;
-
-		va_start(vl, pattern);
-		vsendto_prefix_one(acptr->direction, from, NULL, pattern, vl);
-		va_end(vl);
-	}
-}
-
 /* Prepare buffer based on format string and 'from' for LOCAL delivery.
  * The prefix (:<something>) will be expanded to :nick!user@host if 'from'
  * is a person, taking into account the rules for hidden/cloaked host.
@@ -908,6 +1020,32 @@ static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, con
 	return len;
 }
 
+/** Send a message to a client, expand the sender prefix.
+ * This is similar to sendto_one() except that it will expand the source part :%s
+ * to :nick!user@host if needed, while with sendto_one() it will be :nick.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
+void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
+{
+	va_list vl;
+	va_start(vl, pattern);
+	vsendto_prefix_one(to, from, mtags, pattern, vl);
+	va_end(vl);
+}
+
+/** Send a message to a single client, expand the sender prefix - va_list variant.
+ * This is similar to vsendto_one() except that it will expand the source part :%s
+ * to :nick!user@host if needed, while with sendto_one() it will be :nick.
+ * This function is also similar to sendto_prefix_one(), but this is the va_list
+ * variant.
+ * @param to		The client to send to
+ * @param mtags		Any message tags associated with this message (can be NULL)
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
 void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
 {
 	char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
@@ -928,60 +1066,6 @@ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char 
 	}
 }
 
-/*
- * sendto_prefix_one
- *
- * to - destination client
- * from - client which message is from
- *
- * NOTE: NEITHER OF THESE SHOULD *EVER* BE NULL!!
- * -avalon
- */
-
-void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	va_start(vl, pattern);
-	vsendto_prefix_one(to, from, mtags, pattern, vl);
-	va_end(vl);
-}
-
-/*
- * sendto_realops
- *
- *	Send to *local* ops only but NOT +s nonopers.
- */
-void sendto_realops(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	Client *acptr;
-	char nbuf[1024];
-
-	list_for_each_entry(acptr, &oper_list, special_node)
-	{
-		ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :*** ", me.name, acptr->name);
-		strlcat(nbuf, pattern, sizeof nbuf);
-
-		va_start(vl, pattern);
-		vsendto_one(acptr, NULL, nbuf, vl);
-		va_end(vl);
-	}
-}
-
-/* Sends a message to all (local) opers AND logs to the ircdlog (as LOG_ERROR) */
-void sendto_realops_and_log(FORMAT_STRING(const char *fmt), ...)
-{
-va_list vl;
-static char buf[2048];
-
-	va_start(vl, fmt);
-	vsnprintf(buf, sizeof(buf), fmt, vl);
-	va_end(vl);
-
-	sendto_realops("%s", buf);
-	ircd_log(LOG_ERROR, "%s", buf);
-}
-
 void sendto_connectnotice(Client *newuser, int disconnect, char *comment)
 {
 	Client *acptr;
@@ -1102,6 +1186,11 @@ void sendto_one_nickcmd(Client *server, Client *client, char *umodes)
  * has a % in their nick, which is a safe assumption since % is illegal.
  */
  
+/** Send a server notice to a client.
+ * @param to		The client to send to
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ */
 void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...)
 {
 	static char realpattern[1024];
@@ -1115,25 +1204,34 @@ void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...)
 	va_end(vl);
 }
 
-/** Send MultiLine list as a notice, one for each line */
+/** Send MultiLine list as a notice, one for each line.
+ * @param client	The client to send to
+ * @param m		The MultiLine list.
+ */
 void sendnotice_multiline(Client *client, MultiLine *m)
 {
 	for (; m; m = m->next)
 		sendnotice(client, "%s", m->line);
 }
-void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
-{
-	static char realpattern[1024];
-	va_list vl;
 
-	ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern);
-
-	va_start(vl, pattern);
-	vsendto_one(to, NULL, realpattern, vl);
-	va_end(vl);
-}
 
-/** Send numeric to IRC client */
+/** Send numeric message to a client.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param ...		The parameters for the numeric
+ * @note Be sure to provide the correct number and type of parameters that belong to the numeric. Check src/numeric.c when in doubt!
+ * @section sendnumeric_examples Examples
+ * @subsection sendnumeric_permission_denied Send "Permission Denied" numeric
+ * This numeric has no parameter, so is simple:
+ * @code
+ * sendnumeric(client, ERR_NOPRIVILEGES);
+ * @endcode
+ * @subsection sendnumeric_notenoughparameters Send "Not enough parameters" numeric
+ * This numeric requires 1 parameter: the name of the command.
+ * @code
+ * sendnumeric(client, ERR_NEEDMOREPARAMS, "SOMECOMMAND");
+ * @endcode
+ */
 void sendnumeric(Client *to, int numeric, ...)
 {
 	va_list vl;
@@ -1146,7 +1244,15 @@ void sendnumeric(Client *to, int numeric, ...)
 	va_end(vl);
 }
 
-/** Send numeric to IRC client */
+/** Send numeric message to a client - format to user specific needs.
+ * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
+ * followed by the pattern and format string you choose.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
 void sendnumericfmt(Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
@@ -1159,7 +1265,27 @@ void sendnumericfmt(Client *to, int numeric, FORMAT_STRING(const char *pattern),
 	va_end(vl);
 }
 
-/** Send raw data directly to socket, bypassing everything.
+/** Send text numeric message to a client (RPL_TEXT).
+ * Because this generic output numeric is commonly used it got a special function for it.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
+void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
+{
+	static char realpattern[1024];
+	va_list vl;
+
+	ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern);
+
+	va_start(vl, pattern);
+	vsendto_one(to, NULL, realpattern, vl);
+	va_end(vl);
+}
+
+/* Send raw data directly to socket, bypassing everything.
  * Looks like an interesting function to call? NO! STOP!
  * Don't use this function. It may only be used by the initial
  * Z-Line check via the codepath to banned_client().
@@ -1186,17 +1312,4 @@ void send_raw_direct(Client *user, FORMAT_STRING(FORMAT_STRING(const char *patte
 	(void)send(user->local->fd, sendbuf, sendlen, 0);
 }
 
-/** Send a message to all locally connected IRCOps and log the error.
- */
-void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...)
-{
-	va_list vl;
-	char buf[1024];
-
-	va_start(vl, pattern);
-	ircvsnprintf(buf, sizeof(buf), pattern, vl);
-	va_end(vl);
-
-	ircd_log(LOG_ERROR, "%s", buf);
-	sendto_umode(UMODE_OPER, "%s", buf);
-}
+/** @} */
diff --git a/src/serv.c b/src/serv.c
@@ -32,7 +32,6 @@
 /* for uname(), is POSIX so should be OK... */
 #include <sys/utsname.h>
 #endif
-extern void s_die();
 
 MODVAR int  max_connection_count = 1, max_client_count = 1;
 extern int do_garbage_collect;
diff --git a/src/support.c b/src/support.c
@@ -724,17 +724,6 @@ void outofmemory(size_t bytes)
 	exit(7);
 }
 
-/** Check if the specified file exists */
-int file_exists(char *file)
-{
-	FILE *fd;
-	fd = fopen(file, "r");
-	if (!fd)
-		return 0;
-	fclose(fd);
-	return 1;
-}
-
 /** Returns a unique filename in the specified directory
  * using the specified suffix. The returned value will
  * be of the form <dir>/<random-hex>.<suffix>
diff --git a/src/tls.c b/src/tls.c
@@ -36,9 +36,10 @@ extern HWND hwIRCDWnd;
 #define SAFE_SSL_ACCEPT 3
 #define SAFE_SSL_CONNECT 4
 
+/* Forward declarations */
 static int fatal_ssl_error(int ssl_error, int where, int my_errno, Client *client);
-extern int cipher_check(SSL_CTX *ctx, char **errstr);
-extern int certificate_quality_check(SSL_CTX *ctx, char **errstr);
+int cipher_check(SSL_CTX *ctx, char **errstr);
+int certificate_quality_check(SSL_CTX *ctx, char **errstr);
 
 /* The SSL structures */
 SSL_CTX *ctx_server;
@@ -1040,6 +1041,8 @@ int verify_certificate(SSL *ssl, char *hostname, char **errstr)
 
 	if (SSL_get_verify_result(ssl) != X509_V_OK)
 	{
+		// FIXME: there are actually about 25+ different possible errors,
+		// this is only the most common one:
 		strlcpy(buf, "Certificate is not issued by a trusted Certificate Authority", sizeof(buf));
 		if (errstr)
 			*errstr = buf;
@@ -1333,3 +1336,100 @@ char *outdated_tls_client_build_string(char *pattern, Client *client)
 	buildvarstring(pattern, buf, sizeof(buf), name, value);
 	return buf;
 }
+
+int check_certificate_expiry_ctx(SSL_CTX *ctx, char **errstr)
+{
+#if !defined(HAS_ASN1_TIME_diff) || !defined(HAS_X509_get0_notAfter)
+	return 0;
+#else
+	static char errbuf[512];
+	SSL *ssl;
+	X509 *cert;
+	const ASN1_TIME *cert_expiry_time;
+	int days_expiry = 0, seconds_expiry = 0;
+	long duration;
+
+	*errstr = NULL;
+
+	ssl = SSL_new(ctx);
+	if (!ssl)
+		return 0;
+
+	cert = SSL_get_certificate(ssl);
+	if (!cert)
+	{
+		SSL_free(ssl);
+		return 0;
+	}
+
+	/* get certificate time */
+	cert_expiry_time = X509_get0_notAfter(cert);
+
+	/* calculate difference */
+	ASN1_TIME_diff(&days_expiry, &seconds_expiry, cert_expiry_time, NULL);
+	duration = (days_expiry * 86400) + seconds_expiry;
+
+	/* certificate expiry? */
+	if ((days_expiry > 0) || (seconds_expiry > 0))
+	{
+		snprintf(errbuf, sizeof(errbuf), "certificate expired %s ago", pretty_time_val(duration));
+		SSL_free(ssl);
+		*errstr = errbuf;
+		return 1;
+	} else
+	/* or near-expiry? */
+	if (((days_expiry < 0) || (seconds_expiry < 0)) && (days_expiry > -7))
+	{
+		snprintf(errbuf, sizeof(errbuf), "certificate will expire in %s", pretty_time_val(0 - duration));
+		SSL_free(ssl);
+		*errstr = errbuf;
+		return 1;
+	}
+
+	/* All good */
+	SSL_free(ssl);
+	return 0;
+#endif
+}
+
+void check_certificate_expiry_tlsoptions_and_warn(TLSOptions *tlsoptions)
+{
+	SSL_CTX *ctx;
+	int ret;
+	char *errstr = NULL;
+
+	ctx = init_ctx(tlsoptions, 1);
+	if (!ctx)
+		return;
+
+	if (check_certificate_expiry_ctx(ctx, &errstr))
+	{
+		sendto_umode_global(UMODE_OPER, "Warning: TLS certificate '%s': %s", tlsoptions->certificate_file, errstr);
+		ircd_log(LOG_ERROR, "[warning] TLS certificate '%s': %s", tlsoptions->certificate_file, errstr);
+	}
+	SSL_CTX_free(ctx);
+}
+
+EVENT(tls_check_expiry)
+{
+	ConfigItem_listen *listen;
+	ConfigItem_sni *sni;
+	ConfigItem_link *link;
+
+	/* set block */
+	check_certificate_expiry_tlsoptions_and_warn(iConf.tls_options);
+
+	for (listen = conf_listen; listen; listen = listen->next)
+		if (listen->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(listen->tls_options);
+
+	/* sni::tls-options.... */
+	for (sni = conf_sni; sni; sni = sni->next)
+		if (sni->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(sni->tls_options);
+
+	/* link::outgoing::tls-options.... */
+	for (link = conf_link; link; link = link->next)
+		if (link->tls_options)
+			check_certificate_expiry_tlsoptions_and_warn(link->tls_options);
+}
diff --git a/src/user.c b/src/user.c
@@ -726,3 +726,149 @@ int hide_idle_time(Client *client, Client *target)
 			return 0;
 	}
 }
+
+/** Check if the name of the security-group contains only valid characters.
+ * @param name	The name of the group
+ * @returns 1 if name is valid, 0 if not (eg: illegal characters)
+ */
+int security_group_valid_name(char *name)
+{
+	char *p;
+	if (strlen(name) > SECURITYGROUPLEN)
+		return 0; /* Too long */
+	for (p = name; *p; p++)
+	{
+		if (!isalnum(*p) && !strchr("_-", *p))
+			return 0; /* Character not allowed */
+	}
+	return 1;
+}
+
+/** Find a security-group.
+ * @param name	The name of the security group
+ * @returns A SecurityGroup struct, or NULL if not found.
+ */
+SecurityGroup *find_security_group(char *name)
+{
+	SecurityGroup *s;
+	for (s = securitygroups; s; s = s->next)
+		if (!strcasecmp(name, s->name))
+			return s;
+	return NULL;
+}
+
+/** Checks if a security-group exists.
+ * This function takes the 'unknown-users' magic group into account as well.
+ * @param name	The name of the security group
+ * @returns 1 if it exists, 0 if not
+ */
+int security_group_exists(char *name)
+{
+	if (!strcmp(name, "unknown-users") || find_security_group(name))
+		return 1;
+	return 0;
+}
+
+/** Add a new security-group and add it to the list, but search for existing one first.
+ * @param name	The name of the security group
+ * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
+ */
+SecurityGroup *add_security_group(char *name, int priority)
+{
+	SecurityGroup *s = find_security_group(name);
+
+	/* Existing? */
+	if (s)
+		return s;
+
+	/* Otherwise, create a new entry */
+	s = safe_alloc(sizeof(SecurityGroup));
+	strlcpy(s->name, name, sizeof(s->name));
+	s->priority = priority;
+	AddListItemPrio(s, securitygroups, priority);
+	return s;
+}
+
+/** Free a SecurityGroup struct */
+void free_security_group(SecurityGroup *s)
+{
+	/* atm there is nothing else to free,
+	 * but who knows this may change in the future
+	 */
+	safe_free(s);
+}
+
+/** Initialize the default security-group blocks */
+void set_security_group_defaults(void)
+{
+	SecurityGroup *s, *s_next;
+
+	/* First free all security groups */
+	for (s = securitygroups; s; s = s_next)
+	{
+		s_next = s->next;
+		free_security_group(s);
+	}
+	securitygroups = NULL;
+
+	/* Default group: known-users */
+	s = add_security_group("known-users", 100);
+	s->identified = 1;
+	s->reputation_score = 25;
+	s->webirc = 0;
+
+	/* Default group: tls-and-known-users */
+	s = add_security_group("tls-and-known-users", 200);
+	s->identified = 1;
+	s->reputation_score = 25;
+	s->webirc = 0;
+	s->tls = 1;
+
+	/* Default group: tls-users */
+	s = add_security_group("tls-users", 300);
+	s->tls = 1;
+}
+
+/** Returns 1 if the user is OK as far as the security-group is concerned.
+ * @param client	The client to check
+ * @param s		The security-group to check against
+ * @retval 1 if user is allowed by security-group, 0 if not.
+ */
+int user_allowed_by_security_group(Client *client, SecurityGroup *s)
+{
+	if (s->identified && IsLoggedIn(client))
+		return 1;
+	if (s->webirc && moddata_client_get(client, "webirc"))
+		return 1;
+	if (s->reputation_score && (GetReputation(client) >= s->reputation_score))
+		return 1;
+	if (s->tls && (IsSecureConnect(client) || IsSecure(client)))
+		return 1;
+	return 0;
+}
+
+/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version.
+ * @param client	The client to check
+ * @param secgroupname	The name of the security-group to check against
+ * @retval 1 if user is allowed by security-group, 0 if not.
+ */
+int user_allowed_by_security_group_name(Client *client, char *secgroupname)
+{
+	SecurityGroup *s;
+
+	/* Handle the magical 'unknown-users' case. */
+	if (!strcmp(secgroupname, "unknown-users"))
+	{
+		/* This is simply the inverse of 'known-users' */
+		s = find_security_group("known-users");
+		if (!s)
+			return 0; /* that's weird!? pretty impossible. */
+		return !user_allowed_by_security_group(client, s);
+	}
+
+	/* Find the group and evaluate it */
+	s = find_security_group(secgroupname);
+	if (!s)
+		return 0; /* security group not found: no match */
+	return user_allowed_by_security_group(client, s);
+}
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.7"
+id="5.0.8"
 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.7.0"
+    version="5.0.8.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.7
+AppVerName=UnrealIRCd 5.0.8
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org
@@ -68,6 +68,7 @@ Source: "src\modules\chanmodes\*.dll"; DestDir: "{app}\modules\chanmodes"; Flags
 Source: "src\modules\usermodes\*.dll"; DestDir: "{app}\modules\usermodes"; Flags: ignoreversion
 Source: "src\modules\snomasks\*.dll"; DestDir: "{app}\modules\snomasks"; Flags: ignoreversion
 Source: "src\modules\extbans\*.dll"; DestDir: "{app}\modules\extbans"; Flags: ignoreversion
+Source: "src\modules\third\*.dll"; DestDir: "{app}\modules\third"; Flags: ignoreversion skipifsourcedoesntexist
 
 Source: "c:\dev\unrealircd-5-libs\pcre2\bin\pcre*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion
 Source: "c:\dev\unrealircd-5-libs\argon2\vs2015\build\*.dll"; DestDir: "{app}\bin"; Flags: ignoreversion