unrealircd

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

commit 252e8547f4bc206f9f1323a323231db192a3f9b1
parent 4e71d6feade725e626f1a0e2edd4a8eeafa47f26
Author: acidvegas <acid.vegas@acid.vegas>
Date: Sun, 3 Apr 2022 11:09:29 -0400

Updated to 6.0.3

Diffstat:
M.gitignore | 1-
MConfig | 32+++++++++++++++++++++++++++-----
MMakefile.in | 2+-
MMakefile.windows | 46++++++++++++++++++++++++++++++++--------------
Mconfigure | 51+++++++++++++++++++++++++++++++++++++--------------
Mconfigure.ac | 19+++++++++++++------
Mdoc/Config.header | 2+-
Mdoc/RELEASE-NOTES.md | 141++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Ddoc/conf/modules.sources.list | 2--
Mdoc/conf/snomasks.conf | 215+++++++------------------------------------------------------------------------
Mdoc/conf/unrealircd.link.conf | 1+
Mdoc/conf/unrealircd.remote.conf | 58+++++++++++++++++++++++++++-------------------------------
Mextras/build-tests/nix/run-tests.bbwrapper | 2+-
Mextras/build-tests/windows/build.bat | 4++--
Mextras/c-ares.tar.gz | 0
Mextras/curlinstall | 23++---------------------
Mextras/doxygen/Doxyfile | 2+-
Mextras/pcre2.tar.gz | 0
Minclude/dynconf.h | 1+
Minclude/h.h | 51++++++++++++++++++++++++++++++++++++++++++++++-----
Minclude/modules.h | 31+++++++++++++++++++++----------
Minclude/numeric.h | 5++++-
Minclude/setup.h.in | 3+++
Minclude/struct.h | 39++++++++++++++++++++++++++++++++-------
Minclude/sys.h | 2++
Minclude/version.h | 2+-
Minclude/windows/setup.h | 5+++--
Msrc/Makefile.in | 18+++++++++---------
Msrc/api-command.c | 31+++++++++++++++++++++----------
Msrc/api-event.c | 18------------------
Msrc/conf.c | 460+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/crashreport.c | 7+++++++
Msrc/dbuf.c | 1+
Msrc/dns.c | 2+-
Msrc/ircd.c | 344+++++--------------------------------------------------------------------------
Asrc/ircd_vars.c | 31+++++++++++++++++++++++++++++++
Msrc/list.c | 2++
Msrc/log.c | 239++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/misc.c | 174++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/modules/blacklist.c | 8++++++++
Msrc/modules/chanmodes/halfop.c | 4++--
Msrc/modules/channeldb.c | 14++++++++++++++
Msrc/modules/chghost.c | 2+-
Msrc/modules/dccdeny.c | 13+++++++------
Msrc/modules/extbans/realname.c | 2+-
Msrc/modules/extbans/timedban.c | 1-
Msrc/modules/extended-monitor.c | 12++++++------
Msrc/modules/geoip_base.c | 20++++++++++++++------
Msrc/modules/hideserver.c | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/modules/labeled-response.c | 2+-
Msrc/modules/list.c | 186++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/modules/map.c | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/modules/message.c | 6+++---
Msrc/modules/mode.c | 29++++++++++++++++++-----------
Msrc/modules/reputation.c | 8++++++++
Msrc/modules/sapart.c | 4++--
Msrc/modules/setname.c | 2+-
Msrc/modules/stats.c | 52+++++++++++++++++++++++++++++++++++-----------------
Msrc/modules/svsmode.c | 8++++----
Msrc/modules/tkl.c | 22+++++++++++-----------
Msrc/modules/watch-backend.c | 38++++++++++++++++++++++++--------------
Msrc/modules/webirc.c | 12+++---------
Msrc/modules/websocket.c | 15+++++----------
Msrc/modules/whox.c | 36++++++++++++++++++++++++++++++++++++
Msrc/parse.c | 12+++++++++++-
Asrc/proc_io_client.c | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/proc_io_server.c | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/send.c | 15+++++++++------
Msrc/serv.c | 117+++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/socket.c | 372++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/tls.c | 14++++++++------
Asrc/unrealircdctl.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/user.c | 1+
Msrc/version.c.SH | 2+-
Msrc/windows/UnrealIRCd.exe.manifest | 4++--
Msrc/windows/gui.c | 8+-------
Msrc/windows/service.c | 9+--------
Msrc/windows/unrealinst.iss | 3++-
Asrc/windows/unrealircdctl.exe.manifest | 10++++++++++
Munrealircd.in | 82+++++++++++++++++++++++++++----------------------------------------------------

80 files changed, 2658 insertions(+), 1350 deletions(-)

diff --git a/.gitignore b/.gitignore
@@ -9,7 +9,6 @@ extras/c-ares*
 config.status
 extras/ircdcron/ircd.cron
 extras/ircdcron/ircdchk
-src/modules/snomasks/Makefile
 src/modules/chanmodes/Makefile
 src/modules/extbans/Makefile
 src/modules/usermodes/Makefile
diff --git a/Config b/Config
@@ -18,7 +18,24 @@
 
 # some bits edited by baafie on March 17 2004, every change marked.
 
+# Remove trailing slash in paths (if any)
+FIX_PATHNAMES () {
+	BASEPATH="${BASEPATH%/}"
+	BINDIR="${BINDIR%/}"
+	DATADIR="${DATADIR%/}"
+	CONFDIR="${CONFDIR%/}"
+	MODULESDIR="${MODULESDIR%/}"
+	LOGDIR="${LOGDIR%/}"
+	CACHEDIR="${CACHEDIR%/}"
+	DOCDIR="${DOCDIR%/}"
+	TMPDIR="${TMPDIR%/}"
+	PRIVATELIBDIR="${PRIVATELIBDIR%/}"
+	SSLDIR="${SSLDIR%/}"
+	CURLDIR="${CURLDIR%/}"
+}
 
+# Create and run the ./configure command with the appropriate
+# options based on the users settings.
 RUN_CONFIGURE () {
 ARG=" "
 
@@ -72,6 +89,7 @@ fi
 ARG="$ARG--with-bindir=$BINDIR "
 ARG="$ARG--with-datadir=$DATADIR "
 ARG="$ARG--with-pidfile=$DATADIR/unrealircd.pid "
+ARG="$ARG--with-controlfile=$DATADIR/unrealircd.ctl "
 ARG="$ARG--with-confdir=$CONFDIR "
 ARG="$ARG--with-modulesdir=$MODULESDIR "
 ARG="$ARG--with-logdir=$LOGDIR "
@@ -292,6 +310,7 @@ while [ $# -ge 1 ] ; do
 		if [ -f "config.settings" ] ; then
 			. ./config.settings
 		fi
+		FIX_PATHNAMES
 		RUN_CONFIGURE
 		cd "$UNREALCWD"
 		exit 0
@@ -356,7 +375,7 @@ echo "We will now ask you a number of questions. You can just press ENTER to acc
 echo ""
 
 # This needs to be updated each release so auto-upgrading works for settings, modules, etc!!:
-UNREALRELEASES="unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6"
+UNREALRELEASES="unrealircd-6.0.2 unrealircd-6.0.1.1 unrealircd-6.0.1 unrealircd-6.0.0 unrealircd-6.0.0-rc2 unrealircd-6.0.0-rc1 unrealircd-6.0.0-beta4 unrealircd-6.0.0-beta3 unrealircd-6.0.0-beta2 unrealircd-6.0.0-beta1 unrealircd-5.2.3 unrealircd-5.2.2 unrealircd-5.2.1.1 unrealircd-5.2.1 unrealircd-5.2.1-rc1 unrealircd-5.2.0.2 unrealircd-5.2.0.1 unrealircd-5.2.0 unrealircd-5.2.0-rc1 unrealircd-5.0.9.1 unrealircd-5.0.9 unrealircd-5.0.9-rc1 unrealircd-5.0.8 unrealircd-5.0.8-rc1 unrealircd-5.0.7 unrealircd-5.0.7-rc1 unrealircd-5.0.6"
 if [ -f "config.settings" ]; then
 	. ./config.settings
 else
@@ -701,11 +720,12 @@ while [ -z "$TEST" ] ; do
 	TEST="$GEOIP"
 	echo ""
 	echo "GeoIP is a feature that allows converting an IP address to a location (country)"
-	echo "You have three options in UnrealIRCd:"
-	echo "     classic: This is the DEFAULT geoip engine that should work on all systems"
-	echo "libmaxminddb: This uses the libmaxminddb library. If you want to use it then"
+	echo "Possible build options:"
+	echo "     classic: This is the DEFAULT geoip engine. It should work on all systems"
+	echo "              and receives automatic updates."
+	echo "libmaxminddb: This uses the libmaxminddb library. If you want to use this, then"
 	echo "              you need to install the libmaxminddb library on your system first"
-	echo "        none: Don't built with any geoip feature"
+	echo "        none: Don't build with any geoip library (geoip-csv is still built)"
 	echo "Choose one of: classic, libmaxminddb, none"
 	echo $n "[$TEST] -> $c"
 	read cc
@@ -814,6 +834,8 @@ if [ -z "$EXTRAPARA" ]; then
 	EXTRAPARA="$TEST"
 fi
 
+FIX_PATHNAMES
+
 rm -f config.settings
 cat > config.settings << __EOF__
 #
diff --git a/Makefile.in b/Makefile.in
@@ -179,13 +179,13 @@ depend:
 install: all
 	$(INSTALL) -m 0700 -d $(DESTDIR)@BINDIR@
 	$(INSTALL) -m 0700 src/ircd $(DESTDIR)@BINDIR@/unrealircd
+	$(INSTALL) -m 0700 src/unrealircdctl $(DESTDIR)@BINDIR@/unrealircdctl
 	$(INSTALL) -m 0700 extras/unrealircd-upgrade-script $(DESTDIR)@BINDIR@/unrealircd-upgrade-script
 	$(INSTALL) -m 0700 -d $(DESTDIR)@DOCDIR@
 	$(INSTALL) -m 0600 doc/Authors doc/coding-guidelines doc/tao.of.irc doc/KEYS doc/RELEASE-NOTES.md $(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
diff --git a/Makefile.windows b/Makefile.windows
@@ -165,8 +165,7 @@ CFLAGS=$(DBGCFLAG) $(STDOPTIONS) /FS /MP1 /c /Fosrc/
 CFLAGSST=$(DBGCFLAGST) $(STDOPTIONS) /FS /MP1 /c /Fosrc/
 LFLAGS=kernel32.lib user32.lib gdi32.lib shell32.lib ws2_32.lib advapi32.lib \
  dbghelp.lib oldnames.lib comctl32.lib comdlg32.lib $(STDLIBS) \
- /def:UnrealIRCd.def /implib:UnrealIRCd.lib \
- /nologo $(DBGLFLAG) /out:UnrealIRCd.exe
+ /nologo $(DBGLFLAG)
 MODCFLAGS=$(MODDBGCFLAG) $(STDOPTIONS) /D DYNAMIC_LINKING /D MODULE_COMPILE
 MODLFLAGS=/link /def:src/modules/module.def UnrealIRCd.lib ws2_32.lib $(STDLIBS)
 
@@ -174,10 +173,10 @@ INCLUDES=./include/struct.h ./include/config.h ./include/sys.h \
  ./include/common.h ./include/version.h ./include/h.h ./include/numeric.h \
  ./include/msg.h ./include/setup.h ./include/dynconf.h
 
-EXP_OBJ_FILES=src/channel.obj src/send.obj src/socket.obj \
- src/conf.obj src/conf_preprocessor.obj \
+EXP_OBJ_FILES=src/ircd_vars.obj src/channel.obj src/send.obj src/socket.obj \
+ src/conf.obj src/proc_io_server.obj src/conf_preprocessor.obj \
  src/fdlist.obj src/dbuf.obj  \
- src/hash.obj src/parse.obj src/ircd.obj \
+ src/hash.obj src/parse.obj \
  src/whowas.obj \
  src/misc.obj src/match.obj src/crule.obj \
  src/debug.obj  src/support.obj src/list.obj \
@@ -194,7 +193,7 @@ EXP_OBJ_FILES=src/channel.obj src/send.obj src/socket.obj \
  src/utf8.obj src/log.obj $(CURLOBJ)
 
 OBJ_FILES=$(EXP_OBJ_FILES) src/gui.obj src/service.obj src/windebug.obj src/rtf.obj \
- src/editor.obj src/win.obj 
+ src/editor.obj src/win.obj src/ircd.obj src/proc_io_client.obj
 
 DLL_FILES=\
  src/modules/account-notify.dll \
@@ -411,7 +410,7 @@ DLL_FILES=\
  src/modules/whox.dll
 
 
-ALL: CONF UNREALSVC.EXE UnrealIRCd.exe MODULES 
+ALL: CONF unrealircdctl.exe UNREALSVC.EXE UnrealIRCd.exe MODULES
 
 CLEAN:
 	-@del /Q /S *.dll *.exe *.obj *.pdb *.res *.lib  *.exp *.ilk src\version.c >NUL
@@ -424,21 +423,27 @@ CONF:
 	$(CC) src/windows/config.c
 	-@config.exe
 
-UnrealIRCd.exe: $(OBJ_FILES) src/windows/win.res
-        $(LINK) $(LFLAGS) $(OBJ_FILES) src/windows/win.res /MAP
+UnrealIRCd.exe: $(OBJ_FILES) src/ircd.obj src/windows/win.res
+        $(LINK) $(LFLAGS) /out:UnrealIRCd.exe /def:UnrealIRCd.def /implib:UnrealIRCd.lib $(OBJ_FILES) src/windows/win.res /MAP
 	-@erase src\windows\win.res
 	$(MT) -manifest src\windows\UnrealIRCd.exe.manifest -outputresource:UnrealIRCd.exe;1
-!IFNDEF DEBUGEXTRA
- @echo Standard version built 
-!ELSE
- @echo Extra-Debug version built ... 
-!ENDIF
+
+unrealircdctl.exe: $(OBJ_FILES) src/unrealircdctl.obj src/proc_io_client.obj
+	$(LINK) $(LFLAGS) /SUBSYSTEM:CONSOLE /out:unrealircdctl.exe $(OBJ_FILES) src/unrealircdctl.obj
+	$(MT) -manifest src\windows\unrealircdctl.exe.manifest -outputresource:unrealircdctl.exe;1
+
+# alternative option -- FIXME: REMOVE / CHOOSE
+#unrealircdctl.exe: $(OBJ_FILES) src/unrealircdctl.obj src/proc_io_client.obj src/windows/unrealircdctl.res
+#	$(LINK) $(LFLAGS) /out:unrealircdctl.exe $(OBJ_FILES) src/unrealircdctl.obj src/windows/unrealircdctl.res
 
 #Source files
 
 src/version.obj: src/version.c
         $(CC) $(CFLAGS) src/version.c
 
+src/ircd_vars.obj: src/ircd_vars.c $(INCLUDES)
+        $(CC) $(CFLAGS) src/ircd_vars.c
+
 src/parse.obj: src/parse.c $(INCLUDES)
         $(CC) $(CFLAGS) src/parse.c
 
@@ -485,6 +490,12 @@ src/dns.obj: src/dns.c $(INCLUDES)
 src/conf.obj: src/conf.c $(INCLUDES)
         $(CC) $(CFLAGS) src/conf.c
 
+src/proc_io_server.obj: src/proc_io_server.c $(INCLUDES)
+        $(CC) $(CFLAGS) src/proc_io_server.c
+
+src/proc_io_client.obj: src/proc_io_client.c $(INCLUDES)
+        $(CC) $(CFLAGS) src/proc_io_client.c
+
 src/conf_preprocessor.obj: src/conf_preprocessor.c $(INCLUDES)
         $(CC) $(CFLAGS) src/conf_preprocessor.c
 
@@ -540,6 +551,9 @@ src/win.obj: src/windows/win.c $(INCLUDES)
 src/unrealsvc.obj: src/windows/unrealsvc.c $(INCLUDES)
 	$(CC) $(CFLAGSST) src/windows/unrealsvc.c
 
+src/unrealircdctl.obj: src/unrealircdctl.c $(INCLUDES)
+	$(CC) $(CFLAGS) src/unrealircdctl.c
+
 src/modules.obj: src/modules.c $(INCLUDES)
 	$(CC) $(CFLAGS) src/modules.c
 
@@ -623,6 +637,10 @@ src/windows/unrealsvc.res: src/windows/unrealsvc.rc
         $(RC) /l 0x409 /fosrc/windows/unrealsvc.res /i ./include /i ./src \
               /d NDEBUG src/windows/unrealsvc.rc
 
+src/windows/unrealircdctl.res: src/windows/unrealircdctl.rc
+        $(RC) /l 0x409 /fosrc/windows/unrealircdctl.res /i ./include /i ./src \
+              /d NDEBUG src/windows/unrealircdctl.rc
+
 ################# Modules #################
 
 CUSTOMMODULE: src/modules/third/$(MODULEFILE).c
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 6.0.1.1.
+# Generated by GNU Autoconf 2.69 for unrealircd 6.0.3.
 #
 # Report bugs to <https://bugs.unrealircd.org/>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unrealircd'
 PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='6.0.1.1'
-PACKAGE_STRING='unrealircd 6.0.1.1'
+PACKAGE_VERSION='6.0.3'
+PACKAGE_STRING='unrealircd 6.0.3'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
@@ -659,6 +659,7 @@ PKG_CONFIG_LIBDIR
 PKG_CONFIG_PATH
 PKG_CONFIG
 LDFLAGS_PRIVATELIBS
+CONTROLFILE
 PIDFILE
 DOCDIR
 PERMDATADIR
@@ -752,6 +753,7 @@ with_tmpdir
 with_datadir
 with_docdir
 with_pidfile
+with_controlfile
 with_privatelibdir
 with_maxconnections
 with_no_operoverride
@@ -1345,7 +1347,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 6.0.1.1 to adapt to many kinds of systems.
+\`configure' configures unrealircd 6.0.3 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1411,7 +1413,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unrealircd 6.0.1.1:";;
+     short | recursive ) echo "Configuration of unrealircd 6.0.3:";;
    esac
   cat <<\_ACEOF
 
@@ -1460,6 +1462,7 @@ Optional Packages:
   --with-datadir=path     Specify the directory where permanent data is stored
   --with-docdir=path      Specify the directory where documentation is stored
   --with-pidfile=path     Specify the path of the pid file
+  --with-controlfile=path Specify the path of the control socket
   --with-privatelibdir=path
                           Specify the directory where private libraries are
                           stored. Disable when building a package for a distro
@@ -1586,7 +1589,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unrealircd configure 6.0.1.1
+unrealircd configure 6.0.3
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1955,7 +1958,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 6.0.1.1, which was
+It was created by unrealircd $as_me 6.0.3, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2363,7 +2366,7 @@ _ACEOF
 
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="1"
+UNREAL_VERSION_MINOR="3"
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
@@ -2373,7 +2376,7 @@ _ACEOF
 # The version suffix such as a beta marker or release candidate
 # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a
 # string instead of an integer because it contains arbitrary data.
-UNREAL_VERSION_SUFFIX=".1"
+UNREAL_VERSION_SUFFIX=""
 
 cat >>confdefs.h <<_ACEOF
 #define UNREAL_VERSION_SUFFIX "$UNREAL_VERSION_SUFFIX"
@@ -6548,6 +6551,25 @@ fi
 
 
 
+# Check whether --with-controlfile was given.
+if test "${with_controlfile+set}" = set; then :
+  withval=$with_controlfile;
+cat >>confdefs.h <<_ACEOF
+#define CONTROLFILE "$withval"
+_ACEOF
+
+		CONTROLFILE="$withval"
+else
+
+cat >>confdefs.h <<_ACEOF
+#define CONTROLFILE "$HOME/unrealircd/data/unrealircd.ctl"
+_ACEOF
+
+		CONTROLFILE="$HOME/unrealircd/data/unrealircd.ctl"
+fi
+
+
+
 # Check whether --with-privatelibdir was given.
 if test "${with_privatelibdir+set}" = set; then :
   withval=$with_privatelibdir;
@@ -6589,6 +6611,7 @@ fi
 
 
 
+
 # Check whether --with-maxconnections was given.
 if test "${with_maxconnections+set}" = set; then :
   withval=$with_maxconnections; ac_fd=$withval
@@ -7438,7 +7461,7 @@ fi
 
 if test "$has_system_pcre2" = "no"; then :
 
-pcre2_version="10.36"
+pcre2_version="10.39"
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting PCRE2 regex library" >&5
 $as_echo "extracting PCRE2 regex library" >&6; }
 cur_dir=`pwd`
@@ -7455,7 +7478,7 @@ fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring PCRE2 regex library" >&5
 $as_echo "configuring PCRE2 regex library" >&6; }
 cd pcre2-$pcre2_version
-./configure --enable-jit --enable-shared --disable-unicode --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
+./configure --enable-jit --enable-shared --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling PCRE2 regex library" >&5
 $as_echo "compiling PCRE2 regex library" >&6; }
 $ac_cv_prog_MAKER || exit 1
@@ -7798,7 +7821,7 @@ fi
 
 if test "$has_system_cares" = "no"; then :
 
-cares_version="1.17.2"
+cares_version="1.18.1"
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting c-ares resolver library" >&5
 $as_echo "extracting c-ares resolver library" >&6; }
 cur_dir=`pwd`
@@ -9391,7 +9414,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 6.0.1.1, which was
+This file was extended by unrealircd $as_me 6.0.3, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -9454,7 +9477,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 6.0.1.1
+unrealircd config.status 6.0.3
 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], [6.0.1.1], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [6.0.3], [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,13 +34,13 @@ 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=["1"]
+UNREAL_VERSION_MINOR=["3"]
 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
 # marker. (e.g.: -rcX for unrealircd-3.2.9-rcX). This macro is a
 # string instead of an integer because it contains arbitrary data.
-UNREAL_VERSION_SUFFIX=[".1"]
+UNREAL_VERSION_SUFFIX=[""]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_SUFFIX], ["$UNREAL_VERSION_SUFFIX"], [Version suffix such as a beta marker or release candidate marker. (e.g.: -rcX for unrealircd-3.2.9-rcX)])
 
 AC_PATH_PROG(RM,rm)
@@ -484,6 +484,12 @@ AC_ARG_WITH(pidfile, [AS_HELP_STRING([--with-pidfile=path],[Specify the path of 
 	[AC_DEFINE_UNQUOTED([PIDFILE], ["$HOME/unrealircd/data/unrealircd.pid"], [Define the path of the pid file])
 		PIDFILE="$HOME/unrealircd/data/unrealircd.pid"])
 
+AC_ARG_WITH(controlfile, [AS_HELP_STRING([--with-controlfile=path],[Specify the path of the control socket])],
+	[AC_DEFINE_UNQUOTED([CONTROLFILE], ["$withval"], [Define the path of the control socket])
+		CONTROLFILE="$withval"],
+	[AC_DEFINE_UNQUOTED([CONTROLFILE], ["$HOME/unrealircd/data/unrealircd.ctl"], [Define the path of the control socket])
+		CONTROLFILE="$HOME/unrealircd/data/unrealircd.ctl"])
+
 dnl Ensure that this “feature” can be disabled as it makes it harder to package unrealircd.
 dnl Users have always been able to specify “./configure LDFLAGS=-Wl,-rpath,/path/to/blah”—binki
 AC_ARG_WITH(privatelibdir, [AS_HELP_STRING([--with-privatelibdir=path],[Specify the directory where private libraries are stored. Disable when building a package for a distro])],
@@ -514,6 +520,7 @@ dnl well, Because DATADIR conflicts with the Windows SDK header files.. amazing.
 AC_SUBST(PERMDATADIR)
 AC_SUBST(DOCDIR)
 AC_SUBST(PIDFILE)
+AC_SUBST(CONTROLFILE)
 AC_SUBST(LDFLAGS_PRIVATELIBS)
 
 AC_ARG_WITH(maxconnections, [AS_HELP_STRING([--with-maxconnections=size], [Specify the max file descriptors to use])],
@@ -576,7 +583,7 @@ AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libpcre2*])],[has
 
 AS_IF([test "$has_system_pcre2" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW PCRE2 RELEASE!
-pcre2_version="10.36"
+pcre2_version="10.39"
 AC_MSG_RESULT(extracting PCRE2 regex library)
 cur_dir=`pwd`
 cd extras
@@ -593,7 +600,7 @@ else
 fi
 AC_MSG_RESULT(configuring PCRE2 regex library)
 cd pcre2-$pcre2_version
-./configure --enable-jit --enable-shared --disable-unicode --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
+./configure --enable-jit --enable-shared --prefix=$cur_dir/extras/pcre2 --libdir=$PRIVATELIBDIR || exit 1
 AC_MSG_RESULT(compiling PCRE2 regex library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing PCRE2 regex library)
@@ -716,7 +723,7 @@ AS_IF([test "$has_system_cares" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW C-ARES RELEASE!
 dnl NOTE: when changing this here, ALSO change it in extras/curlinstall
 dnl       and in the comment in this file around line 400!
-cares_version="1.17.2"
+cares_version="1.18.1"
 AC_MSG_RESULT(extracting c-ares resolver library)
 cur_dir=`pwd`
 cd extras
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 6.0.1.1
+                                for UnrealIRCd 6.0.3
                                     
 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,8 +1,139 @@
+UnrealIRCd 6.0.3
+=================
+
+A number of serious issues were discovered in UnrealIRCd 6. Among these is
+an issue which will likely crash the IRCd sooner or later if you /REHASH
+with any active clients connected.
+We suggest everyone who is running UnrealIRCd 6 to upgrade to 6.0.3.
+
+If you are already running UnrealIRCd 6 then read below. Otherwise, jump
+straight to the [summary about UnrealIRCd 6](#Summary) to learn more
+about UnrealIRCd 6.
+
+Fixes:
+* Crash in `WATCH` if the IRCd has been rehashed at least once. After doing
+  a `REHASH` with active clients it will likely corrupt memory. It may take
+  several days until after the rehash for the crash to occur, or even
+  weeks/months on smaller networks (accidental triggering, that is).
+* A `REHASH` with certain remote includes setups could cause a crash or
+  other weird and confusing problems such as complaining about unable
+  to open an ipv6-database or missing snomask configuration.
+  This only affected some people with remote includes, not all.
+* Potential out-of-bounds write in sending code. In practice it seems
+  harmless on most servers but this cannot be 100% guaranteed.
+* Unlikely triggered log message would log uninitialized stack data to the
+  log file or send it to ircops.
+* Channel ops could not remove halfops from a user (`-h`).
+* After using the `RESTART` command (not recommended) the new IRCd was
+  often no longer writing to log files.
+* Fix compile problem if you choose to use cURL remote includes but don't
+  have cURL on the system and ask UnrealIRCd to compile cURL.
+
+Enhancements:
+* The default text log format on disk changed. It now includes the server
+  name where the event was generated. Without this, it was sometimes
+  difficult to trace problems, since previously it sometimes looked like
+  there was a problem on your server when it was actually another server
+  on the network.
+  * Old log format: `[DATE TIME] subsystem.EVENT_ID loglevel: ........`
+  * New log format: `[DATE TIME] servername subsystem.EVENT_ID loglevel: ........`
+
+Changes:
+* Any MOTD lines added by services via
+  [`SVSMOTD`](https://www.unrealircd.org/docs/MOTD_and_Rules#SVSMOTD)
+  are now shown at the end of the MOTD-on-connect (unless using a shortmotd).
+  Previously the lines were only shown if you manually ran the `MOTD` command.
+
+Developers and protocol:
+* `LIST C<xx` now means: filter on channels that are created less
+  than `xx` minutes ago. This is the opposite of what we had earlier.
+  `LIST T<xx` is now supported as well (topic changed in last xx minutes),
+  it was already advertised in ELIST but support was not enabled previously.
+
+UnrealIRCd 6.0.2
+-----------------
+UnrealIRCd 6.0.2 comes with several nice feature enhancements along with
+some fixes. It also includes a fix for a crash bug that can be triggered
+by ordinary users.
+
+Fixes:
+* Fix crash that can be triggered by regular users if you have any `deny dcc`
+  blocks in the config or any spamfilters with the `d` (DCC) target.
+  NOTE: You don't *have* to upgrade to 6.0.2 to fix this, you can also
+  hot-patch this issue without restart, see the news announcement.
+* Windows: fix crash with IPv6 clients (local or remote) due to GeoIP lookup
+* Fix infinite hang on "Loading IRCd configuration" if DNS is not working.
+  For example if the 1st DNS server in `/etc/resolv.conf` is down or refusing
+  requests.
+* Some `MODE` server-to-server commands were missing a timestamp at the end,
+  even though this is mandatory for modes coming from a server.
+* The [channeldb](https://www.unrealircd.org/docs/Set_block#set::channeldb)
+  module now converts letter extbans to named extbans (eg `~a` to `~account`).
+  Previously it did not, which caused letter extbans to appear in the banlist.
+  Later on, when linking servers, this would cause duplicate entries to appear
+  as well, with both the old and new format. The extbans were still effective
+  though, so this is mostly a visual +b/+e/+I list issue.
+* Some [Extended Server Bans](https://www.unrealircd.org/docs/Extended_server_bans)
+  were not working correctly for WEBIRC proxies. In particular, a server ban
+  or exempt (ELINE) on `~country:XX` was only checked against the WEBIRC proxy.
+
+Enhancements:
+* Support for [logging to a channel](https://www.unrealircd.org/docs/Log_block#Logging_to_a_channel).
+  Similar to snomasks but then for channels.
+* Command line interface changes:
+  * The [CLI tool](https://www.unrealircd.org/docs/Command_Line_Interface) now
+    communicates to the running UnrealIRCd process via a UNIX socket to
+    send commands and retrieve output.
+  * The command `./unrealircd rehash` will now show the rehash output,
+    including warnings and errors, and return a proper exit code.
+  * The same for `./unrealircd reloadtls`
+  * New command `./unrealircd status` to show if UnrealIRCd is running, the
+    version, channel and user count, ..
+  * The command `./unrealircd genlinkblock` is now
+    [documented](https://www.unrealircd.org/docs/Linking_servers_(genlinkblock))
+    and is referred to from the
+    [Linking servers tutorial](https://www.unrealircd.org/docs/Tutorial:_Linking_servers).
+  * On Windows in the `C:\Program Files\UnrealIRCd 6\bin` directory there is
+    now an `unrealircdctl.exe` that can be used to do similar things to what
+    you can do on *NIX. Supported operations are: `rehash`, `reloadtls`,
+    `mkpasswd`, `gencloak` and `spkifp`.
+* New option [set::server-notice-show-event](https://www.unrealircd.org/docs/Set_block#set::server-notice-show-event)
+  which can be set to `no` to hide the event information (eg `connect.LOCAL_CLIENT_CONNECT`)
+  in server notices. This can be overridden per-oper in the
+  [Oper block](https://www.unrealircd.org/docs/Oper_block) via oper::server-notice-show-event.
+* Support for IRC over UNIX sockets (on the same machine), if you specify a
+  file in the [listen block](https://www.unrealircd.org/docs/Listen_block)
+  instead of an ip/port. This probably won't be used much, but the option is
+  there. Users will show up with a host of `localhost` and IP `127.0.0.1` to
+  keep things simple.
+* The `MAP` command now shows percentages of users
+* Add `WHO` option to search clients by time connected (eg. `WHO <300 t` to
+  search for less than 300 seconds)
+* Rate limiting of `MODE nick -x` and `-t` via new `vhost-flood` option in
+  [set::anti-flood block](https://www.unrealircd.org/docs/Anti-flood_settings).
+
+Changes:
+* Update Russian `help.ru.conf`.
+
+Developers and protocol:
+* People packaging UnrealIRCd (eg. to an .rpm/.deb):
+  * Be sure to pass the new `--with-controlfile` configure option
+  * There is now an `unrealircdctl` tool that the `unrealircd` shell script
+    uses, it is expected to be in `bindir`.
+* `SVSMODE #chan -b nick` will now correctly remove extbans that prevent `nick`
+  from joining. This fixes a bug where it would remove too much (for `~time`)
+  or not remove extbans (most other extbans, eg `~account`).
+  `SVSMODE #chan -b` has also been fixed accordingly (remove all bans
+  preventing joins).
+  Note that all these commands do not remove bans that do not affect joins,
+  such as `~quiet` or `~text`.
+* For module coders: setting the `EXTBOPT_CHSVSMODE` flag in `extban.options`
+  is no longer useful, the flag is ignored. We now decide based on
+  `BANCHK_JOIN` being in `extban.is_banned_events` if the ban should be
+  removed or not upon SVS(2)MODE -b.
+
 UnrealIRCd 6.0.1.1
-===================
-If you are already running UnrealIRCd 6 then read below on the 
-changes between 6.0.0 and 6.0.1(.1). Otherwise, jump straight to the
-[summary about UnrealIRCd 6](#Summary) to learn more about UnrealIRCd 6.
+-------------------
 
 Fixes:
 * In 6.0.1.1: extended bans were not properly synced between U5 and U6.
@@ -89,6 +220,8 @@ Enhancements
   * Colors are enabled by default in snomask server notices, these can be disabled via
     [set::server-notice-colors](https://www.unrealircd.org/docs/Set_block#set::server-notice-colors)
     and also in [oper::server-notice-colors](https://www.unrealircd.org/docs/Oper_block)
+  * Support for [logging to a channel](https://www.unrealircd.org/docs/Log_block#Logging_to_a_channel).
+    Similar to snomasks but then for channels. *Requires UnrealIRCd 6.0.2 or later*
 * Almost all channel modes are modularized
   * Only the three list modes (+b/+e/+I) are still in the core
   * The five [level modes](https://www.unrealircd.org/docs/Channel_Modes#Access_levels)
diff --git a/doc/conf/modules.sources.list b/doc/conf/modules.sources.list
@@ -1 +0,0 @@
-https://modules.unrealircd.org/modules.list
-\ No newline at end of file
diff --git a/doc/conf/snomasks.conf b/doc/conf/snomasks.conf
@@ -1,228 +1,50 @@
-/* Server bans snomask - 'b' */
-log {
-	source {
-		tkl.BAN_REALNAME;
-		tkl.TKL_ADD;
-		tkl.TKL_DEL;
-		tkl.TKL_ADD_TEMPSHUN;
-		tkl.TKL_DEL_TEMPSHUN;
-		tkl.TKL_EXPIRE;
-		tkl.RMTKL_COMMAND;
-	}
-	destination {
-		snomask b;
-	}
-}
-
-/* Blacklist snomask: 'B' */
 log {
 	source {
+		!debug;
 		blacklist;
-	}
-	destination {
-		snomask B;
-	}
-}
-
-/* Local client connects snomask - 'c' */
-log {
-	source {
+		chgcmds;
 		connect.LOCAL_CLIENT_CONNECT;
 		connect.LOCAL_CLIENT_DISCONNECT;
-	}
-	destination {
-		snomask c;
-	}
-}
-
-/* Remote client connects snomask - 'C' */
-log {
-	source {
 		connect.REMOTE_CLIENT_CONNECT;
 		connect.REMOTE_CLIENT_DISCONNECT;
-	}
-	destination {
-		snomask C;
-	}
-}
-
-/* DCC rejections snomask - 'd' */
-log {
-	source {
 		dcc;
-	}
-	destination {
-		snomask d;
-	}
-}
-
-/* Debug snomask (not recommended) - 'D' */
-log {
-	source {
-		debug;
-	}
-	destination {
-		snomask D;
-	}
-}
-
-/* Floods snomask - 'f' */
-log {
-	source {
 		flood;
-	}
-	destination {
-		snomask f;
-	}
-}
-
-/* Join, parts, kicks - 'j' */
-log {
-	source {
-		// TODO: these don't exist yet..
-		join.LOCAL_CLIENT_JOIN;
-		join.REMOTE_CLIENT_JOIN;
-		part.LOCAL_CLIENT_PART;
-		part.REMOTE_CLIENT_PART;
-		kick.LOCAL_CLIENT_KICK;
-		kick.REMOTE_CLIENT_KICK;
-	}
-	destination {
-		snomask j;
-	}
-}
-
-/* Kill snomask */
-log {
-	source {
 		kill;
-	}
-	destination {
-		snomask k;
-	}
-}
-
-/* Local nick changes snomask - 'n' */
-log {
-	source {
-		nick.LOCAL_NICK_CHANGE;
-	}
-	destination {
-		snomask n;
-	}
-}
-
-/* Remote nick changes snomask - 'N' */
-log {
-	source {
-		nick.REMOTE_NICK_CHANGE;
-	}
-	destination {
-		snomask N;
-	}
-}
-
-/* Deny nick (QLINE) rejections snomask - 'q' */
-log {
-	source {
+		link;
 		nick.QLINE_NICK_LOCAL_ATTEMPT;
 		nick.QLINE_NICK_REMOTE;
-	}
-	destination {
-		snomask q;
-	}
-}
-
-/* Spamfilter hits snomask - 'S' */
-log {
-	source {
-		tkl.SPAMFILTER_MATCH;
-	}
-	destination {
-		snomask S;
-	}
-}
-
-/* IRCOp overriding in channels (OperOverride) - 'o' */
-log {
-	source {
+		nomatch;
+		oper;
 		operoverride;
-	}
-	destination {
-		snomask o;
-	}
-}
-
-/* IRCOp changing user properties or forcing users to do things - 'O' */
-log {
-	source {
-		chgcmds;
 		sacmds;
-	}
-	destination {
-		snomask O;
-	}
-}
-
-/* VHOST usage - 'v' */
-log {
-	source {
+		tkl.BAN_REALNAME;
+		tkl.RMTKL_COMMAND;
+		tkl.SPAMFILTER_MATCH;
+		tkl.TKL_ADD;
+		tkl.TKL_DEL;
+		tkl.TKL_ADD_TEMPSHUN;
+		tkl.TKL_DEL_TEMPSHUN;
+		tkl.TKL_EXPIRE;
 		vhost;
 	}
-	destination {
-		snomask v;
-	}
+	destination { snomask o; }
 }
 
-/* Snomask s (server notices) - the "catch all" snomask for all other things */
 log {
 	source {
-		link;
-		oper;
 		!debug;
-		nomatch;
-	}
-	destination {
-		snomask s;
-	}
-}
-
-/* These log sources are sent to all servers (globally).
- * These are generally two categories:
- * 1) Things that affect the network as a whole, eg linking
- * 2) Things that otherwise cannot be logged by a remote server
- *    that may interest ircops. Eg: a spamfilter match,
- *    since that would otherwise not be propagated.
- */
-log {
-	source {
-		/* All link messages affect the network so
-		 * these should be global. Except for the
-		 * link connecting... and timeout while
-		 * connecting.. messages, which can be noisy.
-		 */
+		flood;
 		link;
 		!link.LINK_CONNECTING;
 		!link.LINK_CONNECT_TIMEOUT;
 		!link.SERVER_LINKED_REMOTE;
 		!link.SERVER_LINKED;
-		/* All oper up/downs */
 		oper;
-		/* Flood messages, important to keep an eye on, network-wide */
-		flood;
-		/* TEMPSHUN: these are otherwise missing for snomask 'b' */
+		samode.SAMODE_COMMAND;
 		tkl.TKL_ADD_TEMPSHUN;
 		tkl.TKL_DEL_TEMPSHUN;
-		/* Spamfilter matches: needed for snomask 'S' */
 		tkl.SPAMFILTER_MATCH;
-		/* Critical issue: */
 		tls.TLS_CERT_EXPIRING;
-		/* SAMODE: needed for snomask 'o' */
-		samode.SAMODE_COMMAND;
-		/* Never any debug messages */
-		!debug;
 	}
-	destination {
-		remote;
-	}
-}
+	destination { remote; }
+}
+\ No newline at end of file
diff --git a/doc/conf/unrealircd.link.conf b/doc/conf/unrealircd.link.conf
@@ -3,5 +3,6 @@ include "https://USERNAME:PASSWORD@HOSTNAME:PORT/except.conf";
 include "https://USERNAME:PASSWORD@HOSTNAME:PORT/ircd.conf";
 include "https://USERNAME:PASSWORD@HOSTNAME:PORT/modules.conf";
 include "https://USERNAME:PASSWORD@HOSTNAME:PORT/opers.conf";
+include "https://USERNAME:PASSWORD@HOSTNAME:PORT/snomasks.conf";
 include "https://USERNAME:PASSWORD@HOSTNAME:PORT/spamfilter.conf";
 me { name "example.supernets.org"; info "SuperNETS IRC Network"; sid XXX; }
 \ No newline at end of file
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -17,11 +17,6 @@ class servers { pingfreq 120; maxclients  10; sendq 1M; connfreq 30;           }
 allow { mask *;         class clients; maxperip 2;  global-maxperip 2;  }
 allow { mask 127.0.0.1; class clients; maxperip 10; global-maxperip 10; }
 
-#require authentication {
-#	mask *@*;
-#	reason "8,4   E N T E R   T H E   V O I D   ";
-#}
-
 listen { ip *; port 6667;      options { clientsonly;      } }
 listen { ip *; port 6697;      options { clientsonly; tls; } }
 listen { ip *; port REDACTED;  options { serversonly; tls; } }
@@ -45,13 +40,13 @@ link irc.supernets.org {
 
 log {
 	source { error; fatal; warn; }
-	destination { file "ircd.log" { maxsize 10M; } }
+	destination { file "ircd.log" { maxsize 5M; } }
 }
 
-log {
-	source { all; }
-	destination { channel "#REDACTED" }
-}
+#log {
+#	source { all; }
+#	destination { channel "#REDACTED" }
+#}
 
 tld { mask *@*; motd remote.motd; rules remote.motd; options { remote; } }
 
@@ -95,7 +90,7 @@ set {
 	gline-address "enterthevoid@supernets.org";
 	modes-on-connect "+iIpTx";
 	modes-on-oper "+Hq";
-	snomask-on-oper "+bBcCfksSoO";
+	snomask-on-oper "+o";
 	modes-on-join "+ns";
 	level-on-join "op";
 	restrict-usermodes "ips";
@@ -127,7 +122,7 @@ set {
 		"REDACTED";
 		"REDACTED";
 	}
-	hiddenhost-prefix "SUPER";
+	cloak-prefix "SUPER";
 	plaintext-policy {
 		user warn;
 		oper deny;
@@ -139,8 +134,8 @@ set {
 		user warn;
 		oper deny;
 		server deny;
-		user-message "4WARNING: You are using an outdated SSL/TLS protocol or cipher";
-		oper-message "Network operators must be using an up-to-date SSL/TLS protocol & cipher";
+		user-message "4WARNING: You are using an outdated TLS protocol or cipher";
+		oper-message "Network operators must be using an up-to-date TLS protocol & cipher";
 	}
 	anti-flood {
 		everyone {
@@ -209,9 +204,10 @@ set {
 		ban-reason "8,4   E N T E R   T H E   V O I D   ";
 	}
 	connthrottle {
-		known-users   { minimum-reputation-score 25; sasl-bypass yes;       }
-		new-users     { local-throttle 20:60;        global-throttle 30:60; }
-		disabled-when { reputation-gathering 1w;     start-delay 3m;        }
+		known-users   { minimum-reputation-score 100; sasl-bypass yes;       }
+		new-users     { local-throttle 20:60;         global-throttle 30:60; }
+		disabled-when { reputation-gathering 1w;      start-delay 3m;        }
+		reason "8,4   E N T E R   T H E   V O I D   ";
 	}
 	history {
 		channel {
@@ -224,24 +220,24 @@ set {
 	}
 	hide-idle-time { policy always; }
 	whois-details {
-		basic           { everyone full; }
-		modes           { everyone none;    self full; oper full; }
-		realhost        { everyone none;    self full; oper full; }
-		registered-nick { everyone full; }
-		channels        { everyone limited; self full; oper full; }
-		server          { everyone full; }
+		account         { everyone full; }
 		away            { everyone full; }
+		basic           { everyone full; }
+		bot             { everyone full; }
+		certfp          { everyone full; }
+		channels        { everyone none; self full; oper full; }
+		geo             { everyone none; }
+		idle            { everyone none; }
+		modes           { everyone none; self full; oper full; }
 		oper            { everyone limited; self full; oper full; }
+		realhost        { everyone none; self full; oper full; }
+		registered-nick { everyone full; }
+		reputation      { everyone full; }
 		secure          { everyone limited; self full; oper full; }
-		bot             { everyone full; }
+		server          { everyone full; }
 		services        { everyone full; }
-		reputation      { everyone none;    self none; oper full; }
-		geo             { everyone none;    self none; oper full; }
-		certfp          { everyone full; }
-		shunned         { everyone none;    self none; oper full; }
-		account         { everyone full; }
+		shunned         { everyone none; self none; oper full; }
 		swhois          { everyone full; }
-		idle            { everyone limited; self full; oper full; }
 	}
 }
 
@@ -254,5 +250,5 @@ hideserver {
 
 security-group known-users {
 	identified yes;
-	reputation-score 25;
+	reputation-score 100;
 }
 \ No newline at end of file
diff --git a/extras/build-tests/nix/run-tests.bbwrapper b/extras/build-tests/nix/run-tests.bbwrapper
@@ -8,7 +8,7 @@
 # in case it misbehaves
 #
 set +ex
-timeout --kill-after=5 600 extras/build-tests/nix/run-tests
+timeout --kill-after=5 900 extras/build-tests/nix/run-tests
 EX="$?"
 killall -9 valgrind valgrind.bin memcheck memcheck-amd64-linux memcheck-x86-linux ircd unrealircd val 1>/dev/null 2>&1
 exit $EX
diff --git a/extras/build-tests/windows/build.bat b/extras/build-tests/windows/build.bat
@@ -36,11 +36,11 @@ rem Now the actual build
 rem - First this, otherwise JOM will fail
 IF NOT EXIST src\version.c nmake -f Makefile.windows CONF
 rem - Then build most of UnrealIRCd.exe etc
-call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe unrealircdctl.exe
 rem - It will fail due to missing symbolfile, which we create here..
 nmake -f makefile.windows SYMBOLFILE
 rem - Then we finalize building UnrealIRCd.exe: should be no error
-call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe
+call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat UNREALSVC.EXE UnrealIRCd.exe unrealircdctl.exe
 if %ERRORLEVEL% NEQ 0 EXIT /B 1
 rem - Build all the modules (DLL files): should be no error
 call extras\build-tests\windows\compilecmd\%SHORTNAME%.bat MODULES
diff --git a/extras/c-ares.tar.gz b/extras/c-ares.tar.gz
Binary files differ.
diff --git a/extras/curlinstall b/extras/curlinstall
@@ -2,10 +2,8 @@
 URL="https://www.unrealircd.org/files/curl-latest.tar.gz"
 OUTF="curl-latest.tar.gz"
 OUTD="curl-latest"
-ARESPATH="`pwd`/extras/c-ares"
 UNREALDIR="`pwd`"
-CARESVERSION="1.17.2"
-LIBDIR="$1"
+PRIVATELIBDIR="$1"
 
 if [ "x$1" = "x" ]; then
 	echo "You should (no longer) run this program directly."
@@ -73,26 +71,9 @@ else
         n="-n"
 fi
 
-if [ ! -d "$ARESPATH/lib" ]; then
-	echo "c-ares has not been build yet, let's do that now..."
-	cd ../extras/
-	tar xzf c-ares.tar.gz || exit 1
-	cd c-ares-$CARESVERSION || exit 1
-	./configure --prefix=$ARESPATH || exit 1
-	(make && make install) || exit 1
-	cd ../../tmp/
-	echo "c-ares built."
-	echo ""
-fi
-
 # We assume curl has been packaged in a way it will extract to "$OUTD"/
 cd "$OUTD" || exit 1
 
 echo "Building and installing libcurl"
-CPPFLAGS="-I$ARESPATH/include" ./configure --prefix=$UNREALDIR/extras/curl --libdir=$LIBDIR --enable-shared \
- --enable-ares=$ARESPATH --with-openssl
-cp -R $ARESPATH/lib ares
+./configure --prefix=$UNREALDIR/extras/curl --libdir=$PRIVATELIBDIR --enable-shared --with-openssl
 make && make install
-
-#cp $ARESPATH/lib/libcares.a $HOME/curl/lib
-# that isn't needed anymore as the lib is already in unreal...
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         = 6.0.1.1
+PROJECT_NUMBER         = 6.0.3
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/extras/pcre2.tar.gz b/extras/pcre2.tar.gz
Binary files differ.
diff --git a/include/dynconf.h b/include/dynconf.h
@@ -170,6 +170,7 @@ struct Configuration {
 	char *stats_server;
 	char *sasl_server;
 	int server_notice_colors;
+	int server_notice_show_event;
 };
 
 extern MODVAR Configuration iConf;
diff --git a/include/h.h b/include/h.h
@@ -28,6 +28,34 @@
 #include "setup.h"
 #include "fdlist.h"
 
+extern int dorehash, dorestart, doreloadcert;
+#ifndef _WIN32
+extern char **myargv;
+#else
+extern LPCSTR cmdLine;
+#endif
+/* Externals */
+extern MODVAR char *buildid;
+extern MODVAR char backupbuf[8192];
+extern EVENT(unrealdns_removeoldrecords);
+extern EVENT(unrealdb_expire_secret_cache);
+extern void init_glines(void);
+extern void tkl_init(void);
+extern void process_clients(void);
+extern void unrealdb_test(void);
+extern void ignore_this_signal();
+extern void s_rehash();
+extern void s_reloadcert();
+extern void s_restart();
+extern void s_die();
+#ifndef _WIN32
+// nix specific
+extern char unreallogo[];
+#else
+// windows specific
+extern SERVICE_STATUS_HANDLE IRCDStatusHandle;
+extern SERVICE_STATUS IRCDStatus;
+#endif
 extern MODVAR char *extraflags;
 extern MODVAR int tainted;
 extern MODVAR Member *freemember;
@@ -117,7 +145,7 @@ extern void ipport_seperate(const char *string, char **ip, char **port);
 extern ConfigItem_class	*find_class(const char *name);
 extern ConfigItem_oper		*find_oper(const char *name);
 extern ConfigItem_operclass	*find_operclass(const char *name);
-extern ConfigItem_listen *find_listen(const char *ipmask, int port, int ipv6);
+extern ConfigItem_listen *find_listen(const char *ipmask, int port, SocketType socket_type);
 extern ConfigItem_sni *find_sni(const char *name);
 extern ConfigItem_ulines	*find_uline(const char *host);
 extern ConfigItem_tld		*find_tld(Client *cptr);
@@ -153,6 +181,7 @@ extern MODVAR struct list_head lclient_list;
 extern MODVAR struct list_head server_list;
 extern MODVAR struct list_head oper_list;
 extern MODVAR struct list_head unknown_list;
+extern MODVAR struct list_head control_list;
 extern MODVAR struct list_head global_server_list;
 extern MODVAR struct list_head dead_list;
 extern RealCommand *find_command(const char *cmd, int flags);
@@ -489,6 +518,7 @@ extern int  channel_canjoin(Client *client, const char *name);
 extern char *collapse(char *pattern);
 extern void dcc_sync(Client *client);
 extern void request_rehash(Client *client);
+extern int rehash_internal(Client *client);
 extern void s_die();
 extern int match_simple(const char *mask, const char *name);
 extern int match_esc(const char *mask, const char *name);
@@ -507,7 +537,7 @@ extern void rehash_motdrules();
 extern void read_motd(const char *filename, MOTDFile *motd); /* s_serv.c */
 extern void send_proto(Client *, ConfigItem_link *);
 extern void unload_all_modules(void);
-extern void set_sock_opts(int fd, Client *cptr, int ipv6);
+extern void set_sock_opts(int fd, Client *cptr, SocketType socket_type);
 extern void stripcrlf(char *line);
 extern int strnatcmp(char const *a, char const *b);
 extern int strnatcasecmp(char const *a, char const *b);
@@ -663,7 +693,6 @@ extern int spamfilter_getconftargets(const char *s);
 extern void remove_all_snomasks(Client *client);
 extern void remove_oper_modes(Client *client);
 extern char *spamfilter_inttostring_long(int v);
-extern MODVAR char backupbuf[];
 extern int is_invited(Client *client, Channel *channel);
 extern void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel, int hide_local_modes);
 extern int op_can_override(const char *acl, Client *client,Channel *channel,void* extra);
@@ -859,6 +888,7 @@ extern const char *cmdname_by_spamftarget(int target);
 extern void unrealdns_delreq_bycptr(Client *cptr);
 extern void unrealdns_gethostbyname_link(const char *name, ConfigItem_link *conf, int ipv4_only);
 extern void unrealdns_delasyncconnects(void);
+extern EVENT(unrealdns_timeout);
 extern int is_autojoin_chan(const char *chname);
 extern void unreal_free_hostent(struct hostent *he);
 extern struct hostent *unreal_create_hostent(const char *name, const char *ip);
@@ -915,10 +945,14 @@ extern void report_crash(void);
 extern void modulemanager(int argc, char *argv[]);
 extern int inet_pton4(const char *src, unsigned char *dst);
 extern int inet_pton6(const char *src, unsigned char *dst);
-extern int unreal_bind(int fd, const char *ip, int port, int ipv6);
+extern int unreal_bind(int fd, const char *ip, int port, SocketType socket_type);
 extern int unreal_connect(int fd, const char *ip, int port, int ipv6);
 extern int is_valid_ip(const char *str);
 extern int ipv6_capable(void);
+extern int unix_sockets_capable(void);
+#ifdef _WIN32
+extern void init_winsock(void);
+#endif
 extern MODVAR Client *remote_rehash_client;
 extern MODVAR int debugfd;
 extern void convert_to_absolute_path(char **path, const char *reldir);
@@ -929,7 +963,7 @@ extern Cmode_t get_extmode_bitbychar(char m);
 extern long find_user_mode(char mode);
 extern void start_listeners(void);
 extern void buildvarstring(const char *inbuf, char *outbuf, size_t len, const char *name[], const char *value[]);
-extern void reinit_tls(void);
+extern int reinit_tls(void);
 extern CMD_FUNC(cmd_error);
 extern CMD_FUNC(cmd_dns);
 extern CMD_FUNC(cmd_info);
@@ -1135,6 +1169,8 @@ extern void flood_limit_exceeded_log(Client *client, const char *floodname);
 /* logging */
 extern int config_test_log(ConfigFile *conf, ConfigEntry *ce);
 extern int config_run_log(ConfigFile *conf, ConfigEntry *ce);
+extern const char *log_level_terminal_color(LogLevel loglevel);
+#define TERMINAL_COLOR_RESET "\033[0m"
 extern LogType log_type_stringtoval(const char *str);
 extern const char *log_type_valtostring(LogType v);
 #ifdef DEBUGMODE
@@ -1208,3 +1244,8 @@ extern void make_umodestr(void);
 extern void initwhowas(void);
 extern void uid_init(void);
 extern const char *uid_get(void);
+/* proc i/o */
+extern void add_proc_io_server(void);
+extern void procio_post_rehash(int failure);
+/* end of proc i/o */
+extern int minimum_msec_since_last_run(struct timeval *tv_old, long minimum);
diff --git a/include/modules.h b/include/modules.h
@@ -394,7 +394,7 @@ typedef enum ExtbanType {
 #define EXTBANTABLESZ		32
 
 typedef enum ExtbanOptions {
-        EXTBOPT_CHSVSMODE=0x1,		/**< SVSMODE -b/-e/-I will clear this ban */
+        EXTBOPT_CHSVSMODE=0x1,		/**< SVSMODE -b/-e/-I will clear this ban (UNUSED as of 6.0.1+) */
         EXTBOPT_ACTMODIFIER=0x2,	/**< Action modifier (not a matcher). These are extended bans like ~q/~n/~j. */
         EXTBOPT_NOSTACKCHILD=0x4,	/**< Disallow prefixing with another extban. Eg disallow ~n:~T:censor:xyz */
         EXTBOPT_INVEX=0x8,		/**< Available for use with +I too */
@@ -1151,12 +1151,14 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, l
 #define HOOKTYPE_POST_LOCAL_NICKCHANGE	106
 /** See hooktype_post_remote_nickchange() */
 #define HOOKTYPE_POST_REMOTE_NICKCHANGE	107
-/** See hooktype_userhost_changed() */
-#define HOOKTYPE_USERHOST_CHANGED 108
-/** See hooktype_realname_changed() */
-#define HOOKTYPE_REALNAME_CHANGED 109
+/** See hooktype_userhost_change() */
+#define HOOKTYPE_USERHOST_CHANGE 108
+/** See hooktype_realname_change() */
+#define HOOKTYPE_REALNAME_CHANGE 109
 /** See hooktype_can_set_topic() */
 #define HOOKTYPE_CAN_SET_TOPIC	110
+/** See hooktype_ip_change() */
+#define HOOKTYPE_IP_CHANGE	111
 /* Adding a new hook here?
  * 1) Add the #define HOOKTYPE_.... with a new number
  * 2) Add a hook prototype (see below)
@@ -2127,14 +2129,22 @@ int hooktype_post_remote_nickchange(Client *client, MessageTag *mtags, const cha
  * @param oldhost		Old hostname of the client
  * @return The return value is ignored (use return 0)
  */
- 
-int hooktype_realname_changed(Client *client, const char *oldinfo);
+int hooktype_userhost_change(Client *client, const char *olduser, const char *oldhost);
+
 /** Called when user realname has changed.
  * @param client		The client whose realname has changed
  * @param oldinfo		Old realname of the client
  * @return The return value is ignored (use return 0)
  */
-int hooktype_userhost_changed(Client *client, const char *olduser, const char *oldhost);
+int hooktype_realname_change(Client *client, const char *oldinfo);
+
+/** Called when changing IP (eg due to PROXY/WEBIRC/etc).
+ * @param client		The client whose IP has changed
+ * @param oldip			Old IP of the client
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_ip_change(Client *client, const char *oldip);
+
 /** @} */
 
 #ifdef GCC_TYPECHECKING
@@ -2247,8 +2257,9 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_IS_INVITED) && !ValidateHook(hooktype_is_invited, func)) || \
         ((hooktype == HOOKTYPE_POST_LOCAL_NICKCHANGE) && !ValidateHook(hooktype_post_local_nickchange, func)) || \
         ((hooktype == HOOKTYPE_POST_REMOTE_NICKCHANGE) && !ValidateHook(hooktype_post_remote_nickchange, func)) || \
-        ((hooktype == HOOKTYPE_USERHOST_CHANGED) && !ValidateHook(hooktype_userhost_changed, func)) || \
-        ((hooktype == HOOKTYPE_REALNAME_CHANGED) && !ValidateHook(hooktype_realname_changed, func)) )\
+        ((hooktype == HOOKTYPE_USERHOST_CHANGE) && !ValidateHook(hooktype_userhost_change, func)) || \
+        ((hooktype == HOOKTYPE_REALNAME_CHANGE) && !ValidateHook(hooktype_realname_change, func)) || \
+        ((hooktype == HOOKTYPE_IP_CHANGE) && !ValidateHook(hooktype_ip_change, func)) ) \
         _hook_error_incompatible();
 #endif /* GCC_TYPECHECKING */
 
diff --git a/include/numeric.h b/include/numeric.h
@@ -37,6 +37,8 @@
 
 #define RPL_REDIR	     10
 
+#define RPL_MAPUSERS	     18
+
 #define RPL_REMOTEISUPPORT 105
 
 /*
@@ -349,10 +351,11 @@
 #define STR_RPL_CREATED			/* 003 */	":This server was created %s"
 #define STR_RPL_MYINFO			/* 004 */	"%s %s %s %s"
 #define STR_RPL_ISUPPORT		/* 005 */	"%s :are supported by this server"
-#define STR_RPL_MAP			/* 006 */	":%s%-*s(%ld) %s"
+#define STR_RPL_MAP			/* 006 */	":%s%s %s | Users: %*ld (%*.2f%%)%s"
 #define STR_RPL_MAPEND			/* 007 */	":End of /MAP"
 #define STR_RPL_SNOMASK			/* 008 */	"+%s :Server notice mask"
 #define STR_RPL_REDIR			/* 010 */	"%s %d :Please use this Server/Port instead"
+#define STR_RPL_MAPUSERS		/* 018 */	":%d server%s and %d user%s, average %.2f users per server"
 #define STR_RPL_REMOTEISUPPORT		/* 105 */	"%s :are supported by this server"
 #define STR_RPL_TRACELINK		/* 200 */	"Link %s%s %s %s"
 #define STR_RPL_TRACECONNECTING		/* 201 */	"Attempt %s %s"
diff --git a/include/setup.h.in b/include/setup.h.in
@@ -12,6 +12,9 @@
 /* Define the location of the configuration files */
 #undef CONFDIR
 
+/* Define the path of the control socket */
+#undef CONTROLFILE
+
 /* Define the location of permanent data files */
 #undef DATADIR
 
diff --git a/include/struct.h b/include/struct.h
@@ -263,12 +263,18 @@ typedef struct Log Log;
 struct Log {
 	Log *prev, *next;
 	LogSource *sources;
+	int type;
 	char destination[CHANNELLEN+1];
+	int show_event;
+	/* for destination::file */
 	char *file;
 	char *filefmt;
 	long maxsize;
-	int type;
 	int logfd;
+	/* for destination::channel */
+	int color;
+	int json_message_tag;
+	int oper_only;
 };
 
 /** This is used for deciding the <index> in logs[<index>] and temp_logs[<index>] */
@@ -317,6 +323,7 @@ typedef enum LogDestination { LOG_DEST_SNOMASK=0, LOG_DEST_OPER=1, LOG_DEST_REMO
  * @{
  */
 typedef enum ClientStatus {
+	CLIENT_STATUS_CONTROL			= -8,	/**< Client is on the control channel */
 	CLIENT_STATUS_LOG			= -7,	/**< Client is a log file */
 	CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE	= -8,	/**< Client is doing a STARTTLS handshake */
 	CLIENT_STATUS_CONNECTING		= -6,	/**< Client is an outgoing connect */
@@ -339,6 +346,7 @@ typedef enum ClientStatus {
 /** Client is not fully registered yet. May become a user or a server, we don't know yet. */
 #define	IsUnknown(x)		(((x)->status == CLIENT_STATUS_UNKNOWN) || ((x)->status == CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE))	
 #define	IsServer(x)		((x)->status == CLIENT_STATUS_SERVER)	/**< Is a server that has completed the connection handshake */
+#define	IsControl(x)		((x)->status == CLIENT_STATUS_CONTROL)	/**< Is on the control channel (not on IRC) */
 #define	IsLog(x)		((x)->status == CLIENT_STATUS_LOG)	/**< Is a log file, not a user or server */
 #define IsStartTLSHandshake(x)	((x)->status == CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE)	/**< Currently doing a STARTTLS handshake */
 #define IsTLSAcceptHandshake(x)	((x)->status == CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE)	/**< Currently doing a TLS handshake - incoming */
@@ -355,6 +363,8 @@ typedef enum ClientStatus {
 #define	SetServer(x)		((x)->status = CLIENT_STATUS_SERVER)
 #define	SetUser(x)		((x)->status = CLIENT_STATUS_USER)
 #define	SetLog(x)		((x)->status = CLIENT_STATUS_LOG)
+#define	SetControl(x)		((x)->status = CLIENT_STATUS_CONTROL)
+#define	SetUser(x)		((x)->status = CLIENT_STATUS_USER)
 
 /** @} */
 
@@ -366,7 +376,7 @@ typedef enum ClientStatus {
 #define	CLIENT_FLAG_DEAD		0x00000002	/**< Client is dead: already quit/exited and removed from all lists -- Remaining part will soon be freed in main loop */
 #define	CLIENT_FLAG_DEADSOCKET		0x00000004	/**< Local socket is dead but otherwise the client still exists fully -- Will soon exit in main loop */
 #define	CLIENT_FLAG_KILLED		0x00000008	/**< Prevents "QUIT" from being sent for this */
-#define CLIENT_FLAG_IPV6		0x00000010	/**< Connection is using IPv6 */
+#define CLIENT_FLAG_MONITOR_REHASH	0x00000010	/**< Client is monitoring rehash output */
 #define CLIENT_FLAG_OUTGOING		0x00000020	/**< Outgoing connection (do not touch cptr->listener->clients) */
 #define	CLIENT_FLAG_CLOSING		0x00000040	/**< Set when closing to suppress errors */
 #define	CLIENT_FLAG_LISTEN		0x00000080	/**< Used to mark clients which we listen() on */
@@ -464,8 +474,8 @@ typedef enum ClientStatus {
 #define IsDNSLookup(x)			((x)->flags & CLIENT_FLAG_DNSLOOKUP)
 #define IsEAuth(x)			((x)->flags & CLIENT_FLAG_EAUTH)
 #define IsIdentSuccess(x)		((x)->flags & CLIENT_FLAG_IDENTSUCCESS)
-#define IsIPV6(x)			((x)->flags & CLIENT_FLAG_IPV6)
 #define IsKilled(x)			((x)->flags & CLIENT_FLAG_KILLED)
+#define IsMonitorRehash(x)		((x)->flags & CLIENT_FLAG_MONITOR_REHASH)
 #define IsListening(x)			((x)->flags & CLIENT_FLAG_LISTEN)
 #define IsLocalhost(x)			((x)->flags & CLIENT_FLAG_LOCALHOST)
 #define IsMap(x)			((x)->flags & CLIENT_FLAG_MAP)
@@ -496,8 +506,8 @@ typedef enum ClientStatus {
 #define SetDNSLookup(x)			do { (x)->flags |= CLIENT_FLAG_DNSLOOKUP; } while(0)
 #define SetEAuth(x)			do { (x)->flags |= CLIENT_FLAG_EAUTH; } while(0)
 #define SetIdentSuccess(x)		do { (x)->flags |= CLIENT_FLAG_IDENTSUCCESS; } while(0)
-#define SetIPV6(x)			do { (x)->flags |= CLIENT_FLAG_IPV6; } while(0)
 #define SetKilled(x)			do { (x)->flags |= CLIENT_FLAG_KILLED; } while(0)
+#define SetMonitorRehash(x)		do { (x)->flags |= CLIENT_FLAG_MONITOR_REHASH; } while(0)
 #define SetListening(x)			do { (x)->flags |= CLIENT_FLAG_LISTEN; } while(0)
 #define SetLocalhost(x)			do { (x)->flags |= CLIENT_FLAG_LOCALHOST; } while(0)
 #define SetMap(x)			do { (x)->flags |= CLIENT_FLAG_MAP; } while(0)
@@ -526,8 +536,8 @@ typedef enum ClientStatus {
 #define ClearDNSLookup(x)		do { (x)->flags &= ~CLIENT_FLAG_DNSLOOKUP; } while(0)
 #define ClearEAuth(x)			do { (x)->flags &= ~CLIENT_FLAG_EAUTH; } while(0)
 #define ClearIdentSuccess(x)		do { (x)->flags &= ~CLIENT_FLAG_IDENTSUCCESS; } while(0)
-#define ClearIPV6(x)			do { (x)->flags &= ~CLIENT_FLAG_IPV6; } while(0)
 #define ClearKilled(x)			do { (x)->flags &= ~CLIENT_FLAG_KILLED; } while(0)
+#define ClearMonitorRehash(x)		do { (x)->flags &= ~CLIENT_FLAG_MONITOR_REHASH; } while(0)
 #define ClearListening(x)		do { (x)->flags &= ~CLIENT_FLAG_LISTEN; } while(0)
 #define ClearLocalhost(x)		do { (x)->flags &= ~CLIENT_FLAG_LOCALHOST; } while(0)
 #define ClearMap(x)			do { (x)->flags &= ~CLIENT_FLAG_MAP; } while(0)
@@ -546,6 +556,9 @@ 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)
+#define IsIPV6(x)			((x)->local->socket_type == SOCKET_TYPE_IPV6)
+#define IsUnixSocket(x)			((x)->local->socket_type == SOCKET_TYPE_UNIX)
+#define SetIPV6(x)			do { (x)->local->socket_type = SOCKET_TYPE_IPV6; } while(0)
 /** @} */
 
 
@@ -779,11 +792,11 @@ struct LoopStruct {
 	unsigned do_bancheck : 1; /* perform *line bancheck? */
 	unsigned do_bancheck_spamf_user : 1; /* perform 'user' spamfilter bancheck */
 	unsigned do_bancheck_spamf_away : 1; /* perform 'away' spamfilter bancheck */
-	unsigned rehashing : 1;
 	unsigned terminating : 1;
 	unsigned config_load_failed : 1;
 	unsigned rehash_download_busy : 1; /* don't return "all downloads complete", needed for race condition */
 	unsigned tainted : 1;
+	int rehashing;
 	Client *rehash_save_client;
 	void (*boot_function)();
 };
@@ -851,6 +864,8 @@ struct SWhois {
 #define CMD_VIRUS		0x0080
 /** Command requires IRCOp privileges */
 #define CMD_OPER		0x0200
+/** Command is for control channel only (unrealircd.ctl socket) */
+#define CMD_CONTROL		0x0400
 
 /** Command function - used by all command handlers.
  * This is used in the code like <pre>CMD_FUNC(cmd_yourcmd)</pre> as a function definition.
@@ -1212,6 +1227,7 @@ extern void unload_all_unused_moddata(void);
 #define LISTENER_TLS		0x000010
 #define LISTENER_BOUND		0x000020
 #define LISTENER_DEFER_ACCEPT	0x000040
+#define LISTENER_CONTROL	0x000080	/**< Control channel */
 
 #define IsServersOnlyListener(x)	((x) && ((x)->options & LISTENER_SERVERSONLY))
 
@@ -1240,6 +1256,7 @@ typedef enum FloodOption {
 	FLD_KNOCK		= 4,	/**< knock-flood */
 	FLD_CONVERSATIONS	= 5,	/**< max-concurrent-conversations */
 	FLD_LAG_PENALTY		= 6,	/**< lag-penalty / lag-penalty-bytes */
+	FLD_VHOST		= 7,	/**< vhost-flood */
 } FloodOption;
 #define MAXFLOODOPTIONS 10
 
@@ -1251,6 +1268,11 @@ struct TrafficStats {
 	long long bytes_received;	/* Received bytes */
 };
 
+/** Socket type (IPv4, IPv6, UNIX) */
+typedef enum {
+	SOCKET_TYPE_IPV4=0, SOCKET_TYPE_IPV6=1, SOCKET_TYPE_UNIX=2
+} SocketType;
+
 /** This shows the Client struct (any client), the User struct (a user), Server (a server) that are commonly accessed both in the core and by 3rd party coders.
  * @defgroup CommonStructs Common structs
  * @{
@@ -1288,6 +1310,7 @@ struct Client {
  */
 struct LocalClient {
 	int fd;				/**< File descriptor, can be <0 if socket has been closed already. */
+	SocketType socket_type;		/**< Type of socket: IPv4, IPV6, UNIX */
 	SSL *ssl;			/**< OpenSSL/LibreSSL struct for TLS connection */
 	time_t fake_lag;		/**< Time when user will next be allowed to send something (actually fake_lag<currenttime+10) */
 	int fake_lag_msec;		/**< Used for calculating 'fake_lag' penalty (modulo) */
@@ -1606,6 +1629,7 @@ struct ConfigItem_oper {
 	char *vhost;
 	int maxlogins;
 	int server_notice_colors;
+	int server_notice_show_event;
 };
 
 /** The TLS options that are used in set::tls and otherblocks::tls-options.
@@ -1666,11 +1690,12 @@ struct ConfigItem_tld {
 struct ConfigItem_listen {
 	ConfigItem_listen *prev, *next;
 	ConfigFlag flag;
+	SocketType socket_type;
+	char *file;
 	char *ip;
 	int port;
 	int options, clients;
 	int fd;
-	int ipv6;
 	SSL_CTX *ssl_ctx;
 	TLSOptions *tls_options;
 	int websocket_options; /* should be in module, but lazy */
diff --git a/include/sys.h b/include/sys.h
@@ -58,10 +58,12 @@
 #ifndef _WIN32
 #include <netinet/in.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <arpa/inet.h>
 #else
 #include <winsock2.h>
 #include <ws2tcpip.h>
+#include <afunix.h>
 #endif
 
 #ifndef _WIN32
diff --git a/include/version.h b/include/version.h
@@ -54,7 +54,7 @@
  * Can be useful if the above 3 versionids are insufficient for you (eg: you want to support CVS).
  * This is updated automatically on the CVS server every Monday. so don't touch it.
  */
-#define UNREAL_VERSION_TIME	202148
+#define UNREAL_VERSION_TIME	202204
 
 #define UNREAL_VERSION		((UNREAL_VERSION_GENERATION << 24) + (UNREAL_VERSION_MAJOR << 16) + (UNREAL_VERSION_MINOR << 8))
 #define UnrealProtocol 		6000
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -33,6 +33,7 @@
 #define CACHEDIR "cache"
 #define TMPDIR "tmp"
 #define PIDFILE PERMDATADIR"/unrealircd.pid"
+#define CONTROLFILE PERMDATADIR"/unrealircd.ctl"
 #define NO_U_TYPES
 #define NEED_U_INT32_T
 #define strcasecmp _stricmp
@@ -61,10 +62,10 @@
 #define UNREAL_VERSION_MAJOR 0
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 1
+#define UNREAL_VERSION_MINOR 3
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
-#define UNREAL_VERSION_SUFFIX ".1"
+#define UNREAL_VERSION_SUFFIX ""
 
 #endif
diff --git a/src/Makefile.in b/src/Makefile.in
@@ -21,10 +21,10 @@
 
 CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
 
-OBJS=dns.o auth.o channel.o crule.o dbuf.o \
-	fdlist.o hash.o ircd.o ircsprintf.o list.o \
+OBJS=ircd_vars.o dns.o auth.o channel.o crule.o dbuf.o \
+	fdlist.o hash.o ircsprintf.o list.o \
 	match.o modules.o parse.o mempool.o operclass.o \
-	conf_preprocessor.o conf.o debug.o dispatch.o \
+	conf_preprocessor.o conf.o proc_io_server.o debug.o dispatch.o \
 	misc.o serv.o aliases.o socket.o \
 	tls.o user.o scache.o send.o support.o \
 	version.o whowas.o random.o api-usermode.o api-channelmode.o \
@@ -62,22 +62,22 @@ all: build
 
 build:
 	# Force build of 'ircd', before we start building any modules:
-	$(MAKE) ircd
+	$(MAKE) ircd unrealircdctl
 	$(MAKE) mods
 
 custommodule:
 	+cd modules/third; $(MAKE) MODULEFILE=$(MODULEFILE) 'EXLIBS=$(EXLIBS)' custommodule
 
-ircd: $(OBJS)
-	$(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o ircd $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB)
+ircd: $(OBJS) ircd.o
+	$(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o ircd ircd.o $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB)
+
+unrealircdctl: $(OBJS) unrealircdctl.o proc_io_client.o
+	$(CC) $(CFLAGS) $(BINCFLAGS) $(CRYPTOLIB) -o unrealircdctl unrealircdctl.o proc_io_client.o $(OBJS) $(LDFLAGS) $(BINLDFLAGS) $(IRCDLIBS) $(CRYPTOLIB)
 
 mods:
 	@if [ ! -r include ] ; then \
 		ln -s ../include include; \
 	fi
-	@if [ ! -r modules ] ; then \
-		echo "You havent done cvs update -P -d"; \
-	fi
 	+cd modules; $(MAKE) all
 
 version.c: version.c.SH
diff --git a/src/api-command.c b/src/api-command.c
@@ -89,7 +89,7 @@ static Command *CommandAddInternal(Module *module, const char *cmd, CmdFunc func
 	Command *command = NULL;
 	RealCommand *c;
 
-	if (find_command_simple(cmd))
+	if ((c = find_command(cmd, flags)) && (c->flags == flags))
 	{
 		if (module)
 			module->errorcode = MODERR_EXISTS;
@@ -253,15 +253,26 @@ static RealCommand *add_Command_backend(const char *cmd)
 RealCommand *find_command(const char *cmd, int flags)
 {
 	RealCommand *p;
-	for (p = CommandHash[toupper(*cmd)]; p; p = p->next) {
-		if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED))
-			continue;
-		if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN))
-			continue;
-		if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS))
-			continue;
-		if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS))
-			continue;
+	for (p = CommandHash[toupper(*cmd)]; p; p = p->next)
+	{
+		if (flags & CMD_CONTROL)
+		{
+			if (!(p->flags & CMD_CONTROL))
+				continue;
+		} else
+		{
+			if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED))
+				continue;
+			if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN))
+				continue;
+			if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS))
+				continue;
+			if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS))
+				continue;
+			if (p->flags & CMD_CONTROL)
+				continue; /* important to also filter it this way ;) */
+		}
+
 		if (!strcasecmp(p->cmd, cmd))
 			return p;
 	}
diff --git a/src/api-event.c b/src/api-event.c
@@ -27,9 +27,6 @@ ID_Copyright("(C) Carsten Munk 2001");
 
 MODVAR Event *events = NULL;
 
-extern EVENT(unrealdns_removeoldrecords);
-extern EVENT(unrealdb_expire_secret_cache);
-
 /** Add an event, a function that will run at regular intervals.
  * @param module	Module that this event belongs to
  * @param name		Name of the event
@@ -211,18 +208,3 @@ void DoEvents(void)
 
 	CleanupEvents();
 }
-
-void SetupEvents(void)
-{
-	/* Start events */
-	EventAdd(NULL, "tunefile", save_tunefile, NULL, 300*1000, 0);
-	EventAdd(NULL, "garbage", garbage_collect, NULL, GARBAGE_COLLECT_EVERY*1000, 0);
-	EventAdd(NULL, "loop", loop_event, NULL, 1000, 0);
-	EventAdd(NULL, "unrealdns_removeoldrecords", unrealdns_removeoldrecords, NULL, 15000, 0);
-	EventAdd(NULL, "check_pings", check_pings, NULL, 1000, 0);
-	EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0);
-	EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0);
-	EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0);
-	EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0);
-	EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 1000, 0);
-}
diff --git a/src/conf.c b/src/conf.c
@@ -199,8 +199,8 @@ void free_tls_options(TLSOptions *tlsoptions);
  * Config parser (IRCd)
 */
 int			config_read_file(const char *filename, const char *display_name);
-void			config_rehash();
-int			config_run_blocks();
+void			config_rehash(void);
+int			config_run_blocks(void);
 int	config_test_blocks();
 
 /*
@@ -464,10 +464,15 @@ int config_parse_flood_generic(const char *str, Configuration *conf, char *block
 
 long config_checkval(const char *orig, unsigned short flags)
 {
-	char *value = raw_strdup(orig);
+	char *value;
 	char *text;
 	long ret = 0;
 
+	/* Handle empty strings early, since we use +1 later in the code etc. */
+	if (BadPtr(orig))
+		return 0;
+
+	value = raw_strdup(orig);
 	if (flags == CFG_YESNO) {
 		for (text = value; *text; text++) {
 			if (!isalnum(*text))
@@ -1586,7 +1591,7 @@ ConfigCommand *config_binary_search(const char *cmd) {
 	return NULL;
 }
 
-void	free_iConf(Configuration *i)
+void free_iConf(Configuration *i)
 {
 	FloodSettings *f, *f_next;
 
@@ -1645,6 +1650,7 @@ void config_setdefaultsettings(Configuration *i)
 
 	safe_strdup(i->oper_snomask, OPER_SNOMASKS);
 	i->server_notice_colors = 1;
+	i->server_notice_show_event = 1;
 	i->ident_read_timeout = 7;
 	i->ident_connect_timeout = 3;
 	i->ban_version_tkl_time = 86400; /* 1d */
@@ -1694,6 +1700,7 @@ void config_setdefaultsettings(Configuration *i)
 	/* - known-users */
 	config_parse_flood_generic("3:60", i, "known-users", FLD_NICK); /* NICK flood protection: max 3 per 60s */
 	config_parse_flood_generic("3:90", i, "known-users", FLD_JOIN); /* JOIN flood protection: max 3 per 90s */
+	config_parse_flood_generic("3:90", i, "known-users", FLD_VHOST); /* MODE -x flood protection: max 3 per 90s */
 	config_parse_flood_generic("4:120", i, "known-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
 	config_parse_flood_generic("4:60", i, "known-users", FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
 	config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
@@ -1702,6 +1709,7 @@ void config_setdefaultsettings(Configuration *i)
 	/* - unknown-users */
 	config_parse_flood_generic("2:60", i, "unknown-users", FLD_NICK); /* NICK flood protection: max 2 per 60s */
 	config_parse_flood_generic("2:90", i, "unknown-users", FLD_JOIN); /* JOIN flood protection: max 2 per 90s */
+	config_parse_flood_generic("2:90", i, "unknown-users", FLD_VHOST); /* MODE -x flood protection: max 2 per 90s */
 	config_parse_flood_generic("4:120", i, "unknown-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
 	config_parse_flood_generic("2:60", i, "unknown-users", FLD_INVITE); /* INVITE flood protection: max 2 per 60s */
 	config_parse_flood_generic("2:120", i, "unknown-users", FLD_KNOCK); /* KNOCK protection: max 2 per 120s */
@@ -2411,7 +2419,8 @@ void config_rehash()
 	}
 	for (listen_ptr = conf_listen; listen_ptr; listen_ptr = listen_ptr->next)
 	{
-		listen_ptr->flag.temporary = 1;
+		if (!(listen_ptr->options & LISTENER_CONTROL))
+			listen_ptr->flag.temporary = 1;
 	}
 	for (tld_ptr = conf_tld; tld_ptr; tld_ptr = (ConfigItem_tld *) next)
 	{
@@ -2642,152 +2651,87 @@ void config_switchover(void)
 	log_blocks_switchover();
 }
 
-int	config_run_blocks()
+/** Priority of config blocks during CONFIG_TEST stage */
+static const char *config_test_priority_blocks[] =
+{
+	"me",
+	"secret",
+	"log", /* "log" needs to be before "set" in CONFIG_TEST */
+	"set",
+	"class",
+};
+
+/** Priority of config blocks during CONFIG_RUN stage */
+static const char *config_run_priority_blocks[] =
+{
+	"me",
+	"secret",
+	"set",
+	"log", /* "log" needs to be after "set" in CONFIG_RUN */
+	"class",
+};
+
+int config_test_blocks()
 {
 	ConfigEntry 	*ce;
 	ConfigFile	*cfptr;
 	ConfigCommand	*cc;
 	int		errors = 0;
+	int i;
 	Hook *h;
-	ConfigItem_allow *allow;
 
-	/* Stage 1: set block first */
-	for (cfptr = conf; cfptr; cfptr = cfptr->next)
-	{
-		if (config_verbose > 1)
-			config_status("Running %s", cfptr->filename);
-		for (ce = cfptr->items; ce; ce = ce->next)
-		{
-			if (!strcmp(ce->name, "set"))
-			{
-				if (_conf_set(cfptr, ce) < 0)
-					errors++;
-			}
-		}
-	}
+	invalid_snomasks_encountered = 0;
 
-	/* Stage 2: now class blocks */
-	for (cfptr = conf; cfptr; cfptr = cfptr->next)
+	/* Stage 1: first the priority blocks, in the order as specified
+	 *          in config_test_priority_blocks[]
+	 */
+	for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
 	{
-		if (config_verbose > 1)
-			config_status("Running %s", cfptr->filename);
-		for (ce = cfptr->items; ce; ce = ce->next)
+		const char *config_block = config_test_priority_blocks[i];
+		cc = config_binary_search(config_block);
+		if (!cc)
+			abort(); /* internal fuckup */
+		for (cfptr = conf; cfptr; cfptr = cfptr->next)
 		{
-			if (!strcmp(ce->name, "class"))
+			if (config_verbose > 1)
+				config_status("Running %s", cfptr->filename);
+			for (ce = cfptr->items; ce; ce = ce->next)
 			{
-				if (_conf_class(cfptr, ce) < 0)
-					errors++;
+				if (!strcmp(ce->name, config_block))
+				{
+					int n = cc->testfunc(cfptr, ce);
+					errors += n;
+					if (!strcmp(config_block, "secret") && (n == 0))
+					{
+						/* Yeah special case: secret { } blocks we run
+						 * immediately here.
+						 */
+						_conf_secret(cfptr, ce);
+					}
+				}
 			}
 		}
 	}
 
-	/* Stage 3: now all the rest */
+	/* Stage 2: now all the other config blocks */
 	for (cfptr = conf; cfptr; cfptr = cfptr->next)
 	{
 		if (config_verbose > 1)
 			config_status("Running %s", cfptr->filename);
 		for (ce = cfptr->items; ce; ce = ce->next)
 		{
-			/* These are already processed above (set, class)
-			 * or via config_test_blocks() (secret).
-			 */
-			if (!strcmp(ce->name, "set") ||
-			    !strcmp(ce->name, "class") ||
-			    !strcmp(ce->name, "secret"))
-			{
-				continue;
-			}
-
-			if ((cc = config_binary_search(ce->name))) {
-				if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0))
-					errors++;
-			}
-			else
+			char skip = 0;
+			for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
 			{
-				int value;
-				for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+				if (!strcmp(ce->name, config_test_priority_blocks[i]))
 				{
-					value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN);
-					if (value == 1)
-						break;
+					skip = 1;
+					break;
 				}
 			}
-		}
-	}
-
-	close_unbound_listeners();
-	listen_cleanup();
-	close_unbound_listeners();
-	loop.do_bancheck = 1;
-	config_switchover();
-	update_throttling_timer_settings();
-
-	/* initialize conf_files with defaults if the block isn't set: */
-	if (!conf_files)
-	  _conf_files(NULL, NULL);
-
-	if (errors > 0)
-	{
-		config_error("%i fatal errors encountered", errors);
-	}
-	return (errors > 0 ? -1 : 1);
-}
-
-
-int	config_test_blocks()
-{
-	ConfigEntry 	*ce;
-	ConfigFile	*cfptr;
-	ConfigCommand	*cc;
-	int		errors = 0;
-	Hook *h;
-
-	invalid_snomasks_encountered = 0;
-
-	/* First, all the log { } blocks everywhere */
-	for (cfptr = conf; cfptr; cfptr = cfptr->next)
-	{
-		if (config_verbose > 1)
-			config_status("Testing %s", cfptr->filename);
-		/* First test and run the log { } blocks */
-		for (ce = cfptr->items; ce; ce = ce->next)
-		{
-			if (!strcmp(ce->name, "log"))
-				errors += config_test_log(cfptr, ce);
-		}
-	}
-
-	for (cfptr = conf; cfptr; cfptr = cfptr->next)
-	{
-		if (config_verbose > 1)
-			config_status("Testing %s", cfptr->filename);
-		/* First test and run the secret { } blocks */
-		for (ce = cfptr->items; ce; ce = ce->next)
-		{
-			if (!strcmp(ce->name, "secret"))
-			{
-				int n = _test_secret(cfptr, ce);
-				errors += n;
-				if (n == 0)
-					_conf_secret(cfptr, ce);
-			}
-		}
-		/* First test the set { } block */
-		for (ce = cfptr->items; ce; ce = ce->next)
-		{
-			if (!strcmp(ce->name, "set"))
-				errors += _test_set(cfptr, ce);
-		}
-		/* Now test all the rest */
-		for (ce = cfptr->items; ce; ce = ce->next)
-		{
-			/* These are already processed, so skip them here.. */
-			if (!strcmp(ce->name, "secret") ||
-			    !strcmp(ce->name, "set") ||
-			    !strcmp(ce->name, "log"))
-			{
+			if (skip)
 				continue;
-			}
+
 			if ((cc = config_binary_search(ce->name))) {
 				if (cc->testfunc)
 					errors += (cc->testfunc(cfptr, ce));
@@ -2841,7 +2785,9 @@ int	config_test_blocks()
 			}
 		}
 	}
+
 	errors += config_post_test();
+
 	if (errors > 0)
 	{
 		config_error("%i errors encountered", errors);
@@ -2856,6 +2802,96 @@ int	config_test_blocks()
 	return (errors > 0 ? -1 : 1);
 }
 
+int config_run_blocks(void)
+{
+	ConfigEntry 	*ce;
+	ConfigFile	*cfptr;
+	ConfigCommand	*cc;
+	int		errors = 0;
+	int i;
+	Hook *h;
+	ConfigItem_allow *allow;
+
+	/* Stage 1: first the priority blocks, in the order as specified
+	 *          in config_run_priority_blocks[]
+	 */
+	for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
+	{
+		const char *config_block = config_run_priority_blocks[i];
+		cc = config_binary_search(config_block);
+		if (!cc)
+			abort(); /* internal fuckup */
+		if (!strcmp(config_block, "secret"))
+			continue; /* yeah special case, we already processed the run part in test for these */
+		for (cfptr = conf; cfptr; cfptr = cfptr->next)
+		{
+			if (config_verbose > 1)
+				config_status("Running %s", cfptr->filename);
+			for (ce = cfptr->items; ce; ce = ce->next)
+			{
+				if (!strcmp(ce->name, config_block))
+				{
+					if (cc->conffunc(cfptr, ce) < 0)
+						errors++;
+				}
+			}
+		}
+	}
+
+	/* Stage 2: now all the other config blocks */
+	for (cfptr = conf; cfptr; cfptr = cfptr->next)
+	{
+		if (config_verbose > 1)
+			config_status("Running %s", cfptr->filename);
+		for (ce = cfptr->items; ce; ce = ce->next)
+		{
+			char skip = 0;
+			for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
+			{
+				if (!strcmp(ce->name, config_run_priority_blocks[i]))
+				{
+					skip = 1;
+					break;
+				}
+			}
+			if (skip)
+				continue;
+
+			if ((cc = config_binary_search(ce->name))) {
+				if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0))
+					errors++;
+			}
+			else
+			{
+				int value;
+				for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+				{
+					value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN);
+					if (value == 1)
+						break;
+				}
+			}
+		}
+	}
+
+	close_unbound_listeners();
+	listen_cleanup();
+	close_unbound_listeners();
+	loop.do_bancheck = 1;
+	config_switchover();
+	update_throttling_timer_settings();
+
+	/* initialize conf_files with defaults if the block isn't set: */
+	if (!conf_files)
+	  _conf_files(NULL, NULL);
+
+	if (errors > 0)
+	{
+		config_error("%i fatal errors encountered", errors);
+	}
+	return (errors > 0 ? -1 : 1);
+}
+
 /*
  * Service functions
 */
@@ -2935,7 +2971,7 @@ int count_oper_sessions(const char *name)
 	return count;
 }
 
-ConfigItem_listen *find_listen(const char *ipmask, int port, int ipv6)
+ConfigItem_listen *find_listen(const char *ipmask, int port, SocketType socket_type)
 {
 	ConfigItem_listen *e;
 
@@ -2943,8 +2979,19 @@ ConfigItem_listen *find_listen(const char *ipmask, int port, int ipv6)
 		return NULL;
 
 	for (e = conf_listen; e; e = e->next)
-		if ((e->ipv6 == ipv6) && (e->port == port) && !strcmp(e->ip, ipmask))
-			return e;
+	{
+		if (e->socket_type != socket_type)
+			continue;
+		if (e->socket_type == SOCKET_TYPE_UNIX)
+		{
+			if (!strcmp(e->file, ipmask))
+				return e;
+		} else
+		{
+			if ((e->socket_type == socket_type) && (e->port == port) && !strcmp(e->ip, ipmask))
+				return e;
+		}
+	}
 
 	return NULL;
 }
@@ -3889,7 +3936,9 @@ int	_conf_oper(ConfigFile *conf, ConfigEntry *ce)
 	oper =  safe_alloc(sizeof(ConfigItem_oper));
 	safe_strdup(oper->name, ce->value);
 
-	oper->server_notice_colors = tempiConf.server_notice_colors; /* default */
+	/* Inherit some defaults: */
+	oper->server_notice_colors = tempiConf.server_notice_colors;
+	oper->server_notice_show_event = tempiConf.server_notice_show_event;
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
@@ -3937,6 +3986,10 @@ int	_conf_oper(ConfigFile *conf, ConfigEntry *ce)
 		{
 			oper->server_notice_colors = config_checkval(cep->value, CFG_YESNO);
 		}
+		else if (!strcmp(cep->name, "server-notice-show-event"))
+		{
+			oper->server_notice_show_event = config_checkval(cep->value, CFG_YESNO);
+		}
 		else if (!strcmp(cep->name, "modes"))
 		{
 			oper->modes = set_usermode(cep->value);
@@ -4069,6 +4122,9 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 			else if (!strcmp(cep->name, "server-notice-colors"))
 			{
 			}
+			else if (!strcmp(cep->name, "server-notice-show-event"))
+			{
+			}
 			/* oper::modes */
 			else if (!strcmp(cep->name, "modes"))
 			{
@@ -4777,6 +4833,7 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cepp;
 	ConfigEntry *tlsconfig = NULL;
 	ConfigItem_listen *listen = NULL;
+	char *file = NULL;
 	char *ip = NULL;
 	int start=0, end=0, port, isnew;
 	int tmpflags =0;
@@ -4784,6 +4841,10 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
+		if (!strcmp(cep->name, "file"))
+		{
+			file = cep->value;
+		} else
 		if (!strcmp(cep->name, "ip"))
 		{
 			ip = cep->value;
@@ -4825,18 +4886,44 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 			}
 		}
 	}
+
+	/* UNIX domain socket code */
+	if (file)
+	{
+		if (!(listen = find_listen(file, 0, SOCKET_TYPE_UNIX)))
+		{
+			listen = safe_alloc(sizeof(ConfigItem_listen));
+			safe_strdup(listen->file, file);
+			listen->socket_type = SOCKET_TYPE_UNIX;
+			listen->fd = -1;
+			isnew = 1;
+		} else {
+			isnew = 0;
+		}
+
+		if (listen->options & LISTENER_BOUND)
+			tmpflags |= LISTENER_BOUND;
+
+		listen->options = tmpflags;
+		if (isnew)
+			AddListItem(listen, conf_listen);
+		listen->flag.temporary = 0;
+
+		return 1;
+	}
+
 	for (port = start; port <= end; port++)
 	{
 		/* First deal with IPv4 */
 		if (!strchr(ip, ':'))
 		{
-			if (!(listen = find_listen(ip, port, 0)))
+			if (!(listen = find_listen(ip, port, SOCKET_TYPE_IPV4)))
 			{
 				listen = safe_alloc(sizeof(ConfigItem_listen));
 				safe_strdup(listen->ip, ip);
 				listen->port = port;
 				listen->fd = -1;
-				listen->ipv6 = 0;
+				listen->socket_type = SOCKET_TYPE_IPV4;
 				isnew = 1;
 			} else
 				isnew = 0;
@@ -4915,13 +5002,13 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 		{
 			if (strchr(ip, ':') || (*ip == '*'))
 			{
-				if (!(listen = find_listen(ip, port, 1)))
+				if (!(listen = find_listen(ip, port, SOCKET_TYPE_IPV6)))
 				{
 					listen = safe_alloc(sizeof(ConfigItem_listen));
 					safe_strdup(listen->ip, ip);
 					listen->port = port;
 					listen->fd = -1;
-					listen->ipv6 = 1;
+					listen->socket_type = SOCKET_TYPE_IPV6;
 					isnew = 1;
 				} else
 					isnew = 0;
@@ -5002,7 +5089,8 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	ConfigEntry *cepp;
 	int errors = 0;
-	char has_ip = 0, has_port = 0, has_options = 0, port_6667 = 0;
+	char has_file = 0, has_ip = 0, has_port = 0, has_options = 0, port_6667 = 0;
+	char *file = NULL;
 	char *ip = NULL;
 	Hook *h;
 
@@ -5117,6 +5205,11 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 			}
 			continue; /* always */
 		} else
+		if (!strcmp(cep->name, "file"))
+		{
+			has_file = 1;
+			file = cep->value;
+		} else
 		if (!strcmp(cep->name, "ip"))
 		{
 			has_ip = 1;
@@ -5189,18 +5282,32 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 		}
 	}
 
-	if (!has_ip)
+	if (has_file)
 	{
-		config_error("%s:%d: listen block requires an listen::ip",
-			ce->file->filename, ce->line_number);
-		errors++;
-	}
-
-	if (!has_port)
+		if (has_ip || has_port)
+		{
+			config_error("%s:%d: listen block should either have a 'file' (for *NIX domain socket), "
+			             "OR have an 'ip' and 'port' (for IPv4/IPv6). You cannot combine both in one listen block.",
+			             ce->file->filename, ce->line_number);
+			errors++;
+		} else {
+			// TODO: check if file can be created fresh etc.
+		}
+	} else
 	{
-		config_error("%s:%d: listen block requires an listen::port",
-			ce->file->filename, ce->line_number);
-		errors++;
+		if (!has_ip)
+		{
+			config_error("%s:%d: listen block requires an listen::ip",
+				ce->file->filename, ce->line_number);
+			errors++;
+		}
+
+		if (!has_port)
+		{
+			config_error("%s:%d: listen block requires an listen::port",
+				ce->file->filename, ce->line_number);
+			errors++;
+		}
 	}
 
 	if (port_6667)
@@ -7167,6 +7274,9 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 		else if (!strcmp(cep->name, "server-notice-colors")) {
 			tempiConf.server_notice_colors = config_checkval(cep->value, CFG_YESNO);
 		}
+		else if (!strcmp(cep->name, "server-notice-show-event")) {
+			tempiConf.server_notice_show_event = config_checkval(cep->value, CFG_YESNO);
+		}
 		else if (!strcmp(cep->name, "level-on-join")) {
 			const char *res = channellevel_to_string(cep->value); /* 'halfop', etc */
 			if (!res)
@@ -7380,6 +7490,10 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 					{
 						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_NICK);
 					}
+					else if (!strcmp(ceppp->name, "vhost-flood"))
+					{
+						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_VHOST);
+					}
 					else if (!strcmp(ceppp->name, "join-flood"))
 					{
 						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_JOIN);
@@ -7847,6 +7961,9 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 		else if (!strcmp(cep->name, "server-notice-colors")) {
 			CheckNull(cep);
 		}
+		else if (!strcmp(cep->name, "server-notice-show-event")) {
+			CheckNull(cep);
+		}
 		else if (!strcmp(cep->name, "level-on-join")) {
 			CheckNull(cep);
 			CheckDuplicate(cep, level_on_join, "level-on-join");
@@ -8340,6 +8457,19 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 							errors++;
 						}
 					}
+					else if (!strcmp(ceppp->name, "vhost-flood"))
+					{
+						int cnt, period;
+						CheckNull(ceppp);
+						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
+						    (cnt < 1) || (cnt > 255) || (period < 5))
+						{
+							config_error("%s:%i: set::anti-flood::vhost-flood error. Syntax is '<count>:<period>' (eg 5:60), "
+								     "count should be 1-255, period should be greater than 4",
+								ceppp->file->filename, ceppp->line_number);
+							errors++;
+						}
+					}
 					else if (!strcmp(ceppp->name, "join-flood"))
 					{
 						int cnt, period;
@@ -9191,14 +9321,22 @@ void start_listeners(void)
 					           log_data_string("listen_ip", listener->ip),
 					           log_data_integer("listen_port", listener->port));
 				} else {
-					if (listener->ipv6)
-						snprintf(boundmsg_ipv6+strlen(boundmsg_ipv6), sizeof(boundmsg_ipv6)-strlen(boundmsg_ipv6),
-							"%s:%d%s, ", listener->ip, listener->port,
-							listener->options & LISTENER_TLS ? "(TLS)" : "");
-					else
-						snprintf(boundmsg_ipv4+strlen(boundmsg_ipv4), sizeof(boundmsg_ipv4)-strlen(boundmsg_ipv4),
-							"%s:%d%s, ", listener->ip, listener->port,
-							listener->options & LISTENER_TLS ? "(TLS)" : "");
+					switch (listener->socket_type)
+					{
+						case SOCKET_TYPE_IPV4:
+							snprintf(boundmsg_ipv4+strlen(boundmsg_ipv4), sizeof(boundmsg_ipv4)-strlen(boundmsg_ipv4),
+								"%s:%d%s, ", listener->ip, listener->port,
+								listener->options & LISTENER_TLS ? "(TLS)" : "");
+							break;
+						case SOCKET_TYPE_IPV6:
+							snprintf(boundmsg_ipv6+strlen(boundmsg_ipv6), sizeof(boundmsg_ipv6)-strlen(boundmsg_ipv6),
+								"%s:%d%s, ", listener->ip, listener->port,
+								listener->options & LISTENER_TLS ? "(TLS)" : "");
+							break;
+						// TODO: show unix domain sockets ;)
+						default:
+							break;
+					}
 				}
 			}
 		}
@@ -9268,6 +9406,7 @@ void config_run(void)
 {
 	extcmodes_check_for_changes();
 	start_listeners();
+	add_proc_io_server();
 	free_all_config_resources();
 }
 
@@ -10499,13 +10638,6 @@ void resource_download_complete(const char *url, const char *file, const char *e
 				safe_strdup(wce->ce->value, rs->file); // now information of url is lost, hm!!
 		}
 	}
-
-	/* If rehashing, check if we are done.
-	 * If booting (not rehashing), this is done from the
-	 * startup loop where it also checks is_config_read_finished().
-	 */
-	if (loop.rehashing && is_config_read_finished())
-		rehash_internal(loop.rehash_save_client);
 }
 
 /** Request to REHASH the configuration file.
@@ -10540,13 +10672,16 @@ void request_rehash(Client *client)
 
 int rehash_internal(Client *client)
 {
+	int failure;
+
 	/* Log it here if it is by a signal */
 	if (client == NULL)
 		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
 
-	loop.rehashing = 1; /* double checking.. */
+	loop.rehashing = 2; /* now doing the actual rehash */
 
-	if (config_test() == 0)
+	failure = config_test();
+	if (failure == 0)
 		config_run();
 	/* TODO: uh.. are we supposed to do all this for a failed rehash too? maybe some but not all? */
 	reread_motdsandrules();
@@ -10557,8 +10692,11 @@ int rehash_internal(Client *client)
 	// unload_all_unused_moddata(); -- this will crash
 	umodes_check_for_changes();
 	charsys_check_for_changes();
+
+	/* Clear everything now that we are done */
 	loop.rehashing = 0;
 	remote_rehash_client = NULL;
+	procio_post_rehash(failure);
 	return 1;
 }
 
diff --git a/src/crashreport.c b/src/crashreport.c
@@ -649,6 +649,13 @@ int crashreport_send(char *fname)
 	if ((n < 0) || strncmp(buf, "HTTP/1.1 100", 12))
 	{
 		printf("Error transmitting bug report (stage II, n=%d)\n", n);
+		if (!strncmp(buf, "HTTP/1.1 403", 12))
+		{
+			printf("Your crash report was rejected automatically.\n"
+			       "This normally means your UnrealIRCd version is too old and unsupported.\n"
+			       "Chances are that your crash issue is already fixed in a later release.\n"
+			       "Check https://www.unrealircd.org/ for latest releases!\n");
+		}
 		return 0;
 	}
 	
diff --git a/src/dbuf.c b/src/dbuf.c
@@ -58,6 +58,7 @@ static void dbuf_free(dbufbuf *ptr)
 
 void dbuf_queue_init(dbuf *dyn)
 {
+	memset(dyn, 0, sizeof(dbuf));
 	INIT_LIST_HEAD(&dyn->dbuf_list);
 }
 
diff --git a/src/dns.c b/src/dns.c
@@ -113,7 +113,7 @@ static int unrealdns_sock_create_cb(ares_socket_t fd, int type, void *data)
 	return ARES_SUCCESS;
 }
 
-static EVENT(unrealdns_timeout)
+EVENT(unrealdns_timeout)
 {
 	ares_process_fd(resolver_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
 }
diff --git a/src/ircd.c b/src/ircd.c
@@ -25,209 +25,10 @@
 char *malloc_options = "h" MALLOC_FLAGS_EXTRA;
 #endif
 
-#ifndef _WIN32
-extern char unreallogo[];
-#endif
-int  SVSNOOP = 0;
-extern MODVAR char *buildid;
-time_t timeofday = 0;
-struct timeval timeofday_tv;
-int  tainted = 0;
-LoopStruct loop;
-#ifndef _WIN32
-uid_t irc_uid = 0;
-gid_t irc_gid = 0;
-#endif
-
-MODVAR IRCCounts irccounts;
-MODVAR Client me;			/* That's me */
-MODVAR char *me_hash;
-extern char backupbuf[8192];
-#ifdef _WIN32
-extern SERVICE_STATUS_HANDLE IRCDStatusHandle;
-extern SERVICE_STATUS IRCDStatus;
-#endif
-
-MODVAR unsigned char conf_debuglevel = 0;
-
+/* Forward declarations */
 void server_reboot(const char *);
 void restart(const char *);
 static void open_debugfile(), setup_signals();
-extern void init_glines(void);
-extern void tkl_init(void);
-extern void process_clients(void);
-extern void unrealdb_test(void);
-
-#ifndef _WIN32
-MODVAR char **myargv;
-#else
-LPCSTR cmdLine;
-#endif
-char *configfile = NULL; 	/* Server configuration file */
-int  debuglevel = 0;		/* Server debug level */
-int  bootopt = 0;		/* Server boot option flags */
-char *debugmode = "";		/*  -"-    -"-   -"-  */
-char *sbrk0;			/* initial sbrk(0) */
-static int dorehash = 0, dorestart = 0, doreloadcert = 0;
-MODVAR int  booted = FALSE;
-
-void s_die()
-{
-#ifdef _WIN32
-	Client *client;
-	if (!IsService)
-	{
-		loop.terminating = 1;
-		unload_all_modules();
-
-		list_for_each_entry(client, &lclient_list, lclient_node)
-			(void) send_queued(client);
-
-		exit(-1);
-	}
-	else {
-		SERVICE_STATUS status;
-		SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
-		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP);
-		ControlService(hService, SERVICE_CONTROL_STOP, &status);
-	}
-#else
-	loop.terminating = 1;
-	unload_all_modules();
-	unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
-	exit(0);
-#endif
-}
-
-#ifndef _WIN32
-static void s_rehash()
-{
-	struct sigaction act;
-	dorehash = 1;
-	act.sa_handler = s_rehash;
-	act.sa_flags = 0;
-	(void)sigemptyset(&act.sa_mask);
-	(void)sigaddset(&act.sa_mask, SIGHUP);
-	(void)sigaction(SIGHUP, &act, NULL);
-}
-
-static void s_reloadcert()
-{
-	struct sigaction act;
-	doreloadcert = 1;
-	act.sa_handler = s_reloadcert;
-	act.sa_flags = 0;
-	(void)sigemptyset(&act.sa_mask);
-	(void)sigaddset(&act.sa_mask, SIGUSR1);
-	(void)sigaction(SIGUSR1, &act, NULL);
-}
-#endif // #ifndef _WIN32
-
-void restart(const char *mesg)
-{
-	server_reboot(mesg);
-}
-
-void s_restart()
-{
-	dorestart = 1;
-#if 0
-	static int restarting = 0;
-
-	if (restarting == 0) {
-		/*
-		 * Send (or attempt to) a dying scream to oper if present
-		 */
-
-		restarting = 1;
-		server_reboot("SIGINT");
-	}
-#endif
-}
-
-
-#ifndef _WIN32
-/** Signal handler for signals which we ignore,
- * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc.
- */
-void ignore_this_signal()
-{
-	struct sigaction act;
-
-	act.sa_handler = ignore_this_signal;
-	act.sa_flags = 0;
-	(void)sigemptyset(&act.sa_mask);
-	(void)sigaddset(&act.sa_mask, SIGALRM);
-	(void)sigaddset(&act.sa_mask, SIGPIPE);
-	(void)sigaction(SIGALRM, &act, (struct sigaction *)NULL);
-	(void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL);
-#ifdef SIGWINCH
-	(void)sigaddset(&act.sa_mask, SIGWINCH);
-	(void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL);
-#endif
-}
-#endif /* #ifndef _WIN32 */
-
-
-void server_reboot(const char *mesg)
-{
-	int i;
-	Client *client;
-	unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL,
-	           "Restarting server: $reason",
-	           log_data_string("reason", mesg));
-
-	list_for_each_entry(client, &lclient_list, lclient_node)
-		(void) send_queued(client);
-
-	/*
-	 * ** fd 0 must be 'preserved' if either the -d or -i options have
-	 * ** been passed to us before restarting.
-	 */
-#ifdef HAVE_SYSLOG
-	(void)closelog();
-#endif
-#ifndef _WIN32
-	for (i = 3; i < MAXCONNECTIONS; i++)
-		(void)close(i);
-	if (!(bootopt & (BOOT_TTY | BOOT_DEBUG)))
-		(void)close(2);
-	(void)close(1);
-	(void)close(0);
-	(void)execv(MYNAME, myargv);
-#else
-	close_connections();
-	if (!IsService)
-	{
-		CleanUp();
-		WinExec(cmdLine, SW_SHOWDEFAULT);
-	}
-#endif
-	unload_all_modules();
-#ifdef _WIN32
-	if (IsService)
-	{
-		SERVICE_STATUS status;
-		PROCESS_INFORMATION pi;
-		STARTUPINFO si;
-		char fname[MAX_PATH];
-		memset(&status, 0, sizeof(status));
-		memset(&si, 0, sizeof(si));
-		IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING;
-		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-		GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
-		CreateProcess(fname, "restartsvc", NULL, NULL, FALSE,
-			0, NULL, NULL, &si, &pi);
-		IRCDStatus.dwCurrentState = SERVICE_STOPPED;
-		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
-		ExitProcess(0);
-	}
-	else
-#endif
-	exit(-1);
-}
-
-MODVAR char *areason;
 
 EVENT(loop_event)
 {
@@ -557,58 +358,6 @@ void fix_timers(void)
 }
 
 
-#ifndef _WIN32
-/* Generate 3 cloak keys and print to console */
-static void generate_cloakkeys()
-{
-	#define GENERATE_CLOAKKEY_LEN 80 /* Length of cloak keys to generate. */
-	char keyBuf[GENERATE_CLOAKKEY_LEN + 1];
-	int keyNum;
-	int charIndex;
-
-	short has_upper;
-	short has_lower;
-	short has_num;
-
-	fprintf(stderr, "Here are 3 random cloak keys that you can copy-paste to your configuration file:\n\n");
-
-	fprintf(stderr, "set {\n\tcloak-keys {\n");
-	for (keyNum = 0; keyNum < 3; ++keyNum)
-	{
-		has_upper = 0;
-		has_lower = 0;
-		has_num = 0;
-
-		for (charIndex = 0; charIndex < sizeof(keyBuf)-1; ++charIndex)
-		{
-			switch (getrandom8() % 3)
-			{
-				case 0: /* Uppercase. */
-					keyBuf[charIndex] = (char)('A' + (getrandom8() % ('Z' - 'A')));
-					has_upper = 1;
-					break;
-				case 1: /* Lowercase. */
-					keyBuf[charIndex] = (char)('a' + (getrandom8() % ('z' - 'a')));
-					has_lower = 1;
-					break;
-				case 2: /* Digit. */
-					keyBuf[charIndex] = (char)('0' + (getrandom8() % ('9' - '0')));
-					has_num = 1;
-					break;
-			}
-		}
-		keyBuf[sizeof(keyBuf)-1] = '\0';
-
-		if (has_upper && has_lower && has_num)
-			fprintf(stderr, "\t\t\"%s\";\n", keyBuf);
-		else
-			/* Try again. For this reason, keyNum must be signed. */
-			keyNum--;
-	}
-	fprintf(stderr, "\t}\n}\n\n");
-}
-#endif
-
 /* MY tdiff... because 'double' sucks.
  * This should work until 2038, and very likely after that as well
  * because 'long' should be 64 bit on all systems by then... -- Syzop
@@ -677,30 +426,19 @@ void detect_timeshift_and_warn(void)
 	oldtimeofday = timeofday;
 }
 
-/** Check if at least 'minimum' seconds passed by since last run.
- * @param tv_old   Pointer to a timeval struct to keep track of things.
- * @param minimum  The time specified in milliseconds (eg: 1000 for 1 second)
- * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned.
- */
-int minimum_msec_since_last_run(struct timeval *tv_old, long minimum)
+void SetupEvents(void)
 {
-	long v;
-
-	if (tv_old->tv_sec == 0)
-	{
-		/* First call ever */
-		tv_old->tv_sec = timeofday_tv.tv_sec;
-		tv_old->tv_usec = timeofday_tv.tv_usec;
-		return 0;
-	}
-	v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000);
-	if (v >= minimum)
-	{
-		tv_old->tv_sec = timeofday_tv.tv_sec;
-		tv_old->tv_usec = timeofday_tv.tv_usec;
-		return 1;
-	}
-	return 0;
+	/* Start events */
+	EventAdd(NULL, "tunefile", save_tunefile, NULL, 300*1000, 0);
+	EventAdd(NULL, "garbage", garbage_collect, NULL, GARBAGE_COLLECT_EVERY*1000, 0);
+	EventAdd(NULL, "loop", loop_event, NULL, 1000, 0);
+	EventAdd(NULL, "unrealdns_removeoldrecords", unrealdns_removeoldrecords, NULL, 15000, 0);
+	EventAdd(NULL, "check_pings", check_pings, NULL, 1000, 0);
+	EventAdd(NULL, "check_deadsockets", check_deadsockets, NULL, 1000, 0);
+	EventAdd(NULL, "handshake_timeout", handshake_timeout, NULL, 1000, 0);
+	EventAdd(NULL, "tls_check_expiry", tls_check_expiry, NULL, (86400/2)*1000, 0);
+	EventAdd(NULL, "unrealdb_expire_secret_cache", unrealdb_expire_secret_cache, NULL, 61000, 0);
+	EventAdd(NULL, "throttling_check_expire", throttling_check_expire, NULL, 1000, 0);
 }
 
 /** The main function. This will call SocketLoop() once the server is ready. */
@@ -710,10 +448,7 @@ int main(int argc, char *argv[])
 int InitUnrealIRCd(int argc, char *argv[])
 #endif
 {
-#ifdef _WIN32
-	WORD wVersionRequested = MAKEWORD(1, 1);
-	WSADATA wsaData;
-#else
+#ifndef _WIN32
 	uid_t uid, euid;
 	gid_t gid, egid;
 #endif
@@ -755,7 +490,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 	SetErrorMode(SEM_FAILCRITICALERRORS);
 #endif
 #if !defined(_WIN32) && !defined(_AMIGA)
-	sbrk0 = (char *)sbrk((size_t)0);
 	uid = getuid();
 	euid = geteuid();
 	gid = getgid();
@@ -784,7 +518,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 #ifndef _WIN32
 	(void)umask(077);	/* better safe than sorry --SRB */
 #else
-	WSAStartup(wVersionRequested, &wsaData);
+	init_winsock();
 #endif
 	setup_signals();
 
@@ -840,43 +574,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 				safe_strdup(configfile, p);
 				convert_to_absolute_path(&configfile, CONFDIR);
 				break;
-#ifndef _WIN32
-		  case 'P':{
-			  short type;
-			  const char *result;
-			  srandom(TStime());
-			  type = Auth_FindType(NULL, p);
-			  if (type == -1)
-			  {
-			      type = AUTHTYPE_ARGON2;
-			  } else {
-			      p = *++argv;
-			      argc--;
-			  }
-			  if (BadPtr(p))
-			  {
-#ifndef _WIN32
-			      p = getpass("Enter password to hash: ");
-#else
-				  printf("ERROR: You should specify a password to hash");
-				  exit(1);
-#endif
-			  }
-			  if ((type == AUTHTYPE_UNIXCRYPT) && (strlen(p) > 8))
-			  {
-			      /* Hmmm.. is this warning really still true (and always) ?? */
-			      printf("WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
-		                 "You are suggested to use the 'argon2' algorithm instead.");
-				  p[8] = '\0';
-			  }
-			  if (!(result = Auth_Hash(type, p))) {
-				  printf("Failed to generate password. Deprecated method? Try 'argon2' instead.\n");
-				  exit(0);
-			  }
-			  printf("Encrypted password is: %s\n", result);
-			  exit(0);
-		  }
-#endif
 #if 0
 		case 'S':
 			charsys_dump_table(p ? p : "*");
@@ -922,11 +619,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 # endif
 			  exit(0);
 #endif
-#ifndef _WIN32
-		  case 'k':
-			  generate_cloakkeys();
-			  exit(0);
-#endif
 		  case 'K':
 			  {
 			  	char *p = NULL;
@@ -1032,7 +724,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 	initstats();
 	if (!loop.config_test)
 		DeleteTempModules();
-	booted = FALSE;
 #if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0
 	/* Hack to stop people from being able to read the config file */
 	(void)chmod(CPATH, DEFAULT_PERMISSIONS);
@@ -1056,11 +747,11 @@ int InitUnrealIRCd(int argc, char *argv[])
 		gettimeofday(&timeofday_tv, NULL);
 		timeofday = timeofday_tv.tv_sec;
 		url_socket_timeout(NULL);
+		unrealdns_timeout(NULL);
 		fd_select(500);
 	}
 	if (config_test() < 0)
 		exit(-1);
-	booted = TRUE;
 	load_tunefile();
 	make_umodestr();
 	SetListening(&me);
@@ -1223,6 +914,9 @@ void SocketLoop(void *dummy)
 			reinit_tls();
 			doreloadcert = 0;
 		}
+		/* If rehashing, check if we are done. */
+		if (loop.rehashing && is_config_read_finished())
+			rehash_internal(loop.rehash_save_client);
 	}
 }
 
diff --git a/src/ircd_vars.c b/src/ircd_vars.c
@@ -0,0 +1,31 @@
+/************************************************************************
+ * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/ircd_vars.c
+ * (c) 2021- Bram Matthys and The UnrealIRCd team
+ * License: GPLv2
+ */
+#include "unrealircd.h"
+
+/** @file
+ * @brief UnrealIRCd global variables of the IRCd
+ */
+
+int SVSNOOP = 0;
+time_t timeofday = 0;
+struct timeval timeofday_tv;
+int tainted = 0;
+LoopStruct loop;
+MODVAR IRCCounts irccounts;
+MODVAR Client me;			/* That's me */
+MODVAR char *me_hash;
+char *configfile = NULL; 	/* Server configuration file */
+int debuglevel = 0;		/* Server debug level */
+int bootopt = 0;		/* Server boot option flags */
+char *debugmode = "";		/*  -"-    -"-   -"-  */
+int dorehash = 0;		/**< Rehash server on next socket loop */
+int dorestart = 0;		/**< Restart server on next socket loop */
+int doreloadcert = 0;		/**< Reload TLS certificate on next socket loop */
+#ifndef _WIN32
+char **myargv;
+#else
+LPCSTR cmdLine;
+#endif
diff --git a/src/list.c b/src/list.c
@@ -44,6 +44,7 @@ MODVAR int  numclients = 0;
 // TODO: Document whether servers are included or excluded in these lists...
 
 MODVAR struct list_head unknown_list;		/**< Local clients in handshake (may become a user or server later) */
+MODVAR struct list_head control_list;		/**< Local "control channel" clients */
 MODVAR struct list_head lclient_list;		/**< Local clients (users only, right?) */
 MODVAR struct list_head client_list;		/**< All clients - local and remote (not in handshake) */
 MODVAR struct list_head server_list;		/**< Locally connected servers */
@@ -71,6 +72,7 @@ void initlists(void)
 	INIT_LIST_HEAD(&server_list);
 	INIT_LIST_HEAD(&oper_list);
 	INIT_LIST_HEAD(&unknown_list);
+	INIT_LIST_HEAD(&control_list);
 	INIT_LIST_HEAD(&global_server_list);
 	INIT_LIST_HEAD(&dead_list);
 
diff --git a/src/log.c b/src/log.c
@@ -28,7 +28,7 @@
 #include "unrealircd.h"
 
 // TODO: Make configurable at compile time (runtime won't do, as we haven't read the config file)
-#define show_event_id_console 0
+#define show_event_console 0
 
 /* Variables */
 Log *logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL };
@@ -186,7 +186,7 @@ int config_test_log(ConfigFile *conf, ConfigEntry *block)
 				/* TODO: Validate the sources lightly for formatting issues */
 				any_sources = 1;
 			}
-		}
+		} else
 		if (!strcmp(ce->name, "destination"))
 		{
 			for (cep = ce->items; cep; cep = cep->next)
@@ -225,6 +225,22 @@ int config_test_log(ConfigFile *conf, ConfigEntry *block)
 							cep->file->filename, cep->line_number, cep->value);
 						errors++;
 					}
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcmp(cepp->name, "color"))
+							;
+						else if (!strcmp(cepp->name, "show-event"))
+							;
+						else if (!strcmp(cepp->name, "json-message-tag"))
+							;
+						else if (!strcmp(cepp->name, "oper-only"))
+							;
+						else
+						{
+							config_error_unknown(cepp->file->filename, cepp->line_number, "log::destination::channel", cepp->name);
+							errors++;
+						}
+					}
 				} else
 				if (!strcmp(cep->name, "file"))
 				{
@@ -308,6 +324,10 @@ int config_test_log(ConfigFile *conf, ConfigEntry *block)
 					continue;
 				}
 			}
+		} else
+		{
+			config_error_unknown(ce->file->filename, ce->line_number, "log", ce->name);
+			errors++;
 		}
 	}
 
@@ -393,6 +413,23 @@ int config_run_log(ConfigFile *conf, ConfigEntry *block)
 					strlcpy(log->destination, cep->value, sizeof(log->destination)); /* destination is the channel */
 					log->sources = sources;
 					AddListItem(log, temp_logs[LOG_DEST_CHANNEL]);
+					/* set defaults */
+					log->color = tempiConf.server_notice_colors;
+					log->show_event = tempiConf.server_notice_show_event;
+					log->json_message_tag = 1;
+					log->oper_only = 1;
+					/* now parse options (if any) */
+					for (cepp = cep->items; cepp; cepp = cepp->next)
+					{
+						if (!strcmp(cepp->name, "color"))
+							log->color = config_checkval(cepp->value, CFG_YESNO);
+						else if (!strcmp(cepp->name, "show-event"))
+							log->show_event = config_checkval(cepp->value, CFG_YESNO);
+						else if (!strcmp(cepp->name, "json-message-tag"))
+							log->json_message_tag = config_checkval(cepp->value, CFG_YESNO);
+						else if (!strcmp(cepp->name, "oper-only"))
+							log->oper_only = config_checkval(cepp->value, CFG_YESNO);
+					}
 				} else
 				if (!strcmp(cep->name, "remote"))
 				{
@@ -499,6 +536,10 @@ void json_expand_client(json_t *j, const char *key, Client *client, int detail)
 
 	/* same for ip, is there for all (well, some services pseudo-users may not have one) */
 	json_object_set_new(child, "ip", json_string_unreal(client->ip));
+	if (client->local && client->local->listener)
+		json_object_set_new(child, "server_port", json_integer(client->local->listener->port));
+	if (client->local && client->local->port)
+		json_object_set_new(child, "client_port", json_integer(client->local->port));
 
 	/* client.details is always available: it is nick!user@host, nick@host, server@host
 	 * server@ip, or just server.
@@ -963,7 +1004,6 @@ static NameValue log_colors_terminal[] = {
 	{ ULOG_ERROR,	"\033[91m" },
 	{ ULOG_FATAL,	"\033[95m" },
 };
-#define TERMINAL_COLOR_RESET "\033[0m"
 
 const char *log_level_irc_color(LogLevel loglevel)
 {
@@ -1151,7 +1191,7 @@ literal:
 }
 
 /** Do the actual writing to log files */
-void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
+void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
 {
 	static int last_log_file_warning = 0;
 	Log *l;
@@ -1171,14 +1211,14 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 		for (m = msg; m; m = m->next)
 		{
 #ifdef _WIN32
-			if (show_event_id_console)
+			if (show_event_console)
 				win_log("* %s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
 			else
 				win_log("* [%s] %s\n", log_level_valtostring(loglevel), m->line);
 #else
 			if (terminal_supports_color())
 			{
-				if (show_event_id_console)
+				if (show_event_console)
 				{
 					fprintf(stderr, "%s%s.%s%s %s[%s]%s %s\n",
 							log_level_terminal_color(ULOG_INVALID), subsystem, event_id, TERMINAL_COLOR_RESET,
@@ -1190,7 +1230,7 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 							m->line);
 				}
 			} else {
-				if (show_event_id_console)
+				if (show_event_console)
 					fprintf(stderr, "%s.%s%s [%s] %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
 				else
 					fprintf(stderr, "[%s] %s\n", log_level_valtostring(loglevel), m->line);
@@ -1303,13 +1343,9 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 			for (m = msg; m; m = m->next)
 			{
 				char text_buf[8192];
-				snprintf(text_buf, sizeof(text_buf), "%s.%s%s %s: %s\n", subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
-				// FIXME: don't write in 2 stages, waste of slow system calls
-				if (write(l->logfd, timebuf, strlen(timebuf)) < 0)
-				{
-					/* Let's ignore any write errors for this one. Next write() will catch it... */
-					;
-				}
+				snprintf(text_buf, sizeof(text_buf), "%s%s %s.%s%s %s: %s\n",
+					timebuf, from_server->name,
+					subsystem, event_id, m->next?"+":"", log_level_valtostring(loglevel), m->line);
 				n = write(l->logfd, text_buf, strlen(text_buf));
 				if (n < strlen(text_buf))
 				{
@@ -1423,13 +1459,66 @@ const char *log_to_snomask(LogLevel loglevel, const char *subsystem, const char 
 
 #define COLOR_NONE "\xf"
 #define COLOR_DARKGREY "\00314"
-/** Do the actual writing to log files */
+
+/** Generic sendto function for logging to IRC. Used for notices to IRCOps and also for sending to individual users on channels */
+void sendto_log(Client *client, const char *msgtype, const char *destination, int show_colors, int show_event,
+                LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
+{
+	MultiLine *m;
+
+	for (m = msg; m; m = m->next)
+	{
+		MessageTag *mtags = NULL;
+		new_message(from_server, NULL, &mtags);
+
+		/* Add JSON data, but only if it is the first message (m == msg) */
+		if (json_serialized && (m == msg))
+		{
+			MessageTag *json_mtag = safe_alloc(sizeof(MessageTag));
+			safe_strdup(json_mtag->name, "unrealircd.org/json-log");
+			safe_strdup(json_mtag->value, json_serialized);
+			AddListItem(json_mtag, mtags);
+		}
+
+		if (show_colors)
+		{
+			if (show_event)
+			{
+				sendto_one(client, mtags, ":%s %s %s :%s%s.%s%s%s %s[%s]%s %s",
+					from_server->name, msgtype, destination,
+					COLOR_DARKGREY, subsystem, event_id, m->next?"+":"", COLOR_NONE,
+					log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE,
+					m->line);
+			} else {
+				sendto_one(client, mtags, ":%s %s %s :%s[%s]%s %s",
+					from_server->name, msgtype, destination,
+					log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE,
+					m->line);
+			}
+		} else {
+			if (show_event)
+			{
+				sendto_one(client, mtags, ":%s %s %s :%s.%s%s [%s] %s",
+					from_server->name, msgtype, destination,
+					subsystem, event_id, m->next?"+":"",
+					log_level_valtostring(loglevel),
+					m->line);
+			} else {
+				sendto_one(client, mtags, ":%s %s %s :[%s] %s",
+					from_server->name, msgtype, destination,
+					log_level_valtostring(loglevel),
+					m->line);
+			}
+		}
+		safe_free_message_tags(mtags);
+	}
+}
+
+/** Send server notices to IRCOps */
 void do_unreal_log_opers(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
 {
 	Client *client;
 	const char *snomask_destinations, *p;
-	MessageTag *mtags = NULL, *mtags_loop;
-	MultiLine *m;
 
 	/* If not fully booted then we don't have a logging to snomask mapping so can't do much.. */
 	if (!loop.booted)
@@ -1443,20 +1532,13 @@ void do_unreal_log_opers(LogLevel loglevel, const char *subsystem, const char *e
 	if (!snomask_destinations)
 		return;
 
-	/* Prepare message tag for those who have CAP unrealircd.org/json-log */
-	if (json_serialized)
-	{
-		mtags = safe_alloc(sizeof(MessageTag));
-		safe_strdup(mtags->name, "unrealircd.org/json-log");
-		safe_strdup(mtags->value, json_serialized);
-	}
-
 	/* To specific snomasks... */
 	list_for_each_entry(client, &oper_list, special_node)
 	{
 		const char *operlogin;
 		ConfigItem_oper *oper;
-		int colors = iConf.server_notice_colors;
+		int show_colors = iConf.server_notice_colors;
+		int show_event = iConf.server_notice_show_event;
 
 		if (snomask_destinations)
 		{
@@ -1477,30 +1559,56 @@ void do_unreal_log_opers(LogLevel loglevel, const char *subsystem, const char *e
 
 		operlogin = get_operlogin(client);
 		if (operlogin && (oper = find_oper(operlogin)))
-			colors = oper->server_notice_colors;
-
-		mtags_loop = mtags;
-		for (m = msg; m; m = m->next)
 		{
-			if (colors)
-			{
-				sendto_one(client, mtags_loop, ":%s NOTICE %s :%s%s.%s%s%s %s[%s]%s %s",
-					from_server->name, client->name,
-					COLOR_DARKGREY, subsystem, event_id, m->next?"+":"", COLOR_NONE,
-					log_level_irc_color(loglevel), log_level_valtostring(loglevel), COLOR_NONE,
-					m->line);
-			} else {
-				sendto_one(client, mtags_loop, ":%s NOTICE %s :%s.%s%s [%s] %s",
-					from_server->name, client->name,
-					subsystem, event_id, m->next?"+":"",
-					log_level_valtostring(loglevel),
-					m->line);
-			}
-			mtags_loop = NULL; /* this way we only send the JSON in the first msg */
+			show_colors = oper->server_notice_colors;
+			show_event = oper->server_notice_show_event;
 		}
+
+		sendto_log(client, "NOTICE", client->name, show_colors, show_event, loglevel, subsystem, event_id, msg, json_serialized, from_server);
 	}
+}
+
+/** Send server notices to channels */
+void do_unreal_log_channels(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
+{
+	Log *l;
+	Member *m;
+	Client *client;
+
+	/* If not fully booted then we don't have a logging to snomask mapping so can't do much.. */
+	if (!loop.booted)
+		return;
+
+	/* Never send these */
+	if (!strcmp(subsystem, "rawtraffic"))
+		return;
+
+	for (l = logs[LOG_DEST_CHANNEL]; l; l = l->next)
+	{
+		const char *operlogin;
+		ConfigItem_oper *oper;
+		Channel *channel;
+
+		if (!log_sources_match(l->sources, loglevel, subsystem, event_id, 0))
+			continue;
 
-	safe_free_message_tags(mtags);
+		channel = find_channel(l->destination);
+		if (!channel)
+			continue;
+
+		for (m = channel->members; m; m = m->next)
+		{
+			Client *client = m->client;
+			if (!MyUser(client))
+				continue;
+			if (l->oper_only && !IsOper(client))
+				continue;
+			sendto_log(client, "PRIVMSG", channel->name, l->color, l->show_event,
+			           loglevel, subsystem, event_id, msg,
+			           l->json_message_tag ? json_serialized : NULL,
+			           from_server);
+		}
+	}
 }
 
 void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized)
@@ -1522,6 +1630,25 @@ void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char *
 	do_unreal_log_remote_deliver(loglevel, subsystem, event_id, msg, json_serialized);
 }
 
+/** Send server notices to control channel */
+void do_unreal_log_control(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server)
+{
+	Client *client;
+	MultiLine *m;
+
+	if (!loop.booted)
+		return;
+
+	/* Never send these */
+	if (!strcmp(subsystem, "rawtraffic"))
+		return;
+
+	list_for_each_entry(client, &control_list, lclient_node)
+		if (IsMonitorRehash(client))
+			for (m = msg; m; m = m->next)
+				sendto_one(client, NULL, "REPLY [%s] %s", log_level_valtostring(loglevel), m->line);
+}
+
 void do_unreal_log_free_args(va_list vl)
 {
 	LogData *d;
@@ -1693,17 +1820,24 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 	/* Convert the message buffer to MultiLine */
 	mmsg = line2multiline(msgbuf);
 
-	/* Now call the disk loggers */
-	do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, json_serialized);
-
-	/* And the ircops stuff */
+	/* Parse the "from server" info, if any */
 	t = json_object_get(j_details, "from_server_name");
 	if (t && (str = json_get_value(t)))
 		from_server = find_server(str, NULL);
 	if (from_server == NULL)
 		from_server = &me;
+
+	/* Now call all the loggers: */
+
+	do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+
+	if ((loop.rehashing == 2) || !strcmp(subsystem, "config"))
+		do_unreal_log_control(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+
 	do_unreal_log_opers(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
 
+	do_unreal_log_channels(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+
 	do_unreal_log_remote(loglevel, subsystem, event_id, mmsg, json_serialized);
 
 	// NOTE: code duplication further down!
@@ -1723,10 +1857,11 @@ void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem
 	unreal_log_recursion_trap = 1;
 
 	/* Call the disk loggers */
-	do_unreal_log_disk(loglevel, subsystem, event_id, msg, json_serialized);
+	do_unreal_log_disk(loglevel, subsystem, event_id, msg, json_serialized, from_server);
 
-	/* And the ircops stuff */
+	/* And to IRC */
 	do_unreal_log_opers(loglevel, subsystem, event_id, msg, json_serialized, from_server);
+	do_unreal_log_channels(loglevel, subsystem, event_id, msg, json_serialized, from_server);
 
 	unreal_log_recursion_trap = 0;
 }
diff --git a/src/misc.c b/src/misc.c
@@ -623,8 +623,8 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons
 
 		if (client->local->fd >= 0 && !IsConnecting(client))
 		{
-			sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)",
-			    get_client_name(client, FALSE), comment);
+			if (!IsControl(client))
+				sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", get_client_name(client, FALSE), comment);
 		}
 		close_connection(client);
 	}
@@ -2369,3 +2369,173 @@ void addlettertodynamicstringsorted(char **str, char letter)
 	safe_free_raw(*str);
 	*str = newbuf;
 }
+
+void s_die()
+{
+#ifdef _WIN32
+	Client *client;
+	if (!IsService)
+	{
+		loop.terminating = 1;
+		unload_all_modules();
+
+		list_for_each_entry(client, &lclient_list, lclient_node)
+			(void) send_queued(client);
+
+		exit(-1);
+	}
+	else {
+		SERVICE_STATUS status;
+		SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+		SC_HANDLE hService = OpenService(hSCManager, "UnrealIRCd", SERVICE_STOP);
+		ControlService(hService, SERVICE_CONTROL_STOP, &status);
+	}
+#else
+	loop.terminating = 1;
+	unload_all_modules();
+	unlink(conf_files ? conf_files->pid_file : IRCD_PIDFILE);
+	exit(0);
+#endif
+}
+
+#ifndef _WIN32
+void s_rehash()
+{
+	struct sigaction act;
+	dorehash = 1;
+	act.sa_handler = s_rehash;
+	act.sa_flags = 0;
+	(void)sigemptyset(&act.sa_mask);
+	(void)sigaddset(&act.sa_mask, SIGHUP);
+	(void)sigaction(SIGHUP, &act, NULL);
+}
+
+void s_reloadcert()
+{
+	struct sigaction act;
+	doreloadcert = 1;
+	act.sa_handler = s_reloadcert;
+	act.sa_flags = 0;
+	(void)sigemptyset(&act.sa_mask);
+	(void)sigaddset(&act.sa_mask, SIGUSR1);
+	(void)sigaction(SIGUSR1, &act, NULL);
+}
+#endif // #ifndef _WIN32
+
+void restart(const char *mesg)
+{
+	server_reboot(mesg);
+}
+
+void s_restart()
+{
+	dorestart = 1;
+}
+
+#ifndef _WIN32
+/** Signal handler for signals which we ignore,
+ * like SIGPIPE ("Broken pipe") and SIGWINCH (terminal window changed) etc.
+ */
+void ignore_this_signal()
+{
+	struct sigaction act;
+
+	act.sa_handler = ignore_this_signal;
+	act.sa_flags = 0;
+	(void)sigemptyset(&act.sa_mask);
+	(void)sigaddset(&act.sa_mask, SIGALRM);
+	(void)sigaddset(&act.sa_mask, SIGPIPE);
+	(void)sigaction(SIGALRM, &act, (struct sigaction *)NULL);
+	(void)sigaction(SIGPIPE, &act, (struct sigaction *)NULL);
+#ifdef SIGWINCH
+	(void)sigaddset(&act.sa_mask, SIGWINCH);
+	(void)sigaction(SIGWINCH, &act, (struct sigaction *)NULL);
+#endif
+}
+#endif /* #ifndef _WIN32 */
+
+
+void server_reboot(const char *mesg)
+{
+	int i;
+	Client *client;
+	unreal_log(ULOG_INFO, "main", "UNREALIRCD_RESTARTING", NULL,
+	           "Restarting server: $reason",
+	           log_data_string("reason", mesg));
+
+	list_for_each_entry(client, &lclient_list, lclient_node)
+		(void) send_queued(client);
+
+	/*
+	 * ** fd 0 must be 'preserved' if either the -d or -i options have
+	 * ** been passed to us before restarting.
+	 */
+#ifdef HAVE_SYSLOG
+	(void)closelog();
+#endif
+#ifndef _WIN32
+	for (i = 3; i < MAXCONNECTIONS; i++)
+		(void)close(i);
+	if (!(bootopt & (BOOT_TTY | BOOT_DEBUG)))
+		(void)close(2);
+	(void)close(1);
+	(void)close(0);
+	close_std_descriptors();
+	(void)execv(MYNAME, myargv);
+#else
+	close_connections();
+	if (!IsService)
+	{
+		CleanUp();
+		WinExec(cmdLine, SW_SHOWDEFAULT);
+	}
+#endif
+	unload_all_modules();
+#ifdef _WIN32
+	if (IsService)
+	{
+		SERVICE_STATUS status;
+		PROCESS_INFORMATION pi;
+		STARTUPINFO si;
+		char fname[MAX_PATH];
+		memset(&status, 0, sizeof(status));
+		memset(&si, 0, sizeof(si));
+		IRCDStatus.dwCurrentState = SERVICE_STOP_PENDING;
+		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+		GetModuleFileName(GetModuleHandle(NULL), fname, MAX_PATH);
+		CreateProcess(fname, "restartsvc", NULL, NULL, FALSE,
+			0, NULL, NULL, &si, &pi);
+		IRCDStatus.dwCurrentState = SERVICE_STOPPED;
+		SetServiceStatus(IRCDStatusHandle, &IRCDStatus);
+		ExitProcess(0);
+	}
+	else
+#endif
+	exit(-1);
+}
+
+/** Check if at least 'minimum' seconds passed by since last run.
+ * @param tv_old   Pointer to a timeval struct to keep track of things.
+ * @param minimum  The time specified in milliseconds (eg: 1000 for 1 second)
+ * @returns When 'minimum' msec passed 1 is returned and the time is reset, otherwise 0 is returned.
+ */
+int minimum_msec_since_last_run(struct timeval *tv_old, long minimum)
+{
+	long v;
+
+	if (tv_old->tv_sec == 0)
+	{
+		/* First call ever */
+		tv_old->tv_sec = timeofday_tv.tv_sec;
+		tv_old->tv_usec = timeofday_tv.tv_usec;
+		return 0;
+	}
+	v = ((timeofday_tv.tv_sec - tv_old->tv_sec) * 1000) + ((timeofday_tv.tv_usec - tv_old->tv_usec)/1000);
+	if (v >= minimum)
+	{
+		tv_old->tv_sec = timeofday_tv.tv_sec;
+		tv_old->tv_usec = timeofday_tv.tv_usec;
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/modules/blacklist.c b/src/modules/blacklist.c
@@ -99,6 +99,7 @@ void blacklist_free_conf(void);
 void delete_blacklist_block(Blacklist *e);
 void blacklist_md_free(ModData *md);
 int blacklist_handshake(Client *client);
+int blacklist_ip_change(Client *client, const char *oldip);
 int blacklist_quit(Client *client, MessageTag *mtags, const char *comment);
 int blacklist_preconnect(Client *client);
 void blacklist_resolver_callback(void *arg, int status, int timeouts, struct hostent *he);
@@ -146,6 +147,7 @@ MOD_INIT()
 
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, blacklist_config_run);
 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, blacklist_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, blacklist_ip_change);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, blacklist_preconnect);
 	HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, blacklist_rehash);
 	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, blacklist_rehash_complete);
@@ -553,6 +555,12 @@ int blacklist_handshake(Client *client)
 	return 0;
 }
 
+int blacklist_ip_change(Client *client, const char *oldip)
+{
+	blacklist_start_check(client);
+	return 0;
+}
+
 int blacklist_start_check(Client *client)
 {
 	Blacklist *bl;
diff --git a/src/modules/chanmodes/halfop.c b/src/modules/chanmodes/halfop.c
@@ -73,9 +73,9 @@ int cmode_halfop_is_ok(Client *client, Channel *channel, char mode, const char *
 			/* User may always remove their own modes */
 			return EX_ALLOW;
 		}
-		if ((what == MODE_ADD) && check_channel_access(client, channel, "hoaq"))
+		if (check_channel_access(client, channel, "oaq"))
 		{
-			/* Permitted for +hoaq */
+			/* Permitted for +oaq */
 			return EX_ALLOW;
 		}
 		if (type == EXCHK_ACCESS_ERR)
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -384,10 +384,24 @@ int read_listmode(UnrealDB *db, Ban **lst)
 
 	for (i = 0; i < total; i++)
 	{
+		const char *str;
 		e = safe_alloc(sizeof(Ban));
 		R_SAFE(unrealdb_read_str(db, &e->banstr));
 		R_SAFE(unrealdb_read_str(db, &e->who));
 		R_SAFE(unrealdb_read_int64(db, &when));
+		str = clean_ban_mask(e->banstr, MODE_ADD, &me, 0);
+		if (str == NULL)
+		{
+			/* Skip this item */
+			config_warn("[channeldb] listmode skipped (no longer valid?): %s", e->banstr);
+			safe_free(e->banstr);
+			safe_free(e->who);
+			safe_free(e);
+			continue;
+		}
+		safe_strdup(e->banstr, str);
+
+		/* Add to list */
 		e->when = when;
 		e->next = *lst;
 		*lst = e;
diff --git a/src/modules/chghost.c b/src/modules/chghost.c
@@ -219,7 +219,7 @@ void _userhost_changed(Client *client)
 		}
 	}
 	
-	RunHook(HOOKTYPE_USERHOST_CHANGED, client, remember_user, remember_host);
+	RunHook(HOOKTYPE_USERHOST_CHANGE, client, remember_user, remember_host);
 
 	if (MyUser(client))
 	{
diff --git a/src/modules/dccdeny.c b/src/modules/dccdeny.c
@@ -25,7 +25,7 @@
 ModuleHeader MOD_HEADER
   = {
 	"dccdeny",
-	"5.0",
+	"6.0.2",
 	"command /dccdeny", 
 	"UnrealIRCd Team",
 	"unrealircd-6",
@@ -525,11 +525,8 @@ int dccdeny_can_send_to_channel(Client *client, Channel *channel, Membership *lp
 		const char *filename = get_dcc_filename(*msg);
 		if (filename && !can_dcc(client, channel->name, NULL, filename, &err))
 		{
-			if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE))
-			{
-				strlcpy(errbuf, err, sizeof(errbuf));
-				*errmsg = errbuf;
-			}
+			strlcpy(errbuf, err, sizeof(errbuf));
+			*errmsg = errbuf;
 			return HOOK_DENY;
 		}
 	}
@@ -649,7 +646,11 @@ static int can_dcc(Client *client, const char *target, Client *targetcli, const 
 	}
 
 	if (match_spamfilter(client, filename, SPAMF_DCC, "PRIVMSG", target, 0, NULL))
+	{
+		/* Dirty hack, yeah spamfilter already sent the error message :( */
+		*errmsg = "";
 		return 0;
+	}
 
 	if ((fl = dcc_isforbidden(client, filename)))
 	{
diff --git a/src/modules/extbans/realname.c b/src/modules/extbans/realname.c
@@ -43,7 +43,7 @@ MOD_INIT()
 	req.conv_param = extban_realname_conv_param;
 	req.is_banned = extban_realname_is_banned;
 	req.is_banned_events = BANCHK_ALL|BANCHK_TKL;
-	req.options = EXTBOPT_CHSVSMODE|EXTBOPT_INVEX|EXTBOPT_TKL;
+	req.options = EXTBOPT_INVEX|EXTBOPT_TKL;
 	if (!ExtbanAdd(modinfo->handle, req))
 	{
 		config_error("could not register extended ban type");
diff --git a/src/modules/extbans/timedban.c b/src/modules/extbans/timedban.c
@@ -78,7 +78,6 @@ MOD_INIT()
 	extban.letter = 't';
 	extban.name = "time";
 	extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */
-	extban.options |= EXTBOPT_CHSVSMODE; /* so "SVSMODE -nick" will unset affected ~t extbans */
 	extban.options |= EXTBOPT_INVEX; /* also permit timed invite-only exceptions (+I) */
 	extban.conv_param = timedban_extban_conv_param;
 	extban.is_ok = timedban_extban_is_ok;
diff --git a/src/modules/extended-monitor.c b/src/modules/extended-monitor.c
@@ -26,8 +26,8 @@ long CAP_EXTENDED_MONITOR = 0L;
 
 int extended_monitor_away(Client *client, MessageTag *mtags, const char *reason, int already_as_away);
 int extended_monitor_account_login(Client *client, MessageTag *mtags);
-int extended_monitor_userhost_changed(Client *client, const char *olduser, const char *oldhost);
-int extended_monitor_realname_changed(Client *client, const char *oldinfo);
+int extended_monitor_userhost_change(Client *client, const char *olduser, const char *oldhost);
+int extended_monitor_realname_change(Client *client, const char *oldinfo);
 int extended_monitor_notification(Client *client, Watch *watch, Link *lp, int event);
 
 ModuleHeader MOD_HEADER
@@ -59,8 +59,8 @@ MOD_INIT()
 
 	HookAdd(modinfo->handle, HOOKTYPE_AWAY, 0, extended_monitor_away);
 	HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, extended_monitor_account_login);
-	HookAdd(modinfo->handle, HOOKTYPE_USERHOST_CHANGED, 0, extended_monitor_userhost_changed);
-	HookAdd(modinfo->handle, HOOKTYPE_REALNAME_CHANGED, 0, extended_monitor_realname_changed);
+	HookAdd(modinfo->handle, HOOKTYPE_USERHOST_CHANGE, 0, extended_monitor_userhost_change);
+	HookAdd(modinfo->handle, HOOKTYPE_REALNAME_CHANGE, 0, extended_monitor_realname_change);
 
 	return MOD_SUCCESS;
 }
@@ -95,13 +95,13 @@ int extended_monitor_account_login(Client *client, MessageTag *mtags)
 	return 0;
 }
 
-int extended_monitor_userhost_changed(Client *client, const char *olduser, const char *oldhost)
+int extended_monitor_userhost_change(Client *client, const char *olduser, const char *oldhost)
 {
 	watch_check(client, WATCH_EVENT_USERHOST, extended_monitor_notification);
 	return 0;
 }
 
-int extended_monitor_realname_changed(Client *client, const char *oldinfo)
+int extended_monitor_realname_change(Client *client, const char *oldinfo)
 {
 	watch_check(client, WATCH_EVENT_REALNAME, extended_monitor_notification);
 	return 0;
diff --git a/src/modules/geoip_base.c b/src/modules/geoip_base.c
@@ -25,6 +25,7 @@ void geoip_base_free(ModData *m);
 const char *geoip_base_serialize(ModData *m);
 void geoip_base_unserialize(const char *str, ModData *m);
 int geoip_base_handshake(Client *client);
+int geoip_base_ip_change(Client *client, const char *oldip);
 int geoip_base_whois(Client *client, Client *target, NameValuePrioList **list);
 int geoip_connect_extinfo(Client *client, NameValuePrioList **list);
 int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
@@ -119,10 +120,10 @@ MOD_INIT()
 
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_base_configrun);
 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, geoip_base_handshake);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, geoip_base_ip_change);
 	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, geoip_base_handshake);
 	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */
-	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, geoip_base_handshake); /* remote user */
 	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois);
 
 	CommandAdd(modinfo->handle, "GEOIP", cmd_geoip, MAXPARA, CMD_USER);
@@ -167,6 +168,12 @@ int geoip_base_handshake(Client *client)
 	return 0;
 }
 
+int geoip_base_ip_change(Client *client, const char *oldip)
+{
+	geoip_base_handshake(client);
+	return 0;
+}
+
 void geoip_base_free(ModData *m)
 {
 	if (m->ptr)
@@ -229,12 +236,13 @@ void geoip_base_unserialize(const char *str, ModData *m)
 	m->ptr = res;
 }
 
-EVENT(geoip_base_set_existing_users_evt){
+EVENT(geoip_base_set_existing_users_evt)
+{
 	Client *client;
-	list_for_each_entry(client, &client_list, client_node){
-		if (!IsUser(client))
-			continue;
-		geoip_base_handshake(client);
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (MyUser(client))
+			geoip_base_handshake(client);
 	}
 }
 
diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c
@@ -43,6 +43,22 @@ static ModuleInfo	*MyModInfo;
 #define MyMod		MyModInfo->handle
 #define SAVE_MODINFO	MyModInfo = modinfo;
 
+static int lmax = 0;
+static int umax = 0;
+
+static int dcount(int n)
+{
+   int cnt = 0;
+
+   while (n != 0)
+   {
+	   n = n/10;
+	   cnt++;
+   }
+
+   return cnt;
+}
+
 ModuleHeader MOD_HEADER
   = {
 	"hideserver",
@@ -230,8 +246,24 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
 	else
 	{
-		sendnumeric(client, RPL_MAP, prompt,
-		            length, server->name, server->server->users, IsOper(client) ? server->id : "");
+		char tbuf[256];
+		char sid[10];
+		int len = length - strlen(server->name) + 1;
+
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+		if (IsOper(client))
+			snprintf(sid, sizeof(sid), " [%s]", server->id);
+		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
+			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(server->server->users * 100.0 / irccounts.clients),
+			IsOper(client) ? sid : "");
 		cnt = 0;
 	}
 
@@ -279,12 +311,25 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 void dump_flat_map(Client *client, Client *server, int length)
 {
 	char buf[4];
+	char tbuf[256];
 	Client *acptr;
-	int cnt = 0, hide_ulines;
+	int cnt = 0, len = 0, hide_ulines;
 
 	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
 
-	sendnumeric(client, RPL_MAP, "", length, server->name, server->server->users, "");
+	len = length - strlen(server->name) + 3;
+	if (len < 0)
+		len = 0;
+	if (len > 255)
+		len = 255;
+
+	tbuf[len--] = '\0';
+	while (len >= 0)
+		tbuf[len--] = '-';
+
+	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
+		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+		(server->server->users * 100.0 / irccounts.clients), "");
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
@@ -304,7 +349,20 @@ void dump_flat_map(Client *client, Client *server, int length)
 			break;
 		if (--cnt == 0)
 			*buf = '`';
-		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->server->users, "");
+
+		len = length - strlen(acptr->name) + 1;
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+
+		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
+			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(acptr->server->users * 100.0 / irccounts.clients), "");
 	}
 }
 
@@ -318,6 +376,10 @@ CMD_OVERRIDE_FUNC(override_map)
 {
 	Client *acptr;
 	int longest = strlen(me.name);
+	float avg_users = 0.0;
+
+	umax = 0;
+	lmax = 0;
 
 	if (parc < 2)
 		parv[1] = "*";
@@ -339,10 +401,16 @@ CMD_OVERRIDE_FUNC(override_map)
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
+		int perc = 0;
 		if (FindHiddenServer(acptr->name))
 			break;
+		perc = (acptr->server->users * 100 / irccounts.clients);
 		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
 			longest = strlen(acptr->name) + acptr->hopcount * 2;
+		if (lmax < perc)
+			lmax = perc;
+		if (umax < dcount(acptr->server->users))
+			umax = dcount(acptr->server->users);
 	}
 
 	if (longest > 60)
@@ -354,6 +422,9 @@ CMD_OVERRIDE_FUNC(override_map)
 	else
 		dump_map(client, &me, "*", 0, longest);
 
+	avg_users = irccounts.clients * 1.0 / irccounts.servers;
+	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
+		(irccounts.clients > 1 ? "s" : ""), avg_users);
 	sendnumeric(client, RPL_MAPEND);
 }
 
diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c
@@ -196,7 +196,7 @@ int lr_post_command(Client *from, MessageTag *mtags, const char *buf)
 			 */
 			int more_tags = currentcmd.firstbuf[0] == '@';
 			currentcmd.client = NULL; /* prevent lr_packet from interfering */
-			snprintf(packet, sizeof(packet),
+			snprintf(packet, sizeof(packet)-3,
 				 "@label=%s%s%s\r\n",
 				 currentcmd.label,
 				 more_tags ? ";" : " ",
diff --git a/src/modules/list.c b/src/modules/list.c
@@ -31,7 +31,7 @@ ModuleHeader MOD_HEADER
   = {
 	"list",
 	"5.0",
-	"command /list", 
+	"command /LIST",
 	"UnrealIRCd Team",
 	"unrealircd-6",
     };
@@ -136,8 +136,8 @@ CMD_FUNC(cmd_list)
 		"use, and what channels LIST will return when you use them.",
 		">number  List channels with more than <number> people.",
 		"<number  List channels with less than <number> people.",
-		"C>number List channels created between now and <number> minutes ago.",
-		"C<number List channels created earlier than <number> minutes ago.",
+		"C>number List channels created more than <number> minutes ago.",
+		"C<number List channels created less than <number> minutes ago.",
 		"T>number List channels whose topics are older than <number> minutes",
 		"         (Ie, they have not changed in the last <number> minutes.",
 		"T<number List channels whose topics are not older than <number> minutes.",
@@ -199,56 +199,52 @@ CMD_FUNC(cmd_list)
 		}
 		switch (*name)
 		{
-		  case '<':
-			  usermax = atoi(name + 1) - 1;
-			  doall = 1;
-			  break;
-		  case '>':
-			  usermin = atoi(name + 1) + 1;
-			  doall = 1;
-			  break;
-		  case 'C':
-		  case 'c':	/* Channel time -- creation time? */
-			  ++name;
-			  switch (*name++)
-			  {
-			    case '<':
-				    chantimemax = currenttime - 60 * atoi(name);
-				    doall = 1;
-				    break;
-			    case '>':
-				    chantimemin = currenttime - 60 * atoi(name);
-				    doall = 1;
-				    break;
-			    default:
-				    sendnumeric(client, ERR_LISTSYNTAX);
-				    error = 1;
-			  }
-			  break;
-#ifdef LIST_USE_T
-		  case 'T':
-		  case 't':
-			  ++name;
-			  switch (*name++)
-			  {
-			    case '<':
-				    topictimemax =
-					currenttime - 60 * atoi(name);
-				    doall = 1;
-				    break;
-			    case '>':
-				    topictimemin =
-					currenttime - 60 * atoi(name);
-				    doall = 1;
-				    break;
-			    default:
-				    sendnumeric(client, ERR_LISTSYNTAX,
-					"Bad list syntax, type /list ?");
-				    error = 1;
-			  }
-			  break;
-#endif
-		  default:	/* A channel, possibly with wildcards.
+			case '<':
+				usermax = atoi(name + 1) - 1;
+				doall = 1;
+				break;
+			case '>':
+				usermin = atoi(name + 1) + 1;
+				doall = 1;
+				break;
+			case 'C':
+			case 'c':	/* Channel time -- creation time? */
+				++name;
+				switch (*name++)
+				{
+					case '<':
+						chantimemin = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					case '>':
+						chantimemax = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					default:
+						sendnumeric(client, ERR_LISTSYNTAX);
+						error = 1;
+				}
+				break;
+			case 'T':
+			case 't':
+				++name;
+				switch (*name++)
+				{
+					case '<':
+						topictimemin = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					case '>':
+						topictimemax = currenttime - 60 * atoi(name);
+						doall = 1;
+						break;
+					default:
+						sendnumeric(client, ERR_LISTSYNTAX);
+						error = 1;
+				}
+				break;
+			default:
+				/* A channel, possibly with wildcards.
 				 * Thought for the future: Consider turning wildcard
 				 * processing on the fly.
 				 * new syntax: !channelmask will tell ircd to ignore
@@ -259,35 +255,38 @@ CMD_FUNC(cmd_list)
 				 * channel even if any of the !channelmask masks
 				 * matches it.
 				 */
-			  if (*name == '!')
-			  {
-				  doall = 1;
-				  add_name_list(nolist, name + 1);
-			  }
-			  else if (strchr(name, '*') || strchr(name, '?'))
-			  {
-				  doall = 1;
-				  add_name_list(yeslist, name);
-			  }
-			  else	/* Just a normal channel */
-			  {
-				  channel = find_channel(name);
-				  if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))) {
-					modebuf[0] = '[';
-					channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
-					if (modebuf[2] == '\0')
-						modebuf[0] = '\0';
-					else
-						strlcat(modebuf, "]", sizeof modebuf);
-					  sendnumeric(client, RPL_LIST,
-					      name, channel->users,
-					      modebuf,
-					      (channel->topic ? channel->topic :
-					      ""));
-}
-			  }
-		}		/* switch */
-	}			/* while */
+				if (*name == '!')
+				{
+					/* Negative matching by name */
+					doall = 1;
+					add_name_list(nolist, name + 1);
+				}
+				else if (strchr(name, '*') || strchr(name, '?'))
+				{
+					/* Channel with wildcards */
+					doall = 1;
+					add_name_list(yeslist, name);
+				}
+				else
+				{
+					/* A specific channel name without wildcards */
+					channel = find_channel(name);
+					if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)))
+					{
+						modebuf[0] = '[';
+						channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0);
+
+						if (modebuf[2] == '\0')
+							modebuf[0] = '\0';
+						else
+							strlcat(modebuf, "]", sizeof modebuf);
+
+						sendnumeric(client, RPL_LIST, name, channel->users, modebuf,
+							    channel->topic ? channel->topic : "");
+					}
+				}
+		} /* switch */
+	} /* for */
 
 	if (doall)
 	{
@@ -335,7 +334,7 @@ int send_list(Client *client)
 	 * choice of numsend. -Rak
 	 */	
 
-	/* Begin of /list? then send official channels. */
+	/* Begin of /LIST? then send official channels first. */
 	if ((lopt->starthash == 0) && conf_offchans)
 	{
 		ConfigItem_offchans *x;
@@ -343,18 +342,15 @@ int send_list(Client *client)
 		{
 			if (find_channel(x->name))
 				continue; /* exists, >0 users.. will be sent later */
-			sendnumeric(client, RPL_LIST, x->name,
-			    0,
-			    "",
-			    x->topic ? x->topic : "");
+			sendnumeric(client, RPL_LIST, x->name, 0, "",
+			            x->topic ? x->topic : "");
 		}
 	}
 
 	for (hashnum = lopt->starthash; hashnum < CHAN_HASH_TABLE_SIZE; hashnum++)
 	{
 		if (numsend > 0)
-			for (channel = hash_get_chan_bucket(hashnum);
-			    channel; channel = channel->hnextch)
+			for (channel = hash_get_chan_bucket(hashnum); channel; channel = channel->hnextch)
 			{
 				if (SecretChannel(channel)
 				    && !IsMember(client, channel)
@@ -373,15 +369,13 @@ int send_list(Client *client)
 				if ((!lopt->showall))
 				{
 					/* User count must be in range */
-					if ((channel->users < lopt->usermin) || 
-					    ((lopt->usermax >= 0) && (channel->users > 
-					    lopt->usermax)))
+					if ((channel->users < lopt->usermin) ||
+					    ((lopt->usermax >= 0) && (channel->users > lopt->usermax)))
 						continue;
 
 					/* Creation time must be in range */
-					if ((channel->creationtime && (channel->creationtime <
-					    lopt->chantimemin)) || (channel->creationtime >
-					    lopt->chantimemax))
+					if ((channel->creationtime && (channel->creationtime < lopt->chantimemin)) ||
+					    (channel->creationtime > lopt->chantimemax))
 						continue;
 
 					/* Topic time must be in range */
@@ -432,7 +426,7 @@ int send_list(Client *client)
 		return 0;
 	}
 
-	/* 
+	/*
 	 * We've exceeded the limit on the number of channels to send back
 	 * at once.
 	 */
diff --git a/src/modules/map.c b/src/modules/map.c
@@ -26,6 +26,22 @@ CMD_FUNC(cmd_map);
 
 #define MSG_MAP 	"MAP"	
 
+static int lmax = 0;
+static int umax = 0;
+
+static int dcount(int n)
+{
+   int cnt = 0;
+
+   while (n != 0)
+   {
+	   n = n/10;
+	   cnt++;
+   }
+
+   return cnt;
+}
+
 ModuleHeader MOD_HEADER
   = {
 	"map",
@@ -70,8 +86,24 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 		sendnumeric(client, RPL_MAPMORE, prompt, length, server->name);
 	else
 	{
-		sendnumeric(client, RPL_MAP, prompt,
-		            length, server->name, server->server->users, IsOper(client) ? server->id : "");
+		char tbuf[256];
+		char sid[10];
+		int len = length - strlen(server->name) + 1;
+
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+		if (IsOper(client))
+			snprintf(sid, sizeof(sid), " [%s]", server->id);
+		sendnumeric(client, RPL_MAP, prompt, server->name, tbuf, umax,
+			server->server->users, (double)(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(server->server->users * 100.0 / irccounts.clients),
+			IsOper(client) ? sid : "");
 		cnt = 0;
 	}
 
@@ -115,12 +147,25 @@ static void dump_map(Client *client, Client *server, char *mask, int prompt_leng
 void dump_flat_map(Client *client, Client *server, int length)
 {
 	char buf[4];
+	char tbuf[256];
 	Client *acptr;
-	int cnt = 0, hide_ulines;
+	int cnt = 0, len = 0, hide_ulines;
 
 	hide_ulines = (HIDE_ULINES && !ValidatePermissionsForPath("server:info:map:ulines",client,NULL,NULL,NULL)) ? 1 : 0;
 
-	sendnumeric(client, RPL_MAP, "", length, server->name, server->server->users, "");
+	len = length - strlen(server->name) + 3;
+	if (len < 0)
+		len = 0;
+	if (len > 255)
+		len = 255;
+
+	tbuf[len--] = '\0';
+	while (len >= 0)
+		tbuf[len--] = '-';
+
+	sendnumeric(client, RPL_MAP, "", server->name, tbuf, umax, server->server->users,
+		(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+		(server->server->users * 100.0 / irccounts.clients), "");
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
@@ -136,7 +181,20 @@ void dump_flat_map(Client *client, Client *server, int length)
 			continue;
 		if (--cnt == 0)
 			*buf = '`';
-		sendnumeric(client, RPL_MAP, buf, length-2, acptr->name, acptr->server->users, "");
+
+		len = length - strlen(acptr->name) + 1;
+		if (len < 0)
+			len = 0;
+		if (len > 255)
+			len = 255;
+
+		tbuf[len--] = '\0';
+		while (len >= 0)
+			tbuf[len--] = '-';
+
+		sendnumeric(client, RPL_MAP, buf, acptr->name, tbuf, umax, acptr->server->users,
+			(lmax < 10) ? 4 : (lmax == 100) ? 6 : 5,
+			(acptr->server->users * 100.0 / irccounts.clients), "");
 	}
 }
 
@@ -150,14 +208,23 @@ CMD_FUNC(cmd_map)
 {
 	Client *acptr;
 	int  longest = strlen(me.name);
+	float avg_users;
+
+	umax = 0;
+	lmax = 0;
 
 	if (parc < 2)
 		parv[1] = "*";
 
 	list_for_each_entry(acptr, &global_server_list, client_node)
 	{
+		int perc = (acptr->server->users * 100 / irccounts.clients);
 		if ((strlen(acptr->name) + acptr->hopcount * 2) > longest)
 			longest = strlen(acptr->name) + acptr->hopcount * 2;
+		if (lmax < perc)
+			lmax = perc;
+		if (umax < dcount(acptr->server->users))
+			umax = dcount(acptr->server->users);
 	}
 
 	if (longest > 60)
@@ -169,5 +236,8 @@ CMD_FUNC(cmd_map)
 	else
 		dump_map(client, &me, "*", 0, longest);
 
+	avg_users = irccounts.clients * 1.0 / irccounts.servers;
+	sendnumeric(client, RPL_MAPUSERS, irccounts.servers, (irccounts.servers > 1 ? "s" : ""), irccounts.clients,
+		(irccounts.clients > 1 ? "s" : ""), avg_users);
 	sendnumeric(client, RPL_MAPEND);
 }
diff --git a/src/modules/message.c b/src/modules/message.c
@@ -37,7 +37,7 @@ long CAP_MESSAGE_TAGS = 0; /**< Looked up at MOD_LOAD, may stay 0 if message-tag
 ModuleHeader MOD_HEADER
   = {
 	"message",	/* Name of module */
-	"5.0", /* Version */
+	"6.0.2", /* Version */
 	"private message and notice", /* Short description of module */
 	"UnrealIRCd Team",
 	"unrealircd-6",
@@ -318,7 +318,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *p
 					 */
 					if (IsDead(client))
 						return;
-					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && errmsg)
+					if (!IsDead(client) && (sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
 						sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->name, errmsg, p2);
 					continue; /* skip delivery to this target */
 				}
@@ -423,7 +423,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, const char *p
 				/* Message is discarded */
 				if (IsDead(client))
 					return;
-				if ((sendtype != SEND_TYPE_NOTICE) && errmsg)
+				if ((sendtype != SEND_TYPE_NOTICE) && !BadPtr(errmsg))
 					sendnumeric(client, ERR_CANTSENDTOUSER, target->name, errmsg);
 			} else
 			{
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -370,28 +370,19 @@ void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc
 			       ":%s MODE %s %s %s",
 			       client->name, channel->name, modebuf, parabuf);
 
-		if (IsServer(client) && sendts != -1)
+		if (IsServer(client) || IsMe(client))
 		{
 			sendto_server(client, 0, 0, mtags,
 				      ":%s MODE %s %s %s %lld",
 				      client->id, channel->name,
 				      modebuf, parabuf,
-				      (long long)sendts);
-		} else
-		if (samode && IsMe(client))
-		{
-			/* SAMODE is a special case: always send a TS of 0 (omitting TS==desync) */
-			sendto_server(client, 0, 0, mtags,
-				      ":%s MODE %s %s %s 0",
-				      client->id, channel->name,
-				      modebuf, parabuf);
+				      (sendts != -1) ? (long long)sendts : 0LL);
 		} else
 		{
 			sendto_server(client, 0, 0, mtags,
 				      ":%s MODE %s %s %s",
 				      client->id, channel->name,
 				      modebuf, parabuf);
-			/* tell them it's not a timestamp, in case the last param is a number. */
 		}
 
 		if (MyConnect(client))
@@ -1159,6 +1150,22 @@ CMD_FUNC(_cmd_umode)
 				goto def;
 			case 't':
 			case 'x':
+				/* set::anti-flood::vhost-flood */
+				if (MyUser(client))
+				{
+					if ((what == MODE_DEL) && !ValidatePermissionsForPath("immune:vhost-flood",client,NULL,NULL,NULL) &&
+							flood_limit_exceeded(client, FLD_VHOST))
+					{
+						/* Throttle... */
+						if (!modex_err)
+						{
+							sendnotice(client, "*** Setting -%c too fast. Please try again later.", *m);
+							modex_err = 1;
+						}
+						break;
+					}
+				}
+
 				switch (UHOST_ALLOWED)
 				{
 				case UHALLOW_ALWAYS:
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -141,6 +141,7 @@ CMD_FUNC(reputationunperm);
 int reputation_whois(Client *client, Client *target, NameValuePrioList **list);
 int reputation_set_on_connect(Client *client);
 int reputation_pre_lconnect(Client *client);
+int reputation_ip_change(Client *client, const char *oldip);
 int reputation_connect_extinfo(Client *client, NameValuePrioList **list);
 int reputation_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int reputation_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
@@ -192,6 +193,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, reputation_config_run);
 	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, reputation_whois);
 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, reputation_set_on_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, reputation_ip_change);
 	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 2000000000, reputation_pre_lconnect); /* (prio: last) */
 	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, -1000000000, reputation_set_on_connect); /* (prio: near-first) */
 	HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 0, reputation_connect_extinfo); /* (prio: near-first) */
@@ -804,6 +806,12 @@ int reputation_set_on_connect(Client *client)
 	return 0;
 }
 
+int reputation_ip_change(Client *client, const char *oldip)
+{
+	reputation_lookup_score_and_set(client);
+	return 0;
+}
+
 int reputation_pre_lconnect(Client *client)
 {
 	/* User will likely be accepted. Inform other servers about the score
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -170,9 +170,9 @@ CMD_FUNC(cmd_sapart)
 	if (comment)
 	{
 		snprintf(commentx, sizeof(commentx), "SAPart: %s", comment);
-		//sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
+		sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
 	} else {
-		//sendnotice(target, "*** You were forced to part %s", request);
+		sendnotice(target, "*** You were forced to part %s", request);
 	}
 
 	parv[0] = target->name; // nick
diff --git a/src/modules/setname.c b/src/modules/setname.c
@@ -158,5 +158,5 @@ CMD_FUNC(cmd_setname)
 	}
 	free_message_tags(mtags);
 	
-	RunHook(HOOKTYPE_REALNAME_CHANGED, client, oldinfo);
+	RunHook(HOOKTYPE_REALNAME_CHANGE, client, oldinfo);
 }
diff --git a/src/modules/stats.c b/src/modules/stats.c
@@ -520,17 +520,17 @@ int stats_command(Client *client, const char *para)
 
 int stats_oper(Client *client, const char *para)
 {
-	ConfigItem_oper *oper_p;
+	ConfigItem_oper *o;
 	ConfigItem_mask *m;
 
-	for (oper_p = conf_oper; oper_p; oper_p = oper_p->next)
+	for (o = conf_oper; o; o = o->next)
 	{
-		for (m = oper_p->mask; m; m = m->next)
+		for (m = o->mask; m; m = m->next)
 		{
-	  		sendnumeric(client, RPL_STATSOLINE,
-	  			'O', m->mask, oper_p->name,
-	  			"-",
-	  			oper_p->class->name? oper_p->class->name : "");
+			sendnumeric(client, RPL_STATSOLINE,
+			            'O', m->mask, o->name,
+			            o->operclass ? o->operclass: "",
+			            o->class->name ? o->class->name : "");
 		}
 	}
 	return 0;
@@ -540,11 +540,20 @@ static char *stats_port_helper(ConfigItem_listen *listener)
 {
 	static char buf[256];
 
-	ircsnprintf(buf, sizeof(buf), "%s%s%s%s",
+	ircsnprintf(buf, sizeof(buf), "%s%s%s",
 	    (listener->options & LISTENER_CLIENTSONLY)? "clientsonly ": "",
 	    (listener->options & LISTENER_SERVERSONLY)? "serversonly ": "",
-	    (listener->options & LISTENER_TLS)?         "tls ": "",
-	    !(listener->options & LISTENER_TLS)?        "plaintext ": "");
+	    (listener->options & LISTENER_DEFER_ACCEPT)? "defer-accept ": "");
+
+	/* And one of these.. */
+	if (listener->options & LISTENER_CONTROL)
+		strlcat(buf, "control ", sizeof(buf));
+	else if (listener->socket_type == SOCKET_TYPE_UNIX)
+		;
+	else if (listener->options & LISTENER_TLS)
+		strlcat(buf, "tls ", sizeof(buf));
+	else
+		strlcat(buf, "plaintext ", sizeof(buf));
 	return buf;
 }
 
@@ -558,13 +567,22 @@ int stats_port(Client *client, const char *para)
 			continue;
 		if ((listener->options & LISTENER_SERVERSONLY) && !ValidatePermissionsForPath("server:info:stats",client,NULL,NULL,NULL))
 			continue;
-		sendnotice(client, "*** Listener on %s:%i (%s): has %i client(s), options: %s %s",
-		           listener->ip,
-		           listener->port,
-		           listener->ipv6 ? "IPv6" : "IPv4",
-		           listener->clients,
-		           stats_port_helper(listener),
-		           listener->flag.temporary ? "[TEMPORARY]" : "");
+		if (listener->socket_type == SOCKET_TYPE_UNIX)
+		{
+			sendnotice(client, "*** Listener on %s (UNIX): has %i client(s), options: %s %s",
+				   listener->file,
+				   listener->clients,
+				   stats_port_helper(listener),
+				   listener->flag.temporary ? "[TEMPORARY]" : "");
+		} else {
+			sendnotice(client, "*** Listener on %s:%i (%s): has %i client(s), options: %s %s",
+				   listener->ip,
+				   listener->port,
+				   listener->socket_type == SOCKET_TYPE_IPV6 ? "IPv6" : "IPv4",
+				   listener->clients,
+				   stats_port_helper(listener),
+				   listener->flag.temporary ? "[TEMPORARY]" : "");
+		}
 	}
 	return 0;
 }
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
@@ -136,7 +136,7 @@ void unban_user(Client *client, Channel *channel, Client *acptr, char chmode)
 		}
 		else if (chmode != 'I' && *ban->banstr == '~' && (extban = findmod_by_bantype(ban->banstr, &nextbanstr)))
 		{
-			if ((extban->options & EXTBOPT_CHSVSMODE) && (extban->is_banned_events & b->ban_check_types))
+			if (extban->is_banned_events & b->ban_check_types)
 			{
 				b->banstr = nextbanstr;
 				if (extban->is_banned(b))
@@ -176,7 +176,7 @@ void clear_bans(Client *client, Channel *channel, char chmode)
 		bnext = ban->next;
 		if (chmode != 'I' && (*ban->banstr == '~') && (extban = findmod_by_bantype(ban->banstr, NULL)))
 		{
-			if (!(extban->options & EXTBOPT_CHSVSMODE))							
+			if (!(extban->is_banned_events & BANCHK_JOIN))
 				continue;
 		}
 		add_send_mode_param(channel, client, '-',  chmode, ban->banstr);
@@ -298,7 +298,7 @@ void channel_svsmode(Client *client, int parc, const char *parv[])
 		sendto_channel(channel, client, client, 0, 0, SEND_LOCAL, mtags,
 		               ":%s MODE %s %s %s",
 		               client->name, channel->name,  modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", client->id, channel->name, modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", client->id, channel->name, modebuf, parabuf, IsServer(client)?" 0":"");
 
 		/* Activate this hook just like cmd_mode.c */
 		RunHook(HOOKTYPE_REMOTE_CHANMODE, client, channel, mtags, modebuf, parabuf, 0, 0, &destroy_channel);
@@ -615,7 +615,7 @@ void add_send_mode_param(Channel *channel, Client *from, char what, char mode, c
 		sendto_channel(channel, from, from, 0, 0, SEND_LOCAL, mtags,
 		               ":%s MODE %s %s %s",
 		               from->name, channel->name, modebuf, parabuf);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s", from->id, channel->name, modebuf, parabuf);
+		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s%s", from->id, channel->name, modebuf, parabuf, IsServer(from)?" 0":"");
 		free_message_tags(mtags);
 		send = 0;
 		*parabuf = 0;
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -41,6 +41,7 @@ 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);
+int tkl_ip_change(Client *client, const char *oldip);
 CMD_FUNC(cmd_gline);
 CMD_FUNC(cmd_shun);
 CMD_FUNC(cmd_tempshun);
@@ -213,6 +214,7 @@ MOD_INIT()
 	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);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 2000000000, tkl_ip_change);
 	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);
@@ -952,6 +954,12 @@ char *spamfilter_id(TKL *tk)
 	return buf;
 }
 
+int tkl_ip_change(Client *client, const char *oldip)
+{
+	check_banned(client, 0);
+	return 0;
+}
+
 /** GLINE - Global kline.
 ** Syntax: /gline [+|-]u@h mask time :reason
 **
@@ -4772,23 +4780,15 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
 
 		if (ret)
 		{
-			/* We have a match! */
-			char destinationbuf[48];
-
-			if (destination) {
-				destinationbuf[0] = ' ';
-				strlcpy(destinationbuf+1, destination, sizeof(destinationbuf)-1); /* cut it off */
-			} else
-				destinationbuf[0] = '\0';
-
-			/* Hold on.. perhaps it's on the exceptions list... */
+			/* We have a match! But.. perhaps it's on the exceptions list? */
 			if (!winner_tkl && destination && target_is_spamexcept(destination))
 				return 0; /* No problem! */
 
 			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
-			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
+			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
 				   log_data_tkl("tkl", tkl),
 				   log_data_string("command", cmd),
+				   log_data_string("_space", destination ? " " : ""),
 				   log_data_string("destination", destination ? destination : ""),
 				   log_data_string("str", str));
 
diff --git a/src/modules/watch-backend.c b/src/modules/watch-backend.c
@@ -29,9 +29,9 @@
 
 ModDataInfo *watchCounterMD;
 ModDataInfo *watchListMD;
-static Watch *watchTable[WATCH_HASH_TABLE_SIZE];
-static int watch_initialized = 0;
-static char siphashkey_watch[SIPHASH_KEY_LENGTH];
+
+static Watch **watchTable = NULL;
+static char *siphashkey_watch = NULL;
 
 void dummy_free(ModData *md);
 void watch_free(ModData *md);
@@ -47,8 +47,8 @@ uint64_t hash_watch_nick_name(const char *name);
 ModuleHeader MOD_HEADER
 = {
 	"watch-backend",
-	"5.0",
-	"backend for /watch", 
+	"6.0.3",
+	"backend for /WATCH",
 	"UnrealIRCd Team",
 	"unrealircd-6",
 };
@@ -65,20 +65,28 @@ MOD_TEST()
 	return MOD_SUCCESS;
 }
 
+void watch_generic_free(ModData *m)
+{
+	safe_free(m->ptr);
+}
+
 MOD_INIT()
 {	
 	ModDataInfo mreq;
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1); /* or do a complex memory freeing algorithm instead */
-	
-	if (!watch_initialized)
+
+	LoadPersistentPointer(modinfo, siphashkey_watch, watch_generic_free);
+	if (siphashkey_watch == NULL)
 	{
-		memset(watchTable, 0, sizeof(watchTable));
+		siphashkey_watch = safe_alloc(SIPHASH_KEY_LENGTH);
 		siphash_generate_key(siphashkey_watch);
-		watch_initialized = 1;
 	}
-	
+	LoadPersistentPointer(modinfo, watchTable, watch_generic_free);
+	if (watchTable == NULL)
+		watchTable = safe_alloc(sizeof(Watch) * WATCH_HASH_TABLE_SIZE);
+
 	memset(&mreq, 0 , sizeof(mreq));
 	mreq.type = MODDATATYPE_LOCAL_CLIENT;
 	mreq.name = "watchCount",
@@ -113,6 +121,8 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
+	SavePersistentPointer(modinfo, siphashkey_watch);
+	SavePersistentPointer(modinfo, watchTable);
 	return MOD_SUCCESS;
 }
 
@@ -151,13 +161,13 @@ int _watch_add(char *nick, Client *client, int flags)
 	hashv = hash_watch_nick_name(nick);
 	
 	/* Find the right nick (header) in the bucket, or NULL... */
-	if ((watch = (Watch *)watchTable[hashv]))
+	if ((watch = watchTable[hashv]))
 		while (watch && mycmp(watch->nick, nick))
 		 watch = watch->hnext;
 	
 	/* If found NULL (no header for this nick), make one... */
 	if (!watch) {
-		watch = (Watch *)safe_alloc(sizeof(Watch)+strlen(nick));
+		watch = safe_alloc(sizeof(Watch)+strlen(nick));
 		watch->lasttime = timeofday;
 		strcpy(watch->nick, nick);
 		
@@ -203,7 +213,7 @@ int _watch_check(Client *client, int event, int (*watch_notify)(Client *client, 
 	hashv = hash_watch_nick_name(client->name);
 	
 	/* Find the right header in this bucket */
-	if ((watch = (Watch *)watchTable[hashv]))
+	if ((watch = watchTable[hashv]))
 		while (watch && mycmp(watch->nick, client->name))
 		 watch = watch->hnext;
 	if (!watch)
@@ -231,7 +241,7 @@ Watch *_watch_get(char *nick)
 	
 	hashv = hash_watch_nick_name(nick);
 	
-	if ((watch = (Watch *)watchTable[hashv]))
+	if ((watch = watchTable[hashv]))
 		while (watch && mycmp(watch->nick, nick))
 		 watch = watch->hnext;
 	
diff --git a/src/modules/webirc.c b/src/modules/webirc.c
@@ -336,6 +336,7 @@ ConfigItem_webirc *find_webirc(Client *client, const char *password, WEBIRCType 
 /* Does the CGI:IRC host spoofing work */
 void dowebirc(Client *client, const char *ip, const char *host, const char *options)
 {
+	char oldip[64];
 	char scratch[64];
 
 	if (IsWEBIRC(client))
@@ -357,6 +358,7 @@ void dowebirc(Client *client, const char *ip, const char *host, const char *opti
 	}
 
 	/* STEP 2: Update GetIP() */
+	strlcpy(oldip, client->ip, sizeof(oldip));
 	safe_strdup(client->ip, ip);
 		
 	/* STEP 3: Update client->local->hostp */
@@ -397,15 +399,7 @@ void dowebirc(Client *client, const char *ip, const char *host, const char *opti
 		}
 	}
 
-	/* blacklist_start_check() */
-	if (RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK] != NULL)
-		RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK]->func.intfunc(client);
-
-	/* Check (g)zlines right now; these are normally checked upon accept(),
-	 * but since we know the IP only now after PASS/WEBIRC, we have to check
-	 * here again...
-	 */
-	check_banned(client, 0);
+	RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
 }
 
 /* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
@@ -686,6 +686,9 @@ int websocket_handshake_valid(Client *client)
 	}
 	if (WSU(client)->forwarded)
 	{
+		struct HTTPForwardedHeader *forwarded;
+		char oldip[64];
+
 		/* check for source ip */
 		if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
 		{
@@ -694,7 +697,6 @@ int websocket_handshake_valid(Client *client)
 			return 0;
 		}
 		/* parse the header */
-		struct HTTPForwardedHeader *forwarded;
 		forwarded = websocket_parse_forwarded_header(WSU(client)->forwarded);
 		/* check header values */
 		if (!is_valid_ip(forwarded->ip))
@@ -705,6 +707,7 @@ int websocket_handshake_valid(Client *client)
 		}
 		/* store data */
 		WSU(client)->secure = forwarded->secure;
+		strlcpy(oldip, client->ip, sizeof(oldip));
 		safe_strdup(client->ip, forwarded->ip);
 		/* Update client->local->hostp */
 		strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
@@ -733,15 +736,7 @@ int websocket_handshake_valid(Client *client)
 				/* Race condition detected, DNS has been done, continue with auth */
 			}
 		}
-		/* blacklist_start_check() */
-		if (RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK] != NULL)
-			RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK]->func.intfunc(client);
-
-		/* Check (g)zlines right now; these are normally checked upon accept(),
-		 * but since we know the IP only now after PASS/WEBIRC, we have to check
-		 * here again...
-		 */
-		check_banned(client, 0);
+		RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
 	}
 	return 1;
 }
diff --git a/src/modules/whox.c b/src/modules/whox.c
@@ -46,6 +46,7 @@ ModuleHeader MOD_HEADER
 #define WMATCH_ACCOUNT	0x0040
 #define WMATCH_IP	0x0080
 #define WMATCH_MODES	0x0100
+#define WMATCH_CONTIME	0x0200
 
 #define RPL_WHOSPCRPL	354
 
@@ -69,6 +70,8 @@ struct who_format
 	const char *querytype;
 	int show_realhost;
 	int show_ip;
+	time_t contimemin;
+	time_t contimemax;
 };
 
 /* Global variables */
@@ -229,6 +232,7 @@ CMD_FUNC(cmd_whox)
 				case 's': fmt.matchsel |= WMATCH_SERVER; continue;
 				case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
 				case 'm': fmt.matchsel |= WMATCH_MODES; continue;
+				case 't': fmt.matchsel |= WMATCH_CONTIME; continue;
 				case 'R':
 					if (IsOper(client))
 						fmt.show_realhost = 1;
@@ -336,6 +340,28 @@ CMD_FUNC(cmd_whox)
 		}
 	}
 
+	/* match connect time */
+	if (fmt.matchsel & WMATCH_CONTIME)
+	{
+		char *s = mask;
+		time_t currenttime = TStime();
+
+		fmt.contimemin = 0;
+		fmt.contimemax = 0;
+
+		switch (*s)
+		{
+			case '<':
+				if (*s++)
+					fmt.contimemin = currenttime - config_checkval(s, CFG_TIME);
+				break;
+			case '>':
+				if (*s++)
+					fmt.contimemax = currenttime - config_checkval(s, CFG_TIME);
+				break;
+		}
+	}
+
 	/* '/who #some_channel' */
 	if (IsChannelName(mask))
 	{
@@ -449,6 +475,16 @@ static int do_match(Client *client, Client *acptr, char *mask, struct who_format
 		}
 	}
 
+	/* match connect time */
+	if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax))
+	{
+		if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin))
+			return 1;
+
+		if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax))
+			return 1;
+	}
+
 	return 0;
 }
 
diff --git a/src/parse.c b/src/parse.c
@@ -104,7 +104,9 @@ void parse_client_queued(Client *client)
 		return; /* we delay processing of data until identd has replied */
 
 	if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
-	    !IsNoHandshakeDelay(client) && (TStime() - client->local->creationtime < iConf.handshake_delay))
+	    !IsNoHandshakeDelay(client) &&
+	    !IsControl(client) &&
+	    (TStime() - client->local->creationtime < iConf.handshake_delay))
 	{
 		return; /* we delay processing of data until set::handshake-delay is reached */
 	}
@@ -368,6 +370,8 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_
 			flags |= CMD_VIRUS;
 		if (IsOper(from))
 			flags |= CMD_OPER;
+		if (IsControl(from))
+			flags |= CMD_CONTROL;
 		cmptr = find_command(ch, flags);
 		if (!cmptr || !(cmptr->flags & CMD_NOLAG))
 		{
@@ -376,6 +380,12 @@ static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_
 		}
 		if (!cmptr)
 		{
+			if (IsControl(from))
+			{
+				sendto_one(from, NULL, "ERROR UNKNOWN_COMMAND: %s", ch);
+				sendto_one(from, NULL, "END 1");
+				return;
+			}
 			/* Don't send error messages in response to NOTICEs
 			 * in pre-connection state.
 			 */
diff --git a/src/proc_io_client.c b/src/proc_io_client.c
@@ -0,0 +1,199 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_client.c
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief Inter-process I/O
+ */
+#include "unrealircd.h"
+
+int procio_client_connect(const char *file)
+{
+	int fd;
+	struct sockaddr_un addr;
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (fd < 0)
+	{
+#ifdef _WIN32
+		fprintf(stderr, "Your Windows version does not support UNIX sockets, "
+		                "so cannot communicate to UnrealIRCd.\n"
+		                "Windows 10 version 1803 (April 2018) or later is needed.\n");
+#else
+		fprintf(stderr, "Cannot communicate to UnrealIRCd: %s\n"
+		                "Perhaps your operating system does not support UNIX Sockets?\n",
+				strerror(ERRNO));
+#endif
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strlcpy(addr.sun_path, file, sizeof(addr.sun_path));
+
+	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+	{
+		fprintf(stderr, "Could not connect to '%s': %s\n",
+			CONTROLFILE, strerror(errno));
+		fprintf(stderr, "The IRC server does not appear to be running.\n");
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+int procio_send(int fd, const char *command)
+{
+	char buf[512];
+	int n;
+	snprintf(buf, sizeof(buf), "%s\r\n", command);
+	n = strlen(buf);
+	if (send(fd, buf, n, 0) != n)
+		return 0;
+	return 1;
+}
+
+const char *recolor_logs(const char *str)
+{
+	static char retbuf[2048];
+	char buf[2048], *p;
+	const char *color = NULL;
+
+	strlcpy(buf, str, sizeof(buf));
+	p = strchr(buf, ' ');
+	if ((*str != '[') || !p)
+		return str;
+	*p++ = '\0';
+
+	if (!strcmp(buf, "[debug]"))
+		color = log_level_terminal_color(ULOG_DEBUG);
+	else if (!strcmp(buf, "[info]"))
+		color = log_level_terminal_color(ULOG_INFO);
+	else if (!strcmp(buf, "[warning]"))
+		color = log_level_terminal_color(ULOG_WARNING);
+	else if (!strcmp(buf, "[error]"))
+		color = log_level_terminal_color(ULOG_ERROR);
+	else if (!strcmp(buf, "[fatal]"))
+		color = log_level_terminal_color(ULOG_FATAL);
+	else
+		color = log_level_terminal_color(ULOG_INVALID);
+
+	snprintf(retbuf, sizeof(retbuf), "%s%s%s %s",
+	         color, buf, TERMINAL_COLOR_RESET, p);
+	return retbuf;
+}
+
+const char *recolor_split(const char *str)
+{
+	static char retbuf[2048];
+	char buf[2048], *p;
+	const char *color = NULL;
+
+	strlcpy(buf, str, sizeof(buf));
+	p = strchr(buf, ' ');
+	if (!p)
+		return str;
+	*p++ = '\0';
+
+	snprintf(retbuf, sizeof(retbuf), "%s%s %s%s%s",
+	         "\033[92m", buf,
+	         "\033[93m", p,
+	         TERMINAL_COLOR_RESET);
+	return retbuf;
+}
+
+int procio_client(const char *command, int auto_color_logs)
+{
+	int fd;
+	char buf[READBUFSIZE];
+	int n;
+	dbuf queue;
+
+	if (auto_color_logs && !terminal_supports_color())
+		auto_color_logs = 0;
+
+	fd = procio_client_connect(CONTROLFILE);
+	if (fd < 0)
+		return -1;
+
+	/* Expect the welcome message */
+	memset(buf, 0, sizeof(buf));
+	n = recv(fd, buf, sizeof(buf), 0);
+	if ((n < 0) || strncmp(buf, "READY", 4))
+	{
+		fprintf(stderr, "Error while communicating to IRCd via '%s': %s\n"
+		                "Maybe the IRC server is not running?\n",
+		                CONTROLFILE, strerror(errno));
+		close(fd);
+		return -1;
+	}
+
+	if (!procio_send(fd, command))
+	{
+		fprintf(stderr, "Error while sending command to IRCd via '%s'. Strange!\n",
+		                CONTROLFILE);
+		close(fd);
+		return -1;
+	}
+
+	*buf = '\0';
+	dbuf_queue_init(&queue);
+	while(1)
+	{
+		n = recv(fd, buf, sizeof(buf)-1, 0);
+		if (n <= 0)
+			break;
+		buf[n] = '\0'; /* terminate the string */
+		dbuf_put(&queue, buf, n);
+
+		/* And try to read all complete lines: */
+		do
+		{
+			n = dbuf_getmsg(&queue, buf);
+			if (n > 0)
+			{
+				if (!strncmp(buf, "REPLY ", 6))
+				{
+					char *reply = buf+6;
+					if (auto_color_logs == 0)
+						printf("%s\n", reply);
+					else if (auto_color_logs == 1)
+						printf("%s\n", recolor_logs(reply));
+					else
+						printf("%s\n", recolor_split(reply));
+				} else
+				if (!strncmp(buf, "END ", 4))
+				{
+					int exitcode = atoi(buf+4);
+					close(fd);
+					return exitcode;
+				}
+			}
+		} while(n > 0);
+	}
+
+	/* IRCd hung up without saying goodbye, possibly problematic,
+	 * or at least we cannot determine, so exit with status 66.
+	 */
+	close(fd);
+	return 66;
+}
diff --git a/src/proc_io_server.c b/src/proc_io_server.c
@@ -0,0 +1,170 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/proc_io_server.c
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief Inter-process I/O
+ */
+#include "unrealircd.h"
+#include <ares.h>
+
+CMD_FUNC(procio_status);
+CMD_FUNC(procio_modules);
+CMD_FUNC(procio_rehash);
+CMD_FUNC(procio_exit);
+CMD_FUNC(procio_help);
+
+/** Create the unrealircd.ctl socket (server-side) */
+void add_proc_io_server(void)
+{
+	ConfigItem_listen *listener;
+
+#ifdef _WIN32
+	/* Ignore silently on Windows versions older than W10 build 17061 */
+	if (!unix_sockets_capable())
+		return;
+#endif
+	listener = safe_alloc(sizeof(ConfigItem_listen));
+	safe_strdup(listener->file, CONTROLFILE);
+	listener->socket_type = SOCKET_TYPE_UNIX;
+	listener->options = LISTENER_CONTROL;
+	listener->fd = -1;
+	AddListItem(listener, conf_listen);
+	if (add_listener(listener) == -1)
+		exit(-1);
+	CommandAdd(NULL, "STATUS", procio_status, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "MODULES", procio_modules, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL);
+	CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL);
+}
+
+/** Start of "control channel" client handshake - this is minimal
+ * @param client	The client
+ */
+void start_of_control_client_handshake(Client *client)
+{
+	sendto_one(client, NULL, "READY %s %s", me.name, version);
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+CMD_FUNC(procio_status)
+{
+	sendto_one(client, NULL, "REPLY servername %s", me.name);
+	sendto_one(client, NULL, "REPLY unrealircd_version %s", version);
+	sendto_one(client, NULL, "REPLY libssl_version %s", SSLeay_version(SSLEAY_VERSION));
+	sendto_one(client, NULL, "REPLY libsodium_version %s", sodium_version_string());
+#ifdef USE_LIBCURL
+	sendto_one(client, NULL, "REPLY libcurl_version %s", curl_version());
+#endif
+	sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL));
+	sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version());
+	sendto_one(client, NULL, "REPLY global_clients %ld", (long)irccounts.clients);
+	sendto_one(client, NULL, "REPLY local_clients %ld", (long)irccounts.me_clients);
+	sendto_one(client, NULL, "REPLY operators %ld", (long)irccounts.operators);
+	sendto_one(client, NULL, "REPLY servers %ld", (long)irccounts.servers);
+	sendto_one(client, NULL, "REPLY channels %ld", (long)irccounts.channels);
+	sendto_one(client, NULL, "END 0");
+}
+
+extern MODVAR Module *Modules;
+CMD_FUNC(procio_modules)
+{
+	char tmp[1024];
+	Module *m;
+
+	for (m = Modules; m; m = m->next)
+	{
+		tmp[0] = '\0';
+		if (m->flags & MODFLAG_DELAYED)
+			strlcat(tmp, "[Unloading] ", sizeof(tmp));
+		if (m->options & MOD_OPT_PERM_RELOADABLE)
+			strlcat(tmp, "[PERM-BUT-RELOADABLE] ", sizeof(tmp));
+		if (m->options & MOD_OPT_PERM)
+			strlcat(tmp, "[PERM] ", sizeof(tmp));
+		if (!(m->options & MOD_OPT_OFFICIAL))
+			strlcat(tmp, "[3RD] ", sizeof(tmp));
+		sendto_one(client, NULL, "REPLY %s %s - %s - by %s %s",
+		           m->header->name,
+		           m->header->version,
+		           m->header->description,
+		           m->header->author,
+		           tmp);
+	}
+	sendto_one(client, NULL, "END 0");
+}
+
+CMD_FUNC(procio_rehash)
+{
+	if (loop.rehashing)
+	{
+		sendto_one(client, NULL, "REPLY ERROR: A rehash is already in progress");
+		sendto_one(client, NULL, "END 1");
+		return;
+	}
+	
+
+	if (parv[1] && !strcmp(parv[1], "-tls"))
+	{
+		int ret;
+		SetMonitorRehash(client);
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD_TLS", NULL, "Reloading all TLS related data (./unrealircd reloadtls)");
+		ret = reinit_tls();
+		sendto_one(client, NULL, "END %d", ret == 0 ? -1 : 0);
+		ClearMonitorRehash(client);
+	} else {
+		SetMonitorRehash(client);
+		request_rehash(client);
+		/* completion will go via procio_post_rehash() */
+	}
+}
+
+CMD_FUNC(procio_exit)
+{
+	sendto_one(client, NULL, "END 0");
+	exit_client(client, NULL, "");
+}
+
+CMD_FUNC(procio_help)
+{
+	sendto_one(client, NULL, "REPLY Commands available:");
+	sendto_one(client, NULL, "REPLY EXIT");
+	sendto_one(client, NULL, "REPLY HELP");
+	sendto_one(client, NULL, "REPLY REHASH");
+	sendto_one(client, NULL, "REPLY STATUS");
+	sendto_one(client, NULL, "REPLY MODULES");
+	sendto_one(client, NULL, "END 0");
+}
+
+/** Called upon REHASH completion (with or without failure) */
+void procio_post_rehash(int failure)
+{
+	Client *client;
+
+	list_for_each_entry(client, &control_list, lclient_node)
+	{
+		if (IsMonitorRehash(client))
+		{
+			sendto_one(client, NULL, "END %d", failure);
+			ClearMonitorRehash(client);
+		}
+	}
+}
diff --git a/src/send.c b/src/send.c
@@ -218,7 +218,7 @@ void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
 #endif
-	ircvsnprintf(sendbuf, sizeof(sendbuf), pattern, vl);
+	ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
 #if defined(__GNUC__)
 #pragma GCC diagnostic pop
 #endif
@@ -229,7 +229,7 @@ void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
 		sendbufto_one(to, sendbuf, 0);
 	} else {
 		/* Message tags need to be prepended */
-		snprintf(sendbuf2, sizeof(sendbuf2), "@%s %s", mtags_str, sendbuf);
+		snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
 		sendbufto_one(to, sendbuf2, 0);
 	}
 }
@@ -339,10 +339,10 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 
 	if (IsMe(to))
 	{
-		char tmp_msg[500], *p;
+		char tmp_msg[500];
 
-		p = strchr(msg, '\r');
-		if (p) *p = '\0';
+		strlcpy(tmp_msg, msg, sizeof(tmp_msg));
+		stripcrlf(tmp_msg);
 		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to,
 			   "Trying to send data to myself: $buf",
 			   log_data_string("buf", tmp_msg));
@@ -395,7 +395,10 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 	 * a bad idea, CPU-wise. So now we just mark the client indicating
 	 * that there is data to send.
 	 */
-	mark_data_to_send(to);
+	if (IsControl(to))
+		send_queued(to); /* send this one ASAP */
+	else
+		mark_data_to_send(to);
 }
 
 /** A single function to send data to a channel.
diff --git a/src/serv.c b/src/serv.c
@@ -716,62 +716,77 @@ CMD_FUNC(cmd_restart)
 /** Send short message of the day to the client */
 void short_motd(Client *client)
 {
-       ConfigItem_tld *tld;
-       MOTDFile *themotd;
-       MOTDLine *motdline;
-       struct tm *tm;
-       char is_short;
+	ConfigItem_tld *tld;
+	MOTDFile *themotd;
+	MOTDLine *motdline;
+	struct tm *tm;
+	char is_short;
 
-       tm = NULL;
-       is_short = 1;
+	tm = NULL;
+	is_short = 1;
 
-       tld = find_tld(client);
+	tld = find_tld(client);
 
-       /*
+	/*
 	* Try different sources of short MOTDs, falling back to the
 	* long MOTD.
-       */
-       themotd = &smotd;
-       if (tld && tld->smotd.lines)
-	       themotd = &tld->smotd;
-
-       /* try long MOTDs */
-       if (!themotd->lines)
-       {
-	       is_short = 0;
-	       if (tld && tld->motd.lines)
-		       themotd = &tld->motd;
-	       else
-		       themotd = &motd;
-       }
-
-       if (!themotd->lines)
-       {
-               sendnumeric(client, ERR_NOMOTD);
-               return;
-       }
-       if (themotd->last_modified.tm_year)
-       {
-	       tm = &themotd->last_modified; /* for readability */
-               sendnumeric(client, RPL_MOTDSTART, me.name);
-               sendnumericfmt(client, RPL_MOTD, ":- %d/%d/%d %d:%02d", tm->tm_mday, tm->tm_mon + 1,
-                   1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
-       }
-       if (is_short)
-       {
-               sendnumeric(client, RPL_MOTD, "This is the short MOTD. To view the complete MOTD type /motd");
-               sendnumeric(client, RPL_MOTD, "");
-       }
-
-       motdline = NULL;
-       if (themotd)
-	       motdline = themotd->lines;
-       while (motdline)
-       {
-               sendnumeric(client, RPL_MOTD, motdline->line);
-               motdline = motdline->next;
-       }
-       sendnumeric(client, RPL_ENDOFMOTD);
+	*/
+	themotd = &smotd;
+	if (tld && tld->smotd.lines)
+		themotd = &tld->smotd;
+
+	/* try long MOTDs */
+	if (!themotd->lines)
+	{
+		is_short = 0;
+		if (tld && tld->motd.lines)
+			themotd = &tld->motd;
+		else
+			themotd = &motd;
+	}
+
+	if (!themotd->lines)
+	{
+		sendnumeric(client, ERR_NOMOTD);
+		return;
+	}
+	if (themotd->last_modified.tm_year)
+	{
+		tm = &themotd->last_modified; /* for readability */
+		sendnumeric(client, RPL_MOTDSTART, me.name);
+		sendnumericfmt(client, RPL_MOTD, ":- %d/%d/%d %d:%02d", tm->tm_mday, tm->tm_mon + 1,
+		               1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
+	}
+	if (is_short)
+	{
+		sendnumeric(client, RPL_MOTD, "This is the short MOTD. To view the complete MOTD type /motd");
+		sendnumeric(client, RPL_MOTD, "");
+	}
+
+	motdline = NULL;
+	if (themotd)
+		motdline = themotd->lines;
+	while (motdline)
+	{
+		sendnumeric(client, RPL_MOTD, motdline->line);
+		motdline = motdline->next;
+	}
+
+	if (!is_short)
+	{
+		/* If the admin does not use a short MOTD then we append the SVSMOTD here...
+		 * If we did show a short motd then we don't append SVSMOTD,
+		 * since they want to keep it short.
+		 */
+		motdline = svsmotd.lines;
+		while (motdline)
+		{
+			sendnumeric(client, RPL_MOTD, motdline->line);
+			motdline = motdline->next;
+		}
+	}
+
+	sendnumeric(client, RPL_ENDOFMOTD);
 }
 
 /** Read motd-like file, used for rules/motd/botmotd/opermotd/etc.
diff --git a/src/socket.c b/src/socket.c
@@ -32,7 +32,7 @@ int OpenFiles = 0;    /* GLOBAL - number of files currently open */
 int readcalls = 0;
 
 void completed_connection(int, int, void *);
-void set_sock_opts(int, Client *, int);
+void set_sock_opts(int, Client *, SocketType);
 void set_ipv6_opts(int);
 void close_listener(ConfigItem_listen *listener);
 static char readbuf[BUFSIZE];
@@ -41,6 +41,7 @@ extern char *version;
 MODVAR time_t last_allinuse = 0;
 
 void start_of_normal_client_handshake(Client *client);
+extern void start_of_control_client_handshake(Client *client);
 void proceed_normal_client_handshake(Client *client, struct hostent *he);
 
 /** Close all connections - only used when we terminate the server (eg: /DIE or SIGTERM) */
@@ -72,6 +73,15 @@ void close_connections(void)
 		}
 	}
 
+	list_for_each_entry(client, &control_list, lclient_node)
+	{
+		if (client->local->fd >= 0)
+		{
+			fd_close(client->local->fd);
+			client->local->fd = -2;
+		}
+	}
+
 	close_unbound_listeners();
 
 	OpenFiles = 0;
@@ -101,10 +111,17 @@ static void listener_accept(int listener_fd, int revents, void *data)
 			 * Of course the underlying cause of this issue should be investigated, as this
 			 * is very much a workaround.
 			 */
-			unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
-				   log_data_socket_error(listener->fd),
-				   log_data_string("listen_ip", listener->ip),
-				   log_data_integer("listen_port", listener->port));
+			if (listener->file)
+			{
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on file $file: $socket_error",
+					   log_data_socket_error(listener->fd),
+					   log_data_string("file", listener->file));
+			} else {
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
+					   log_data_socket_error(listener->fd),
+					   log_data_string("listen_ip", listener->ip),
+					   log_data_integer("listen_port", listener->port));
+			}
 			close_listener(listener);
 			start_listeners();
 		}
@@ -113,45 +130,67 @@ static void listener_accept(int listener_fd, int revents, void *data)
 
 	ircstats.is_ac++;
 
-	set_sock_opts(cli_fd, NULL, listener->ipv6);
+	set_sock_opts(cli_fd, NULL, listener->socket_type);
 
-	if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients))
+	/* Allow connections to the control socket, even if maxclients is reached */
+	if (listener->options & LISTENER_CONTROL)
 	{
-		ircstats.is_ref++;
-		if (last_allinuse < TStime() - 15)
+		/* ... but not unlimited ;) */
+		if ((++OpenFiles >= maxclients+(CLIENTS_RESERVE/2)) || (cli_fd >= maxclients+(CLIENTS_RESERVE/2)))
 		{
-			unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
-				   log_data_string("listen_ip", listener->ip),
-				   log_data_integer("listen_port", listener->port));
-			last_allinuse = TStime();
+			ircstats.is_ref++;
+			if (last_allinuse < TStime() - 15)
+			{
+				unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
+					   log_data_string("file", listener->file));
+				last_allinuse = TStime();
+			}
+			fd_close(cli_fd);
+			--OpenFiles;
+			return;
 		}
+	} else
+	{
+		if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients))
+		{
+			ircstats.is_ref++;
+			if (last_allinuse < TStime() - 15)
+			{
+				if (listener->file)
+				{
+					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
+						   log_data_string("file", listener->file));
+				} else {
+					unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
+						   log_data_string("listen_ip", listener->ip),
+						   log_data_integer("listen_port", listener->port));
+				}
+				last_allinuse = TStime();
+			}
 
-		(void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0);
+			(void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0);
 
-		fd_close(cli_fd);
-		--OpenFiles;
-		return;
+			fd_close(cli_fd);
+			--OpenFiles;
+			return;
+		}
 	}
 
 	/* add_connection() may fail. we just don't care. */
 	add_connection(listener, cli_fd);
 }
 
-/** Create a listener port.
- * @param listener	The listen { } block configuration
- * @param ip		IP address to bind on
- * @param port		Port to bind on
- * @param ipv6		IPv6 (1) or IPv4 (0)
- * @returns 0 on success and <0 on error. Yeah, confusing.
- */
-int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
+int unreal_listen_inet(ConfigItem_listen *listener)
 {
+	const char *ip = listener->ip;
+	int port = listener->port;
+
 	if (BadPtr(ip))
 		ip = "*";
-	
+
 	if (*ip == '*')
 	{
-		if (ipv6)
+		if (listener->socket_type == SOCKET_TYPE_IPV6)
 			ip = "::";
 		else
 			ip = "0.0.0.0";
@@ -160,11 +199,11 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 	/* At first, open a new socket */
 	if (listener->fd >= 0)
 		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
-	
+
 	if (port == 0)
 		abort(); /* Impossible as well, right? */
 
-	listener->fd = fd_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
+	listener->fd = fd_socket(listener->socket_type == SOCKET_TYPE_IPV6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
 	if (listener->fd < 0)
 	{
 		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
@@ -187,9 +226,9 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 		return -1;
 	}
 
-	set_sock_opts(listener->fd, NULL, ipv6);
+	set_sock_opts(listener->fd, NULL, listener->socket_type);
 
-	if (!unreal_bind(listener->fd, ip, port, ipv6))
+	if (!unreal_bind(listener->fd, ip, port, listener->socket_type))
 	{
 		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
 		           "Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
@@ -240,23 +279,95 @@ int unreal_listen(ConfigItem_listen *listener, char *ip, int port, int ipv6)
 	return 0;
 }
 
+int unreal_listen_unix(ConfigItem_listen *listener)
+{
+	if (listener->socket_type != SOCKET_TYPE_UNIX)
+		abort(); /* "impossible" */
+
+	/* At first, open a new socket */
+	if (listener->fd >= 0)
+		abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
+
+	listener->fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Listener socket (UNIX)");
+	if (listener->fd < 0)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
+		           "Could not create UNIX domain socket for $file: $socket_error",
+			   log_data_socket_error(-1),
+			   log_data_string("file", listener->file));
+		return -1;
+	}
+
+	if (++OpenFiles >= maxclients)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
+		           "Could not create UNIX domain socket for $file: all connections in use",
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	set_sock_opts(listener->fd, NULL, listener->socket_type);
+
+	if (!unreal_bind(listener->fd, listener->file, 0, SOCKET_TYPE_UNIX))
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
+		           "Could not listen on UNIX domain socket $file: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	if (listen(listener->fd, LISTEN_SIZE) < 0)
+	{
+		unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
+		           "Could not listen on UNIX domain socket $file: $socket_error",
+		           log_data_socket_error(listener->fd),
+		           log_data_string("file", listener->file));
+		fd_close(listener->fd);
+		listener->fd = -1;
+		--OpenFiles;
+		return -1;
+	}
+
+	fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
+
+	return 0;
+}
+
+/** Create a listener port.
+ * @param listener	The listen { } block configuration
+ * @returns 0 on success and <0 on error. Yeah, confusing.
+ */
+int unreal_listen(ConfigItem_listen *listener)
+{
+	if ((listener->socket_type == SOCKET_TYPE_IPV4) || (listener->socket_type == SOCKET_TYPE_IPV6))
+		return unreal_listen_inet(listener);
+	return unreal_listen_unix(listener);
+}
+
 /** Activate a listen { } block */
-int add_listener(ConfigItem_listen *conf)
+int add_listener(ConfigItem_listen *listener)
 {
-	if (unreal_listen(conf, conf->ip, conf->port, conf->ipv6))
+	if (unreal_listen(listener))
 	{
 		/* Error is already handled upstream */
-		conf->fd = -2;
+		listener->fd = -2;
 	}
 
-	if (conf->fd >= 0)
+	if (listener->fd >= 0)
 	{
-		conf->options |= LISTENER_BOUND;
+		listener->options |= LISTENER_BOUND;
 		return 1;
 	}
 	else
 	{
-		conf->fd = -1;
+		listener->fd = -1;
 		return -1;
 	}
 }
@@ -433,7 +544,7 @@ void consider_ident_lookup(Client *client)
 	char buf[BUFSIZE];
 
 	/* If ident checking is disabled or it's an outgoing connect, then no ident check */
-	if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)))
+	if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)) || IsUnixSocket(client))
 	{
 		ClearIdentLookupSent(client);
 		ClearIdentLookup(client);
@@ -560,37 +671,38 @@ void set_socket_buffers(int fd, int rcvbuf, int sndbuf)
 }
 
 /** Set the appropriate socket options */
-void set_sock_opts(int fd, Client *client, int ipv6)
+void set_sock_opts(int fd, Client *client, SocketType socket_type)
 {
 	int opt;
 
-	if (ipv6)
+	if (socket_type == SOCKET_TYPE_IPV6)
 		set_ipv6_opts(fd);
 
-#ifdef SO_REUSEADDR
-	opt = 1;
-	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
+	if ((socket_type == SOCKET_TYPE_IPV4) || (socket_type == SOCKET_TYPE_IPV6))
 	{
-		unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-		           "Could not setsockopt(SO_REUSEADDR): $socket_error",
-			   log_data_socket_error(-1));
-	}
+#ifdef SO_REUSEADDR
+		opt = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not setsockopt(SO_REUSEADDR): $socket_error",
+				   log_data_socket_error(-1));
+		}
 #endif
 
 #if defined(SO_USELOOPBACK) && !defined(_WIN32)
-	opt = 1;
-	if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
-	{
-		unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
-		           "Could not setsockopt(SO_USELOOPBACK): $socket_error",
-			   log_data_socket_error(-1));
-	}
+		opt = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
+		{
+			unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
+				   "Could not setsockopt(SO_USELOOPBACK): $socket_error",
+				   log_data_socket_error(-1));
+		}
 #endif
 
-	/* Previously we also called set_socket_buffers() to set some
-	 * specific buffer limits. This is no longer needed on modern OS's.
-	 * Setting it explicitly actually slows things down.
-	 */
+	}
+
+	/* The following code applies to all socket types: IPv4, IPv6, UNIX domain sockets */
 
 	/* Set to non blocking: */
 #if !defined(_WIN32)
@@ -641,7 +753,7 @@ int is_loopback_ip(char *ip)
 
 	for (e = conf_listen; e; e = e->next)
 	{
-		if ((e->options & LISTENER_BOUND) && !strcmp(ip, e->ip))
+		if ((e->options & LISTENER_BOUND) && e->ip && !strcmp(ip, e->ip))
 			return 1;
 	}
 	return 0;
@@ -718,15 +830,15 @@ Client *add_connection(ConfigItem_listen *listener, int fd)
 	Client *client;
 	const char *ip;
 	int port = 0;
-	
+
 	client = make_client(NULL, &me);
+	client->local->socket_type = listener->socket_type;
 
-	/* If listener is IPv6 then mark client (client) as IPv6 */
-	if (listener->ipv6)
-		SetIPV6(client);
+	if (listener->socket_type == SOCKET_TYPE_UNIX)
+		ip = "127.0.0.1";
+	else
+		ip = getpeerip(client, fd, &port);
 
-	ip = getpeerip(client, fd, &port);
-	
 	if (!ip)
 	{
 		/* On Linux 2.4 and FreeBSD the socket may just have been disconnected
@@ -762,29 +874,38 @@ refuse_client:
 		SetLocalhost(client);
 	}
 
-	/* Check set::max-unknown-connections-per-ip */
-	if (check_too_many_unknown_connections(client))
+	if (!(listener->options & LISTENER_CONTROL))
 	{
-		ircsnprintf(zlinebuf, sizeof(zlinebuf),
-		            "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)\r\n",
-		            client->ip);
-		(void)send(fd, zlinebuf, strlen(zlinebuf), 0);
-		goto refuse_client;
-	}
+		/* Check set::max-unknown-connections-per-ip */
+		if (check_too_many_unknown_connections(client))
+		{
+			ircsnprintf(zlinebuf, sizeof(zlinebuf),
+				    "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)\r\n",
+				    client->ip);
+			(void)send(fd, zlinebuf, strlen(zlinebuf), 0);
+			goto refuse_client;
+		}
 
-	/* Check (G)Z-Lines and set::anti-flood::connect-flood */
-	if (check_banned(client, NO_EXIT_CLIENT))
-		goto refuse_client;
+		/* Check (G)Z-Lines and set::anti-flood::connect-flood */
+		if (check_banned(client, NO_EXIT_CLIENT))
+			goto refuse_client;
+	}
 
 	client->local->listener = listener;
 	if (client->local->listener != NULL)
 		client->local->listener->clients++;
 	add_client_to_list(client);
 
-	irccounts.unknown++;
-	client->status = CLIENT_STATUS_UNKNOWN;
-
-	list_add(&client->lclient_node, &unknown_list);
+	if (!(listener->options & LISTENER_CONTROL))
+	{
+		/* IRC: unknown connection */
+		irccounts.unknown++;
+		client->status = CLIENT_STATUS_UNKNOWN;
+		list_add(&client->lclient_node, &unknown_list);
+	} else {
+		client->status = CLIENT_STATUS_CONTROL;
+		list_add(&client->lclient_node, &control_list);
+	}
 
 	if ((listener->options & LISTENER_TLS) && ctx_server)
 	{
@@ -809,7 +930,9 @@ refuse_client:
 				goto refuse_client;
 			}
 		}
-	}
+	} else
+	if (listener->options & LISTENER_CONTROL)
+		start_of_control_client_handshake(client);
 	else
 		start_of_normal_client_handshake(client);
 	return client;
@@ -828,7 +951,7 @@ void start_of_normal_client_handshake(Client *client)
 
 	RunHook(HOOKTYPE_HANDSHAKE, client);
 
-	if (!DONT_RESOLVE)
+	if (!DONT_RESOLVE && !IsUnixSocket(client))
 	{
 		if (should_show_connect_info(client))
 			sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_DNS);
@@ -1027,6 +1150,20 @@ void process_clients(void)
 			}
 		}
 	} while(&client->lclient_node != &unknown_list);
+
+	do {
+		list_for_each_entry(client, &control_list, lclient_node)
+		{
+			if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
+			{
+				parse_client_queued(client);
+				if (IsDead(client))
+					break;
+			}
+		}
+	} while(&client->lclient_node != &control_list);
+
+
 }
 
 /** Check if 'ip' is a valid IP address, and if so what type.
@@ -1038,16 +1175,16 @@ void process_clients(void)
 int is_valid_ip(const char *ip)
 {
 	char scratch[64];
-	
+
 	if (BadPtr(ip))
 		return 0;
 
 	if (inet_pton(AF_INET, ip, scratch) == 1)
 		return 4; /* IPv4 */
-	
+
 	if (inet_pton(AF_INET6, ip, scratch) == 1)
 		return 6; /* IPv6 */
-	
+
 	return 0; /* not an IP address */
 }
 
@@ -1060,11 +1197,21 @@ int ipv6_capable(void)
 	int s = socket(AF_INET6, SOCK_STREAM, 0);
 	if (s < 0)
 		return 0; /* NO ipv6 */
-	
+
 	CLOSE_SOCK(s);
 	return 1; /* YES */
 }
 
+/** Return 1 if UNIX sockets of type SOCK_STREAM are supported, and 0 otherwise */
+int unix_sockets_capable(void)
+{
+	int fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Testing UNIX socket");
+	if (fd < 0)
+		return 0;
+	fd_close(fd);
+	return 1;
+}
+
 /** Attempt to deliver data to a client.
  * This function is only called from send_queued() and will deal
  * with sending to the TLS or plaintext connection.
@@ -1156,7 +1303,7 @@ int deliver_it(Client *client, char *str, int len, int *want_read)
 int unreal_connect(int fd, const char *ip, int port, int ipv6)
 {
 	int n;
-	
+
 	if (ipv6)
 	{
 		struct sockaddr_in6 server;
@@ -1182,16 +1329,26 @@ int unreal_connect(int fd, const char *ip, int port, int ipv6)
 	{
 		return 0; /* FATAL ERROR */
 	}
-	
+
 	return 1; /* SUCCESS (probably still in progress) */
 }
 
 /** Bind to an IP/port (port may be 0 for auto).
  * @returns 0 on failure, other on success.
  */
-int unreal_bind(int fd, const char *ip, int port, int ipv6)
+int unreal_bind(int fd, const char *ip, int port, SocketType socket_type)
 {
-	if (ipv6)
+	if (socket_type == SOCKET_TYPE_IPV4)
+	{
+		struct sockaddr_in server;
+		memset(&server, 0, sizeof(server));
+		server.sin_family = AF_INET;
+		server.sin_port = htons(port);
+		if (inet_pton(AF_INET, ip, &server.sin_addr.s_addr) != 1)
+			return 0;
+		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
+	}
+	else if (socket_type == SOCKET_TYPE_IPV6)
 	{
 		struct sockaddr_in6 server;
 		memset(&server, 0, sizeof(server));
@@ -1200,13 +1357,34 @@ int unreal_bind(int fd, const char *ip, int port, int ipv6)
 		if (inet_pton(AF_INET6, ip, &server.sin6_addr.s6_addr) != 1)
 			return 0;
 		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
-	} else {
-		struct sockaddr_in server;
+	} else
+	{
+		struct sockaddr_un server;
+		mode_t saved_umask;
+		int ret;
+
+		unlink(ip); /* (ignore errors) */
+
 		memset(&server, 0, sizeof(server));
-		server.sin_family = AF_INET;
-		server.sin_port = htons(port);
-		if (inet_pton(AF_INET, ip, &server.sin_addr.s_addr) != 1)
-			return 0;
-		return !bind(fd, (struct sockaddr *)&server, sizeof(server));
+		server.sun_family = AF_UNIX;
+		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
+		saved_umask = umask(077); // TODO: make this configurable
+		ret = !bind(fd, (struct sockaddr *)&server, sizeof(server));
+		umask(saved_umask);
+
+		return ret;
 	}
 }
+
+#ifdef _WIN32
+void init_winsock(void)
+{
+	WSADATA WSAData;
+	if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
+	{
+		MessageBox(NULL, "Unable to initialize WinSock", "UnrealIRCD Initalization Error", MB_OK);
+		fprintf(stderr, "Unable to initialize WinSock\n");
+		exit(1);
+	}
+}
+#endif
diff --git a/src/tls.c b/src/tls.c
@@ -512,7 +512,7 @@ int init_tls(void)
 
 /** Reinitialize TLS server and client contexts - after REHASH -tls
  */
-void reinit_tls(void)
+int reinit_tls(void)
 {
 	SSL_CTX *tmp;
 	ConfigItem_listen *listen;
@@ -524,7 +524,7 @@ void reinit_tls(void)
 	{
 		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 		           "TLS Reload failed. See previous errors.");
-		return;
+		return 0;
 	}
 	if (ctx_server)
 		SSL_CTX_free(ctx_server);
@@ -535,7 +535,7 @@ void reinit_tls(void)
 	{
 		unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 		           "TLS Reload failed at client context. See previous errors.");
-		return;
+		return 0;
 	}
 	if (ctx_client)
 		SSL_CTX_free(ctx_client);
@@ -551,7 +551,7 @@ void reinit_tls(void)
 			{
 				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 					   "TLS Reload failed at listen::tls-options. See previous errors.");
-				return;
+				return 0;
 			}
 			if (listen->ssl_ctx)
 				SSL_CTX_free(listen->ssl_ctx);
@@ -569,7 +569,7 @@ void reinit_tls(void)
 			{
 				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 					   "TLS Reload failed at sni::tls-options. See previous errors.");
-				return;
+				return 0;
 			}
 			if (sni->ssl_ctx)
 				SSL_CTX_free(sni->ssl_ctx);
@@ -588,13 +588,15 @@ void reinit_tls(void)
 				unreal_log(ULOG_ERROR, "config", "TLS_RELOAD_FAILED", NULL,
 					   "TLS Reload failed at link $servername due to outgoing::tls-options. See previous errors.",
 					   log_data_string("servername", link->servername));
-				return;
+				return 0;
 			}
 			if (link->ssl_ctx)
 				SSL_CTX_free(link->ssl_ctx);
 			link->ssl_ctx = tmp; /* activate */
 		}
 	}
+
+	return 1;
 }
 
 /** Set SSL connection as nonblocking */
diff --git a/src/unrealircdctl.c b/src/unrealircdctl.c
@@ -0,0 +1,266 @@
+/************************************************************************
+ *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/unrealircdctl
+ *   (c) 2022- Bram Matthys and The UnrealIRCd team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers. 
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/** @file
+ * @brief UnrealIRCd Control
+ */
+#include "unrealircd.h"
+
+#ifdef _WIN32
+ #define UNREALCMD "unrealircdctl"
+#else
+ #define UNREALCMD "./unrealircd"
+#endif
+
+
+extern int procio_client(const char *command, int auto_color_logs);
+
+void unrealircdctl_usage(const char *program_name)
+{
+	printf("Usage: %s <option>\n"
+	       "Where <option> is one of:\n"
+	       "rehash         - Rehash the server (reread configuration files)\n"
+	       "reloadtls      - Reload the SSL/TLS certificates\n"
+	       "status         - Show current status of server\n"
+	       "module-status  - Show currently loaded modules\n"
+	       "mkpasswd       - Hash a password\n"
+	       "gencloak       - Display 3 random cloak keys\n"
+	       "spkifp         - Display SPKI Fingerprint\n"
+	       "\n", program_name);
+	exit(-1);
+}
+
+void unrealircdctl_rehash(void)
+{
+	if (procio_client("REHASH", 1) == 0)
+	{
+		printf("Rehashed succesfully.\n");
+		exit(0);
+	}
+	printf("Rehash failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_reloadtls(void)
+{
+	if (procio_client("REHASH -tls", 1) == 0)
+	{
+		printf("Reloading of TLS certificates successful.\n");
+		exit(0);
+	}
+	printf("Reloading TLS certificates failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_status(void)
+{
+	if (procio_client("STATUS", 2) == 0)
+	{
+		printf("UnrealIRCd is up and running.\n");
+		exit(0);
+	}
+	printf("UnrealIRCd status report failed.\n");
+	exit(1);
+}
+
+void unrealircdctl_module_status(void)
+{
+	if (procio_client("MODULES", 2) == 0)
+		exit(0);
+	printf("Could not retrieve complete module list.\n");
+	exit(1);
+}
+
+void unrealircdctl_mkpasswd(int argc, char *argv[])
+{
+	AuthenticationType type;
+	const char *result;
+	char *p = argv[2];
+
+	type = Auth_FindType(NULL, p);
+	if (type == -1)
+	{
+		type = AUTHTYPE_ARGON2;
+	} else {
+		p = argv[3];
+	}
+	if (BadPtr(p))
+	{
+#ifndef _WIN32
+		p = getpass("Enter password to hash: ");
+#else
+		printf("ERROR: You should specify a password to hash");
+		exit(1);
+#endif
+	}
+	if ((type == AUTHTYPE_UNIXCRYPT) && (strlen(p) > 8))
+	{
+		/* Hmmm.. is this warning really still true (and always) ?? */
+		printf("WARNING: Password truncated to 8 characters due to 'crypt' algorithm. "
+		       "You are suggested to use the 'argon2' algorithm instead.");
+		p[8] = '\0';
+	}
+	if (!(result = Auth_Hash(type, p))) {
+		printf("Failed to generate password. Deprecated method? Try 'argon2' instead.\n");
+		exit(0);
+	}
+	printf("Encrypted password is: %s\n", result);
+	exit(0);
+}
+
+void unrealircdctl_gencloak(int argc, char *argv[])
+{
+	#define GENERATE_CLOAKKEY_LEN 80 /* Length of cloak keys to generate. */
+	char keyBuf[GENERATE_CLOAKKEY_LEN + 1];
+	int keyNum;
+	int charIndex;
+
+	short has_upper;
+	short has_lower;
+	short has_num;
+
+	printf("Here are 3 random cloak keys that you can copy-paste to your configuration file:\n\n");
+
+	printf("set {\n\tcloak-keys {\n");
+	for (keyNum = 0; keyNum < 3; ++keyNum)
+	{
+		has_upper = 0;
+		has_lower = 0;
+		has_num = 0;
+
+		for (charIndex = 0; charIndex < sizeof(keyBuf)-1; ++charIndex)
+		{
+			switch (getrandom8() % 3)
+			{
+				case 0: /* Uppercase. */
+					keyBuf[charIndex] = (char)('A' + (getrandom8() % ('Z' - 'A')));
+					has_upper = 1;
+					break;
+				case 1: /* Lowercase. */
+					keyBuf[charIndex] = (char)('a' + (getrandom8() % ('z' - 'a')));
+					has_lower = 1;
+					break;
+				case 2: /* Digit. */
+					keyBuf[charIndex] = (char)('0' + (getrandom8() % ('9' - '0')));
+					has_num = 1;
+					break;
+			}
+		}
+		keyBuf[sizeof(keyBuf)-1] = '\0';
+
+		if (has_upper && has_lower && has_num)
+			printf("\t\t\"%s\";\n", keyBuf);
+		else
+			/* Try again. For this reason, keyNum must be signed. */
+			keyNum--;
+	}
+	printf("\t}\n}\n\n");
+	exit(0);
+}
+
+void unrealircdctl_spkifp(int argc, char *argv[])
+{
+	char *file = argv[2];
+	SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method());
+	SSL *ssl;
+	X509 *cert;
+	const char *spkifp;
+
+	if (!ctx)
+	{
+		printf("Internal failure while initializing SSL/TLS library context\n");
+		exit(1);
+	}
+
+	if (!file)
+	{
+		printf("NOTE: This script uses the default certificate location (any set::tls settings\n"
+		       "are ignored). If this is not what you want then specify a certificate\n"
+		       "explicitly like this: %s spkifp conf/tls/example.pem\n\n", UNREALCMD);
+		safe_strdup(file, "tls/server.cert.pem");
+		convert_to_absolute_path(&file, CONFDIR);
+	}
+
+	if (!file_exists(file))
+	{
+		printf("Could not open certificate: %s\n"
+		       "You can specify a certificate like this: %s spkifp conf/tls/example.pem\n",
+		       UNREALCMD, file);
+		exit(1);
+	}
+
+	if (SSL_CTX_use_certificate_chain_file(ctx, file) <= 0)
+	{
+		printf("Could not read certificate '%s'\n", file);
+		exit(1);
+	}
+
+	ssl = SSL_new(ctx);
+	if (!ssl)
+	{
+		printf("Something went wrong when generating the SPKI fingerprint.\n");
+		exit(1);
+	}
+
+	cert = SSL_get_certificate(ssl);
+	spkifp = spki_fingerprint_ex(cert);
+	printf("The SPKI fingerprint for certificate '%s' is:\n"
+	       "%s\n"
+	       "\n"
+	       "You normally add this password on the other side of the link as:\n"
+	       "password \"%s\" { spkifp; };\n"
+	       "\n",
+	       file, spkifp, spkifp);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+#ifdef _WIN32
+	chdir(".."); /* go up one level from "bin" */
+	init_winsock();
+#endif
+	dbuf_init();
+	init_random();
+	early_init_tls();
+
+	if (argc == 1)
+		unrealircdctl_usage(argv[0]);
+
+	if (!strcmp(argv[1], "rehash"))
+		unrealircdctl_rehash();
+	else if (!strcmp(argv[1], "reloadtls"))
+		unrealircdctl_reloadtls();
+	else if (!strcmp(argv[1], "status"))
+		unrealircdctl_status();
+	else if (!strcmp(argv[1], "module-status"))
+		unrealircdctl_module_status();
+	else if (!strcmp(argv[1], "mkpasswd"))
+		unrealircdctl_mkpasswd(argc, argv);
+	else if (!strcmp(argv[1], "gencloak"))
+		unrealircdctl_gencloak(argc, argv);
+	else if (!strcmp(argv[1], "spkifp") || !strcmp(argv[1], "spki"))
+		unrealircdctl_spkifp(argc, argv);
+	else
+		unrealircdctl_usage(argv[0]);
+	exit(0);
+}
diff --git a/src/user.c b/src/user.c
@@ -1083,6 +1083,7 @@ MODVAR const char *floodoption_names[] = {
 	"knock-flood",
 	"max-concurrent-conversations",
 	"lag-penalty",
+	"vhost-flood",
 	NULL
 };
 
diff --git a/src/version.c.SH b/src/version.c.SH
@@ -7,7 +7,7 @@ echo "Extracting src/version.c..."
 if [ -d ../.git ]; then
 	SUFFIX="-$(git rev-parse --short HEAD)"
 fi
-id="6.0.1.1$SUFFIX"
+id="6.0.3$SUFFIX"
 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.6"
-    version="6.0.1.0"
+    version="6.0.3.0"
     type="win32"
 />
 <description>Internet Relay Chat Daemon</description>
@@ -12,7 +12,7 @@
         <assemblyIdentity
             type="win32"
             name="Microsoft.Windows.Common-Controls"
-            version="6.0.1.0"
+            version="6.0.0.0"
             processorArchitecture="amd64"
             publicKeyToken="6595b64144ccf1df"
             language="*"
diff --git a/src/windows/gui.c b/src/windows/gui.c
@@ -188,7 +188,6 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
 	MSG msg;
 	unsigned char *s;
 	HWND hWnd;
-	WSADATA WSAData;
 	HICON hIcon;
 	SC_HANDLE hService, hSCManager;
 	SERVICE_TABLE_ENTRY DispatchTable[] = 
@@ -250,12 +249,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
 	if (!LoadLibrary("riched20.dll"))
 		LoadLibrary("riched32.dll");
 	InitDebug();
-
-	if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0)
-	{
-		MessageBox(NULL, "Unable to initialize WinSock", "UnrealIRCD Initalization Error", MB_OK);
-		return FALSE;
-	}
+	init_winsock();
 	hInst = hInstance; 
 
 	MainDlgBackground = CreateSolidBrush(RGB(75, 134, 238)); /* Background of main dialog */
diff --git a/src/windows/service.c b/src/windows/service.c
@@ -90,7 +90,6 @@ VOID WINAPI IRCDCtrlHandler(DWORD opcode)
  */
 VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) 
 {
-	WSADATA WSAData;
 	DWORD error = 0;
 	char path[MAX_PATH], *folder;
 
@@ -121,13 +120,7 @@ VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
 	GetOSName(OSName);
 
 	InitDebug();
-
-	/* Initialize Winsocks */
-	if ((error = WSAStartup(MAKEWORD(1, 1), &WSAData)) != 0) 
-	{
-		SetServiceStop(error);
-		return;
-	}
+	init_winsock();
 
 	/* Initialize the IRCd */
 	if ((error = InitUnrealIRCd(dwArgc, lpszArgv)) != 1) 
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -6,7 +6,7 @@
 
 [Setup]
 AppName=UnrealIRCd 6
-AppVerName=UnrealIRCd 6.0.1.1
+AppVerName=UnrealIRCd 6.0.3
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org
@@ -45,6 +45,7 @@ Name: "fixperm"; Description: "Make UnrealIRCd folder writable by current user";
 ; UnrealIRCd binaries
 Source: "UnrealIRCd.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
 Source: "UnrealIRCd.pdb"; DestDir: "{app}\bin"; Flags: ignoreversion
+Source: "unrealircdctl.exe"; DestDir: "{app}\bin"; Flags: ignoreversion signonce
 Source: "unrealsvc.exe";  DestDir: "{app}\bin"; Flags: ignoreversion signonce
 
 ; TLS certificate generation helpers
diff --git a/src/windows/unrealircdctl.exe.manifest b/src/windows/unrealircdctl.exe.manifest
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+    processorArchitecture="amd64"
+    name="UnrealIRCd.UnrealIRCd.6"
+    version="6.0.1.0"
+    type="win32"
+/>
+<description>UnrealIRCd - Control utility</description>
+</assembly>
diff --git a/unrealircd.in b/unrealircd.in
@@ -2,6 +2,7 @@
 
 PID_FILE="@PIDFILE@"
 PID_BACKUP="@PIDFILE@.bak"
+UNREALIRCDCTL="@BINDIR@/unrealircdctl"
 
 # When built with --with-asan, ASan does not dump core by default because
 # older gcc/clang might dump a 16TB core file. We explicitly enable it here.
@@ -87,16 +88,13 @@ elif [ "$1" = "stop" ] ; then
 		kill -9 `cat $PID_FILE` 1>/dev/null 2>&1
 	fi
 elif [ "$1" = "rehash" ] ; then
-	echo "Rehashing UnrealIRCd"
-	if [ ! -r $PID_FILE ] ; then
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
-	kill -1 `cat $PID_FILE`
-	if [ "$?" != 0 ]; then
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
+	$UNREALIRCDCTL $*
+elif [ "$1" = "status" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "module-status" ] ; then
+	$UNREALIRCDCTL $*
+elif [ "$1" = "reloadtls" ] ; then
+	$UNREALIRCDCTL $*
 elif [ "$1" = "restart" ] ; then
 	echo "Restarting UnrealIRCd"
 	$0 stop
@@ -117,23 +115,12 @@ elif [ "$1" = "configtest" ] ; then
 elif [ "$1" = "module" ] ; then
 	shift
 	@BINDIR@/unrealircd -m $*
-elif [ "$1" = "reloadtls" ] ; then
-	echo "Reloading SSL/TLS certificates"
-	if [ ! -r $PID_FILE ] ; then
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
-	kill -USR1 `cat $PID_FILE`
-	if [ "$?" != 0 ]; then
-		echo "ERROR: UnrealIRCd is not running"
-		exit 1
-	fi
 elif [ "$1" = "mkpasswd" ] ; then
-	@BINDIR@/unrealircd -P $2 $3
+	$UNREALIRCDCTL $*
 elif [ "$1" = "version" ] ; then
 	@BINDIR@/unrealircd -v
 elif [ "$1" = "gencloak" ] ; then
-	@BINDIR@/unrealircd -k
+	$UNREALIRCDCTL $*
 elif [ "$1" = "backtrace" ] ; then
 	cd @TMPDIR@
 
@@ -224,34 +211,7 @@ __EOF__
 	echo ""
 	echo "Thanks!"
 elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then
-	CERT="@CONFDIR@/tls/server.cert.pem"
-	if [ "$2" != "" ]; then
-		CERT="$2"
-	else
-		echo "NOTE: This script uses the default certificate location (any set::tls settings"
-		echo "are ignored). If this is not what you want then specify a certificate"
-		echo "explicitly like this: ./unrealircd spkifp conf/tls/example.pem"
-		echo ""
-	fi
-	if [ ! -f "$CERT" ]; then
-		echo "Could not open certificate: $CERT"
-		echo "You can specify a certificate like this: ./unrealircd spkifp conf/tls/example.pem"
-		exit 1
-	fi
-	openssl x509 -noout -in "$CERT" -pubkey | openssl asn1parse -noout -inform pem -out @TMPDIR@/tmp.public.key
-	HASH="`openssl dgst -sha256 -binary @TMPDIR@/tmp.public.key | openssl enc -base64`"
-	rm -f @TMPDIR@/tmp.public.key
-	if [ "$HASH" = "" ]; then
-		echo "Sorry, something went wrong when generating the SPKI fingerprint."
-		echo "Is the 'openssl' tool properly installed?"
-		exit 1
-	fi
-	echo "The SPKI fingerprint for certificate $CERT is:"
-	echo "$HASH"
-	echo ""
-	echo "You normally add this password on the other side of the link as:"
-	echo "password \"$HASH\" { spkifp; };"
-	echo ""
+	$UNREALIRCDCTL $*
 elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 	if [ ! -d "@BUILDDIR@" ]; then
 		echo "UnrealIRCd source not found. Sorry, it is not possible to patch."
@@ -282,6 +242,11 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 		exit 1
 	fi
 
+	if ! patch --dry-run -p1 -N <patch 1>/dev/null 2>&1; then
+		echo "Patch failed to apply (no files changed)"
+		exit 1
+	fi
+
 	if ! patch -p1 <patch; then
 		echo "Patch failed to apply"
 		exit 1
@@ -291,11 +256,18 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 	make || gmake || exit 1
 	make install || gmake install || exit 1
 
-	cd -
+	cd @SCRIPTDIR@
 	if [ "$1" = "hot-patch" ]; then
 		echo "Patch applied successfully and installed. Rehashing your IRCd..."
-		./unrealircd rehash
-		echo "Done! All should be good now."
+		if ./unrealircd rehash; then
+			echo "Patch installed and server rehashed correctly. All should be good now!"
+		else
+			echo "Patching the source code and recompiling succeeded,"
+			echo "however rehashing the current UnrealIRCd process FAILED"
+			echo "so it is NOT running the patched code yet."
+			echo "IMPORTANT: Check error output above!"
+			exit 1
+		fi
 	else
 		echo "Patch applied successfully. You must now restart your IRC server."
 	fi
@@ -316,6 +288,8 @@ else
 	echo "unrealircd rehash        Reload the configuration file"
 	echo "unrealircd reloadtls     Reload the SSL/TLS certificates"
 	echo "unrealircd restart       Restart the IRC Server (stop+start)"
+	echo "unrealircd status        Show current status of the IRC Server"
+	echo "unrealircd module-status Show all currently loaded modules"
 	echo "unrealircd upgrade       Upgrade UnrealIRCd to the latest version"
 	echo "unrealircd upgrade-conf  Upgrade the configuration file from UnrealIRCd"
 	echo "                         3.2.x/4.x to 5.x format"