unrealircd

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

commit d53bf1d1c052846c177e74e9f78372ea8902c2a7
parent 66d0369626a5a19a73d2e6053895d5e4ca9a3073
Author: acidvegas <acid.vegas@acid.vegas>
Date: Fri, 5 May 2023 18:12:01 -0400

Updated to 6.1.0

Diffstat:
MConfig | 29++++++++++++++++++++++++++---
MMakefile.in | 3+++
MMakefile.windows | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
MREADME.md | 45++++-----------------------------------------
Mautoconf/m4/unreal.m4 | 23++++++++++++++++++++++-
Mconfigure | 6951+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mconfigure.ac | 45++++++++++++++++++++++++++++-----------------
Mdoc/Config.header | 2+-
Mdoc/RELEASE-NOTES.md | 453+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdoc/conf/modules.conf | 17+++++++++++++++++
Mdoc/conf/snomasks.conf | 1+
Mdoc/conf/unrealircd.remote.conf | 10++++++++++
Mdoc/technical/005.txt | 8++++----
Dextras/argon2-20181209.tar.gz | 0
Aextras/argon2.tar.gz | 0
Mextras/build-tests/nix/build | 2+-
Mextras/build-tests/windows/compilecmd/vs2019.bat | 2+-
Mextras/c-ares.tar.gz | 0
Mextras/curlinstall | 4+++-
Mextras/doxygen/Doxyfile | 2+-
Mextras/jansson.tar.gz | 0
Mextras/pcre2.tar.gz | 0
Mextras/tests/tls/cipherscan_profiles/baseline.txt | 16+++++++---------
Dextras/tests/tls/cipherscan_profiles/openssl-101.txt | 27---------------------------
Dextras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt | 27---------------------------
Dextras/tests/tls/cipherscan_profiles/openssl-300.txt | 27---------------------------
Minclude/common.h | 3++-
Minclude/config.h | 4++--
Minclude/dbuf.h | 1+
Minclude/dynconf.h | 4++++
Minclude/fdlist.h | 2+-
Minclude/h.h | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Minclude/modules.h | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Minclude/numeric.h | 2++
Minclude/setup.h.in | 16++++++++++++----
Minclude/struct.h | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Minclude/version.h | 2+-
Minclude/whowas.h | 11++++++++++-
Minclude/windows/setup.h | 7++++---
Msrc/Makefile.in | 4++--
Msrc/api-channelmode.c | 10++++++----
Msrc/api-clicap.c | 31++++++++++++++++++-------------
Msrc/api-efunctions.c | 48+++++++++++++++++++++++++++++++++++++++++++++---
Msrc/api-extban.c | 63++++++++++++++++++++-------------------------------------------
Asrc/api-rpc.c | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/channel.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/conf.c | 753+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/conf_preprocessor.c | 12+++++++++++-
Msrc/crule.c | 1178++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/dbuf.c | 29+++++++++++++++++++++++++++++
Msrc/dispatch.c | 8++++----
Msrc/fdlist.c | 2+-
Msrc/hash.c | 20+++++++++++++++++++-
Msrc/ircd.c | 30+++++++++++++++++++++++++++---
Asrc/json.c | 580+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/list.c | 49+++++++++++++++++++++++++++++++++++++++++++++----
Msrc/log.c | 487++++++++++++++-----------------------------------------------------------------
Msrc/macosx/UnrealIRCd/Base.lproj/Main.storyboard | 2+-
Msrc/match.c | 5++++-
Msrc/misc.c | 192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/modulemanager.c | 45+++++++++++++++++++++++++++++++++++++++++++--
Msrc/modules.c | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/modules/Makefile.in | 15++++++++++-----
Msrc/modules/antimixedutf8.c | 4++--
Msrc/modules/authprompt.c | 57++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/modules/blacklist.c | 17+++++++++++++----
Msrc/modules/botmotd.c | 6++----
Msrc/modules/certfp.c | 9++++++---
Msrc/modules/chanmodes/delayjoin.c | 2+-
Msrc/modules/chanmodes/floodprot.c | 1530++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/modules/chanmodes/history.c | 7++++---
Msrc/modules/chanmodes/key.c | 5+++--
Msrc/modules/chanmodes/limit.c | 5+++--
Msrc/modules/chanmodes/link.c | 5+++--
Msrc/modules/channeldb.c | 14+++-----------
Msrc/modules/chathistory.c | 28++++++++++++++++++++--------
Msrc/modules/chghost.c | 44++++++++++++++++++++++++++++++++++++--------
Msrc/modules/chgident.c | 53++++++++++++++++++++++++++++++++++-------------------
Msrc/modules/chgname.c | 23+++++++++++++++++------
Asrc/modules/connect-flood.c | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/connect.c | 31++++++++-----------------------
Msrc/modules/creationtime.c | 3++-
Msrc/modules/echo-message.c | 2+-
Msrc/modules/extbans/Makefile.in | 2+-
Msrc/modules/extbans/account.c | 10++++++----
Asrc/modules/extbans/flood.c | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/extbans/timedban.c | 20+-------------------
Msrc/modules/geoip_base.c | 6+++---
Msrc/modules/hideserver.c | 4++--
Msrc/modules/history_backend_mem.c | 2+-
Asrc/modules/issued-by-tag.c | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/join.c | 5+++--
Msrc/modules/json-log-tag.c | 7+++++++
Msrc/modules/kick.c | 2+-
Msrc/modules/labeled-response.c | 4++--
Msrc/modules/list.c | 4++--
Asrc/modules/max-unknown-connections-per-ip.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/message-tags.c | 13+++++++++++--
Msrc/modules/message.c | 6+++++-
Msrc/modules/mode.c | 42+++++++++++++++++++++---------------------
Msrc/modules/motd.c | 8++++----
Msrc/modules/nick.c | 34+++++++++++++++++++---------------
Msrc/modules/oper.c | 19+++++++++++--------
Msrc/modules/opermotd.c | 6++----
Msrc/modules/pass.c | 57---------------------------------------------------------
Msrc/modules/pingpong.c | 2+-
Msrc/modules/protoctl.c | 15+++++++++------
Asrc/modules/real-quit-reason.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/reputation.c | 4++--
Msrc/modules/restrict-commands.c | 4++--
Asrc/modules/rpc/Makefile.in | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/channel.c | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/log.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/name_ban.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/rpc.c | 1922+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/server.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/server_ban.c | 342+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/server_ban_exception.c | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/spamfilter.c | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/stats.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/user.c | 697+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/rpc/whowas.c | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/rules.c | 13+++++--------
Msrc/modules/sajoin.c | 35+++++++++++++++++++++++------------
Msrc/modules/samode.c | 5++++-
Msrc/modules/sapart.c | 70++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/modules/sasl.c | 52+++-------------------------------------------------
Msrc/modules/server-time.c | 19+------------------
Msrc/modules/server.c | 346+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/modules/setident.c | 11+++--------
Msrc/modules/sinfo.c | 12++++++++++--
Msrc/modules/sjoin.c | 4++++
Msrc/modules/slog.c | 2+-
Asrc/modules/sreply.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/standard-replies.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/stats.c | 36------------------------------------
Msrc/modules/svsjoin.c | 2+-
Msrc/modules/svskill.c | 2+-
Asrc/modules/svslogin.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/svslusers.c | 2+-
Msrc/modules/svsmode.c | 13+++++++++----
Msrc/modules/svsmotd.c | 2+-
Msrc/modules/svsnick.c | 15+++++++++------
Msrc/modules/svsnline.c | 6+++---
Msrc/modules/svsnolag.c | 2+-
Msrc/modules/svsnoop.c | 2+-
Msrc/modules/svso.c | 11+++++++----
Msrc/modules/svspart.c | 2+-
Msrc/modules/svssilence.c | 2+-
Msrc/modules/svssno.c | 16+++++++++++-----
Msrc/modules/svswatch.c | 2+-
Msrc/modules/targetfloodprot.c | 10++++++++--
Msrc/modules/tkl.c | 602+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/modules/tkldb.c | 2+-
Asrc/modules/tline.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/tls_antidos.c | 2+-
Msrc/modules/tls_cipher.c | 9++++++---
Msrc/modules/topic.c | 46++++++++++++++++++++++++++++++++++++++++------
Msrc/modules/webirc.c | 4++--
Asrc/modules/webserver.c | 675+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/websocket.c | 782++++++++++++++-----------------------------------------------------------------
Asrc/modules/websocket_common.c | 523+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/whois.c | 4+++-
Msrc/modules/whowas.c | 32+++++++++++++++++++++++++-------
Asrc/modules/whowasdb.c | 641+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/operclass.c | 2+-
Msrc/parse.c | 2+-
Msrc/proc_io_server.c | 23++++++++++++++++++++++-
Msrc/securitygroup.c | 26+++++++++++++++++++++++++-
Msrc/send.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/serv.c | 24+++++-------------------
Msrc/socket.c | 129+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/support.c | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/tls.c | 32++++++++++++++++++++++----------
Msrc/unrealdb.c | 2+-
Msrc/unrealircdctl.c | 2++
Msrc/url_curl.c | 25+++++++++++++++++++++++++
Msrc/url_unreal.c | 38++++++++++++++++++++++++++++----------
Msrc/user.c | 22++++++++++++++++++++++
Msrc/utf8.c | 17++++++++++++-----
Msrc/version.c.SH | 2+-
Msrc/whowas.c | 102++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/windows/UnrealIRCd.exe.manifest | 2+-
Msrc/windows/unrealinst.iss | 3++-
Munrealircd.in | 114++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------

185 files changed, 18241 insertions(+), 6995 deletions(-)

diff --git a/Config b/Config
@@ -280,7 +280,7 @@ OPEROVERRIDEVERIFY=""
 GENCERTIFICATE="1"
 EXTRAPARA=""
 SANITIZER=""
-GEOIP="none"
+GEOIP="classic"
 if [ "`eval echo -n 'a'`" = "-n a" ] ; then
 	c="\c"
 else
@@ -364,7 +364,7 @@ fi
 clear
 
 if [ -f "doc/Config.header" -a -z "$NOINTRO" ] ; then
-	more doc/Config.header
+	cat doc/Config.header
 	echo ""
 	echo $n "[Press Enter to continue]"
 	read cc
@@ -375,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.4.1 unrealircd-6.0.4 unrealircd-6.0.4-rc2 unrealircd-6.0.4-rc1 unrealircd-6.0.3 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"
+UNREALRELEASES="unrealircd-6.1.0-rc2 unrealircd-6.1.0-rc1 unrealircd-6.0.7 unrealircd-6.0.6 unrealircd-6.0.5 unrealircd-6.0.5-rc2 unrealircd-6.0.5-rc1 unrealircd-6.0.4.2 unrealircd-6.0.4.1 unrealircd-6.0.4 unrealircd-6.0.4-rc2 unrealircd-6.0.4-rc1 unrealircd-6.0.3 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
@@ -527,6 +527,29 @@ else
 	SSLDIR=`eval echo $cc` # modified
 fi
 
+if [ "$SSLDIR" != "" -a "$SSLDIR" != "/usr" ]; then
+	echo ""
+	echo "You answered previous question manually. Just note that if the library is not"
+	echo "in your default library path then UnrealIRCd may fail to start with an error"
+	echo "that it cannot find certain .so files (libraries). In that case you would have"
+	echo "to either set the LD_LIBARY_PATH environment variable, or you could update the"
+	echo "Makefile to link with -Wl,-rpath,$SSLDIR or similar."
+	echo ""
+	if [ "$SSLDIR" = "/usr/local" ]; then
+		echo "**** CAUTION ****"
+		echo "You have chosen to use OpenSSL from /usr/local. Just be aware that if you"
+		echo "use the LD_LIBRARY_PATH or -Wl,-rpath,$SSLDIR from above,"
+		echo "that you are diverting OTHER libraries to that path as well."
+		echo "It's not only loading OpenSSL from /usr/local but also potentially other"
+		echo "libraries like PCRE2, Jansson, or any of the other libraries that"
+		echo "UnrealIRCd uses (including dependencies about 40 libs in total!)"
+		echo "All that can result in weird issues and crashes!"
+		echo ""
+	fi
+	echo "Press enter to continue with the rest of the questions, or CTRL+C to abort."
+	read cc
+fi
+
 TEST=""
 while [ -z "$TEST" ] ; do
 	if [ "$REMOTEINC" = "1" ] ; then
diff --git a/Makefile.in b/Makefile.in
@@ -199,6 +199,9 @@ install: all
 	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/extbans
 	@rm -f $(DESTDIR)@MODULESDIR@/extbans/*.so 1>/dev/null 2>&1
 	$(INSTALL) -m 0700 src/modules/extbans/*.so $(DESTDIR)@MODULESDIR@/extbans
+	$(INSTALL) -m 0700 -d $(DESTDIR)@MODULESDIR@/rpc
+	@rm -f $(DESTDIR)@MODULESDIR@/rpc/*.so 1>/dev/null 2>&1
+	$(INSTALL) -m 0700 src/modules/rpc/*.so $(DESTDIR)@MODULESDIR@/rpc
 	@#If the conf/ssl directory exists then rename it here to conf/tls
 	@#and add a symlink for backwards compatibility (so that f.e. certbot
 	@#doesn't randomly fail after an upgrade to U5).
diff --git a/Makefile.windows b/Makefile.windows
@@ -184,13 +184,13 @@ EXP_OBJ_FILES=src/ircd_vars.obj src/channel.obj src/send.obj src/socket.obj \
  src/version.obj src/ircsprintf.obj \
  src/scache.obj src/dns.obj src/modules.obj \
  src/aliases.obj src/api-event.obj src/api-usermode.obj src/auth.obj src/tls.obj \
- src/random.obj src/api-channelmode.obj src/api-moddata.obj src/mempool.obj \
+ src/random.obj src/api-channelmode.obj src/api-moddata.obj src/api-rpc.obj src/mempool.obj \
  src/dispatch.obj src/api-isupport.obj src/api-command.obj \
  src/api-clicap.obj src/api-messagetag.obj src/api-history-backend.obj \
  src/api-extban.obj src/api-efunctions.obj src/crypt_blowfish.obj \
  src/operclass.obj src/crashreport.obj src/unrealdb.obj \
  src/openssl_hostname_validation.obj \
- src/utf8.obj src/log.obj $(CURLOBJ)
+ src/utf8.obj src/json.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/ircd.obj src/proc_io_client.obj
@@ -253,6 +253,7 @@ DLL_FILES=\
  src/modules/clienttagdeny.dll \
  src/modules/close.dll \
  src/modules/connect.dll \
+ src/modules/connect-flood.dll \
  src/modules/connthrottle.dll \
  src/modules/creationtime.dll \
  src/modules/cycle.dll \
@@ -265,6 +266,7 @@ DLL_FILES=\
  src/modules/extbans/country.dll \
  src/modules/extbans/inchannel.dll \
  src/modules/extbans/join.dll \
+ src/modules/extbans/flood.dll \
  src/modules/extbans/msgbypass.dll \
  src/modules/extbans/nickchange.dll \
  src/modules/extbans/operclass.dll \
@@ -305,6 +307,7 @@ DLL_FILES=\
  src/modules/locops.dll \
  src/modules/lusers.dll \
  src/modules/map.dll \
+ src/modules/max-unknown-connections-per-ip.dll \
  src/modules/md.dll \
  src/modules/message.dll \
  src/modules/message-ids.dll \
@@ -329,11 +332,23 @@ DLL_FILES=\
  src/modules/plaintext-policy.dll \
  src/modules/protoctl.dll \
  src/modules/quit.dll \
+ src/modules/real-quit-reason.dll \
  src/modules/reply-tag.dll \
  src/modules/reputation.dll \
  src/modules/require-module.dll \
  src/modules/restrict-commands.dll \
  src/modules/rmtkl.dll \
+ src/modules/rpc/channel.dll \
+ src/modules/rpc/log.dll \
+ src/modules/rpc/name_ban.dll \
+ src/modules/rpc/rpc.dll \
+ src/modules/rpc/stats.dll \
+ src/modules/rpc/server.dll \
+ src/modules/rpc/server_ban.dll \
+ src/modules/rpc/server_ban_exception.dll \
+ src/modules/rpc/spamfilter.dll \
+ src/modules/rpc/whowas.dll \
+ src/modules/rpc/user.dll \
  src/modules/rules.dll \
  src/modules/sajoin.dll \
  src/modules/samode.dll \
@@ -353,12 +368,15 @@ DLL_FILES=\
  src/modules/slog.dll \
  src/modules/sqline.dll \
  src/modules/squit.dll \
+ src/modules/sreply.dll \
  src/modules/staff.dll \
+ src/modules/standard-replies.dll \
  src/modules/starttls.dll \
  src/modules/stats.dll \
  src/modules/sts.dll \
  src/modules/svsjoin.dll \
  src/modules/svskill.dll \
+ src/modules/svslogin.dll \
  src/modules/svslusers.dll \
  src/modules/svsmode.dll \
  src/modules/svsmotd.dll \
@@ -376,6 +394,7 @@ DLL_FILES=\
  src/modules/time.dll \
  src/modules/tkl.dll \
  src/modules/tkldb.dll \
+ src/modules/tline.dll \
  src/modules/tls_antidos.dll \
  src/modules/tls_cipher.dll \
  src/modules/topic.dll \
@@ -407,10 +426,13 @@ DLL_FILES=\
  src/modules/watch.dll \
  src/modules/webirc.dll \
  src/modules/webredir.dll \
+ src/modules/webserver.dll \
  src/modules/websocket.dll \
+ src/modules/websocket_common.dll \
  src/modules/whois.dll \
  src/modules/who_old.dll \
  src/modules/whowas.dll \
+ src/modules/whowasdb.dll \
  src/modules/whox.dll
 
 
@@ -582,6 +604,9 @@ src/api-channelmode.obj: src/api-channelmode.c $(INCLUDES)
 src/api-moddata.obj: src/api-moddata.c $(INCLUDES)
 	$(CC) $(CFLAGS) src/api-moddata.c
 
+src/api-rpc.obj: src/api-rpc.c $(INCLUDES)
+	$(CC) $(CFLAGS) src/api-rpc.c
+
 src/mempool.obj: src/mempool.c $(INCLUDES)
 	$(CC) $(CFLAGS) src/mempool.c
 
@@ -633,6 +658,9 @@ src/utf8.obj: src/utf8.c $(INCLUDES) ./include/dbuf.h
 src/openssl_hostname_validation.obj: src/openssl_hostname_validation.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/openssl_hostname_validation.c
 
+src/json.obj: src/json.c $(INCLUDES) ./include/dbuf.h
+        $(CC) $(CFLAGS) src/json.c
+
 src/log.obj: src/log.c $(INCLUDES) ./include/dbuf.h
         $(CC) $(CFLAGS) src/log.c
 
@@ -832,6 +860,9 @@ src/modules/close.dll: src/modules/close.c $(INCLUDES)
 src/modules/connect.dll: src/modules/connect.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/connect.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connect.pdb $(MODLFLAGS)
 
+src/modules/connect-flood.dll: src/modules/connect-flood.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/connect-flood.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connect-flood.pdb $(MODLFLAGS)
+
 src/modules/connthrottle.dll: src/modules/connthrottle.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/connthrottle.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/connthrottle.pdb $(MODLFLAGS)
 
@@ -868,6 +899,9 @@ src/modules/extbans/inchannel.dll: src/modules/extbans/inchannel.c $(INCLUDES)
 src/modules/extbans/join.dll: src/modules/extbans/join.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/extbans/join.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/join.pdb $(MODLFLAGS)
 
+src/modules/extbans/flood.dll: src/modules/extbans/flood.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/extbans/flood.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/flood.pdb $(MODLFLAGS)
+
 src/modules/extbans/msgbypass.dll: src/modules/extbans/msgbypass.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/extbans/msgbypass.c /Fesrc/modules/extbans/ /Fosrc/modules/extbans/ /Fdsrc/modules/extbans/msgbypass.pdb $(MODLFLAGS)
 
@@ -991,6 +1025,9 @@ src/modules/lusers.dll: src/modules/lusers.c $(INCLUDES)
 src/modules/map.dll: src/modules/map.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/map.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/map.pdb $(MODLFLAGS)
 
+src/modules/max-unknown-connections-per-ip.dll: src/modules/max-unknown-connections-per-ip.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/max-unknown-connections-per-ip.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/max-unknown-connections-per-ip.pdb $(MODLFLAGS)
+
 src/modules/md.dll: src/modules/md.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/md.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/md.pdb $(MODLFLAGS)
 
@@ -1063,6 +1100,9 @@ src/modules/protoctl.dll: src/modules/protoctl.c $(INCLUDES)
 src/modules/quit.dll: src/modules/quit.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/quit.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/quit.pdb $(MODLFLAGS)
 
+src/modules/real-quit-reason.dll: src/modules/real-quit-reason.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/real-quit-reason.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/real-quit-reason.pdb $(MODLFLAGS)
+
 src/modules/reply-tag.dll: src/modules/reply-tag.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/reply-tag.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/reply-tag.pdb $(MODLFLAGS)
 
@@ -1078,6 +1118,39 @@ src/modules/restrict-commands.dll: src/modules/restrict-commands.c $(INCLUDES)
 src/modules/rmtkl.dll: src/modules/rmtkl.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/rmtkl.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/rmtkl.pdb $(MODLFLAGS)
 
+src/modules/rpc/channel.dll: src/modules/rpc/channel.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/channel.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/channel.pdb $(MODLFLAGS)
+
+src/modules/rpc/log.dll: src/modules/rpc/log.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/log.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/log.pdb $(MODLFLAGS)
+
+src/modules/rpc/name_ban.dll: src/modules/rpc/name_ban.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/name_ban.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/name_ban.pdb $(MODLFLAGS)
+
+src/modules/rpc/rpc.dll: src/modules/rpc/rpc.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/rpc.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/rpc.pdb $(MODLFLAGS)
+
+src/modules/rpc/stats.dll: src/modules/rpc/stats.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/stats.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/stats.pdb $(MODLFLAGS)
+
+src/modules/rpc/server.dll: src/modules/rpc/server.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/server.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server.pdb $(MODLFLAGS)
+
+src/modules/rpc/server_ban.dll: src/modules/rpc/server_ban.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/server_ban.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server_ban.pdb $(MODLFLAGS)
+
+src/modules/rpc/server_ban_exception.dll: src/modules/rpc/server_ban_exception.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/server_ban_exception.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/server_ban_exception.pdb $(MODLFLAGS)
+
+src/modules/rpc/spamfilter.dll: src/modules/rpc/spamfilter.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/spamfilter.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/spamfilter.pdb $(MODLFLAGS)
+
+src/modules/rpc/user.dll: src/modules/rpc/user.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/user.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/user.pdb $(MODLFLAGS)
+
+src/modules/rpc/whowas.dll: src/modules/rpc/whowas.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/rpc/whowas.c /Fesrc/modules/rpc/ /Fosrc/modules/rpc/ /Fdsrc/modules/rpc/whowas.pdb $(MODLFLAGS)
+
 src/modules/rules.dll: src/modules/rules.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/rules.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/rules.pdb $(MODLFLAGS)
 
@@ -1135,9 +1208,15 @@ src/modules/sqline.dll: src/modules/sqline.c $(INCLUDES)
 src/modules/squit.dll: src/modules/squit.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/squit.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/squit.pdb $(MODLFLAGS)
 
+src/modules/sreply.dll: src/modules/sreply.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/sreply.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/sreply.pdb $(MODLFLAGS)
+
 src/modules/staff.dll: src/modules/staff.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/staff.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/staff.pdb $(MODLFLAGS)
 
+src/modules/standard-replies.dll: src/modules/standard-replies.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/standard-replies.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/standard-replies.pdb $(MODLFLAGS)
+
 src/modules/starttls.dll: src/modules/starttls.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/starttls.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/starttls.pdb $(MODLFLAGS)
 
@@ -1153,6 +1232,9 @@ src/modules/svsjoin.dll: src/modules/svsjoin.c $(INCLUDES)
 src/modules/svskill.dll: src/modules/svskill.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/svskill.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svskill.pdb $(MODLFLAGS)
 
+src/modules/svslogin.dll: src/modules/svslogin.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/svslogin.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svslogin.pdb $(MODLFLAGS)
+
 src/modules/svslusers.dll: src/modules/svslusers.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/svslusers.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/svslusers.pdb $(MODLFLAGS)
 
@@ -1204,6 +1286,9 @@ src/modules/tkl.dll: src/modules/tkl.c $(INCLUDES)
 src/modules/tkldb.dll: src/modules/tkldb.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/tkldb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tkldb.pdb $(MODLFLAGS)
 
+src/modules/tline.dll: src/modules/tline.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/tline.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tline.pdb $(MODLFLAGS)
+
 src/modules/tls_antidos.dll: src/modules/tls_antidos.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/tls_antidos.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/tls_antidos.pdb $(MODLFLAGS)
 
@@ -1297,9 +1382,15 @@ src/modules/webirc.dll: src/modules/webirc.c $(INCLUDES)
 src/modules/webredir.dll: src/modules/webredir.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/webredir.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webredir.pdb $(MODLFLAGS)
 
+src/modules/webserver.dll: src/modules/webserver.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/webserver.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webserver.pdb $(MODLFLAGS)
+
 src/modules/websocket.dll: src/modules/websocket.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/websocket.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket.pdb $(MODLFLAGS)
 
+src/modules/websocket_common.dll: src/modules/websocket_common.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/websocket_common.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket_common.pdb $(MODLFLAGS)
+
 src/modules/whois.dll: src/modules/whois.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/whois.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whois.pdb $(MODLFLAGS)
 
@@ -1309,6 +1400,9 @@ src/modules/who_old.dll: src/modules/who_old.c $(INCLUDES)
 src/modules/whowas.dll: src/modules/whowas.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/whowas.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whowas.pdb $(MODLFLAGS)
 
+src/modules/whowasdb.dll: src/modules/whowasdb.c $(INCLUDES)
+	$(CC) $(MODCFLAGS) src/modules/whowasdb.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whowasdb.pdb $(MODLFLAGS)
+
 src/modules/whox.dll: src/modules/whox.c $(INCLUDES)
 	$(CC) $(MODCFLAGS) src/modules/whox.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/whox.pdb $(MODLFLAGS)
 
diff --git a/README.md b/README.md
@@ -11,55 +11,18 @@ online documentation.
 
 ## Versions
 * UnrealIRCd 6 is the *stable* series since December 2021. All new features go in there.
-* UnrealIRCd 5 is the *oldstable* series. It will receive bug fixes until
-  July 1, 2022 plus another 12 months of security fixes.
+* UnrealIRCd 5 is the *oldstable* series. It is End Of Life. Only security bugs will
+  be fixed until June 30, 2023, after which all support will stop.
 * For full details of release scheduling and EOL dates, see
   [UnrealIRCd releases](https://www.unrealircd.org/docs/UnrealIRCd_releases) on the wiki
 
 ## How to get started
-### Use the wiki!
-**IMPORTANT:** We recommend you follow our installation guide on the wiki instead of the
-steps in this README. The wiki has more detailed information and is more easy to navigate.
+Follow the installation guide on the wiki. See:
 * [Installing from source for *NIX](https://www.unrealircd.org/docs/Installing_from_source)
 * [Installating instructions for Windows](https://www.unrealircd.org/docs/Installing_(Windows))
 
-Please consult the online documentation at https://www.unrealircd.org/docs/ when setting up the IRCd!
-
-### Step 1: Installation
-#### Windows
-Simply download the UnrealIRCd Windows version from www.unrealircd.org
-
-Alternatively you can compile UnrealIRCd for Windows yourself. However this is not straightforward and thus not recommended.
-
-#### *BSD/Linux/macOS
-Do the following steps under a separate account for running UnrealIRCd,
-[do NOT compile or run as root](https://www.unrealircd.org/docs/Do_not_run_as_root).
-
-### Step 1: Compile the IRCd
-
-* Run `./Config`
-* Run `make`
-* Run `make install`
-* Now change to the directory where you installed UnrealIRCd, e.g. `cd /home/xxxx/unrealircd`
-
-### Step 2: Configuration
-Configuration files are stored in the `conf/` folder by default (eg: `/home/xxxx/unrealircd/conf`)
-
-#### Create a configuration file
-If you are new, then you need to create your own configuration file:
-Copy `conf/examples/example.conf` to `conf/` and call it `unrealircd.conf`.
-Then open it in an editor and carefully modify it using the documentation and FAQ as a guide (see below).
-
-### Step 3: Booting
-
-#### Linux/*BSD/macOS
-Run `./unrealircd start` in the directory where you installed UnrealIRCd.
-
-#### Windows
-Start -> All Programs -> UnrealIRCd -> UnrealIRCd
-
 ## Documentation & FAQ
-You can find the **documentation** online at: https://www.unrealircd.org/docs/
+You can find all **documentation** online at: https://www.unrealircd.org/docs/
 
 We also have a good **FAQ**: https://www.unrealircd.org/docs/FAQ
 
diff --git a/autoconf/m4/unreal.m4 b/autoconf/m4/unreal.m4
@@ -97,7 +97,7 @@ AC_DEFUN([CHECK_LIBCURL],
   with the system-installed libcURL, this is a bad idea which may result in error
   messages looking like:
 
-  	\`\`[error] unrealircd.conf:9: include: error downloading '(http://example.net/ex.conf)': Could not resolve host: example.net (Successful completion)''
+  	error downloading ... Could not resolve host: example.net (Successful completion)
 
   Or UnrealIRCd might even crash.
 
@@ -321,6 +321,26 @@ else
 fi
 ])
 
+AC_DEFUN([CHECK_X509_check_host],
+[
+AC_MSG_CHECKING([for X509_check_host in SSL library])
+AC_LANG_PUSH(C)
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+AC_TRY_LINK([#include <openssl/x509v3.h>],
+	[X509_check_host(NULL, NULL, 0, 0, NULL);],
+	has_function=1,
+	has_function=0)
+LIBS="$SAVE_LIBS"
+AC_LANG_POP(C)
+if test $has_function = 1; then
+	AC_MSG_RESULT([yes])
+	AC_DEFINE([HAS_X509_check_host], [], [Define if ssl library has X509_check_host])
+else
+	AC_MSG_RESULT([no])
+fi
+])
+
 dnl For geoip-api-c
 AC_DEFUN([CHECK_GEOIP_CLASSIC],
 [
@@ -366,6 +386,7 @@ AC_DEFUN([CHECK_GEOIP_CLASSIC],
 			AC_MSG_RESULT(compiling GeoIP Classic library)
 			$ac_cv_prog_MAKER || exit 1
 			AC_MSG_RESULT(installing GeoIP Classic library)
+			rm -f "$PRIVATELIBDIR/"libGeoIP.so*
 			$ac_cv_prog_MAKER install || exit 1
 			dnl Try pkg-config first...
 			AS_IF([test -n "$ac_cv_path_PKGCONFIG"],
diff --git a/configure b/configure
@@ -1,11 +1,12 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for unrealircd 6.0.4.2.
+# Generated by GNU Autoconf 2.71 for unrealircd 6.1.0.
 #
 # Report bugs to <https://bugs.unrealircd.org/>.
 #
 #
-# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
+# Inc.
 #
 #
 # This configure script is free software; the Free Software Foundation
@@ -16,14 +17,16 @@
 
 # Be more Bourne compatible
 DUALCASE=1; export DUALCASE # for MKS sh
-if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+as_nop=:
+if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
   emulate sh
   NULLCMD=:
   # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
   # is contrary to our usage.  Disable this feature.
   alias -g '${1+"$@"}'='"$@"'
   setopt NO_GLOB_SUBST
-else
+else $as_nop
   case `(set -o) 2>/dev/null` in #(
   *posix*) :
     set -o posix ;; #(
@@ -33,46 +36,46 @@ esac
 fi
 
 
+
+# Reset variables that may have inherited troublesome values from
+# the environment.
+
+# IFS needs to be set, to space, tab, and newline, in precisely that order.
+# (If _AS_PATH_WALK were called with IFS unset, it would have the
+# side effect of setting IFS to empty, thus disabling word splitting.)
+# Quoting is to prevent editors from complaining about space-tab.
 as_nl='
 '
 export as_nl
-# Printing a long string crashes Solaris 7 /usr/bin/printf.
-as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
-as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
-as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
-# Prefer a ksh shell builtin over an external printf program on Solaris,
-# but without wasting forks for bash or zsh.
-if test -z "$BASH_VERSION$ZSH_VERSION" \
-    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
-  as_echo='print -r --'
-  as_echo_n='print -rn --'
-elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
-  as_echo='printf %s\n'
-  as_echo_n='printf %s'
-else
-  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
-    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
-    as_echo_n='/usr/ucb/echo -n'
-  else
-    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
-    as_echo_n_body='eval
-      arg=$1;
-      case $arg in #(
-      *"$as_nl"*)
-	expr "X$arg" : "X\\(.*\\)$as_nl";
-	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
-      esac;
-      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
-    '
-    export as_echo_n_body
-    as_echo_n='sh -c $as_echo_n_body as_echo'
-  fi
-  export as_echo_body
-  as_echo='sh -c $as_echo_body as_echo'
-fi
+IFS=" ""	$as_nl"
+
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# Ensure predictable behavior from utilities with locale-dependent output.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# We cannot yet rely on "unset" to work, but we need these variables
+# to be unset--not just set to an empty or harmless value--now, to
+# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh).  This construct
+# also avoids known problems related to "unset" and subshell syntax
+# in other old shells (e.g. bash 2.01 and pdksh 5.2.14).
+for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH
+do eval test \${$as_var+y} \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+
+# Ensure that fds 0, 1, and 2 are open.
+if (exec 3>&0) 2>/dev/null; then :; else exec 0</dev/null; fi
+if (exec 3>&1) 2>/dev/null; then :; else exec 1>/dev/null; fi
+if (exec 3>&2)            ; then :; else exec 2>/dev/null; fi
 
 # The user is always right.
-if test "${PATH_SEPARATOR+set}" != set; then
+if ${PATH_SEPARATOR+false} :; then
   PATH_SEPARATOR=:
   (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
     (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
@@ -81,13 +84,6 @@ if test "${PATH_SEPARATOR+set}" != set; then
 fi
 
 
-# IFS
-# We need space, tab and new line, in precisely that order.  Quoting is
-# there to prevent editors from complaining about space-tab.
-# (If _AS_PATH_WALK were called with IFS unset, it would disable word
-# splitting by setting IFS to empty value.)
-IFS=" ""	$as_nl"
-
 # Find who we are.  Look in the path if we contain no directory separator.
 as_myself=
 case $0 in #((
@@ -96,8 +92,12 @@ case $0 in #((
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    test -r "$as_dir$0" && as_myself=$as_dir$0 && break
   done
 IFS=$as_save_IFS
 
@@ -109,30 +109,10 @@ if test "x$as_myself" = x; then
   as_myself=$0
 fi
 if test ! -f "$as_myself"; then
-  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
   exit 1
 fi
 
-# Unset variables that we do not need and which cause bugs (e.g. in
-# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
-# suppresses any "Segmentation fault" message there.  '((' could
-# trigger a bug in pdksh 5.2.14.
-for as_var in BASH_ENV ENV MAIL MAILPATH
-do eval test x\${$as_var+set} = xset \
-  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
-done
-PS1='$ '
-PS2='> '
-PS4='+ '
-
-# NLS nuisances.
-LC_ALL=C
-export LC_ALL
-LANGUAGE=C
-export LANGUAGE
-
-# CDPATH.
-(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
 
 # Use a proper internal environment variable to ensure we don't fall
   # into an infinite loop, continuously re-executing ourselves.
@@ -154,20 +134,22 @@ esac
 exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
 # Admittedly, this is quite paranoid, since all the known shells bail
 # out after a failed `exec'.
-$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
-as_fn_exit 255
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
   fi
   # We don't want this to propagate to other subprocesses.
           { _as_can_reexec=; unset _as_can_reexec;}
 if test "x$CONFIG_SHELL" = x; then
-  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  as_bourne_compatible="as_nop=:
+if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
   emulate sh
   NULLCMD=:
   # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
   # is contrary to our usage.  Disable this feature.
   alias -g '\${1+\"\$@\"}'='\"\$@\"'
   setopt NO_GLOB_SUBST
-else
+else \$as_nop
   case \`(set -o) 2>/dev/null\` in #(
   *posix*) :
     set -o posix ;; #(
@@ -187,42 +169,53 @@ as_fn_success || { exitcode=1; echo as_fn_success failed.; }
 as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
 as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
 as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
-if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+if ( set x; as_fn_ret_success y && test x = \"\$1\" )
+then :
 
-else
+else \$as_nop
   exitcode=1; echo positional parameters were not saved.
 fi
 test x\$exitcode = x0 || exit 1
+blah=\$(echo \$(echo blah))
+test x\"\$blah\" = xblah || exit 1
 test -x / || exit 1"
   as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
   as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
   eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
   test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
 test \$(( 1 + 1 )) = 2 || exit 1"
-  if (eval "$as_required") 2>/dev/null; then :
+  if (eval "$as_required") 2>/dev/null
+then :
   as_have_required=yes
-else
+else $as_nop
   as_have_required=no
 fi
-  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null
+then :
 
-else
+else $as_nop
   as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 as_found=false
 for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
   as_found=:
   case $as_dir in #(
 	 /*)
 	   for as_base in sh bash ksh sh5; do
 	     # Try only shells that exist, to save several forks.
-	     as_shell=$as_dir/$as_base
+	     as_shell=$as_dir$as_base
 	     if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
-		    { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+		    as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null
+then :
   CONFIG_SHELL=$as_shell as_have_required=yes
-		   if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+		   if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null
+then :
   break 2
 fi
 fi
@@ -230,14 +223,21 @@ fi
        esac
   as_found=false
 done
-$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
-	      { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
-  CONFIG_SHELL=$SHELL as_have_required=yes
-fi; }
 IFS=$as_save_IFS
+if $as_found
+then :
+
+else $as_nop
+  if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+	      as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null
+then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi
+fi
 
 
-      if test "x$CONFIG_SHELL" != x; then :
+      if test "x$CONFIG_SHELL" != x
+then :
   export CONFIG_SHELL
              # We cannot yet assume a decent shell, so we have to provide a
 # neutralization value for shells without unset; and this also
@@ -255,18 +255,19 @@ esac
 exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
 # Admittedly, this is quite paranoid, since all the known shells bail
 # out after a failed `exec'.
-$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2
 exit 255
 fi
 
-    if test x$as_have_required = xno; then :
-  $as_echo "$0: This script requires a shell more modern than all"
-  $as_echo "$0: the shells that I found on your system."
-  if test x${ZSH_VERSION+set} = xset ; then
-    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
-    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+    if test x$as_have_required = xno
+then :
+  printf "%s\n" "$0: This script requires a shell more modern than all"
+  printf "%s\n" "$0: the shells that I found on your system."
+  if test ${ZSH_VERSION+y} ; then
+    printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later."
   else
-    $as_echo "$0: Please tell bug-autoconf@gnu.org and
+    printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and
 $0: https://bugs.unrealircd.org/ about your system,
 $0: including any error possibly output before this
 $0: message. Then install a modern shell, or manually run
@@ -294,6 +295,7 @@ as_fn_unset ()
 }
 as_unset=as_fn_unset
 
+
 # as_fn_set_status STATUS
 # -----------------------
 # Set $? to STATUS, without forking.
@@ -311,6 +313,14 @@ as_fn_exit ()
   as_fn_set_status $1
   exit $1
 } # as_fn_exit
+# as_fn_nop
+# ---------
+# Do nothing but, unlike ":", preserve the value of $?.
+as_fn_nop ()
+{
+  return $?
+}
+as_nop=as_fn_nop
 
 # as_fn_mkdir_p
 # -------------
@@ -325,7 +335,7 @@ as_fn_mkdir_p ()
     as_dirs=
     while :; do
       case $as_dir in #(
-      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
       *) as_qdir=$as_dir;;
       esac
       as_dirs="'$as_qdir' $as_dirs"
@@ -334,7 +344,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
 	 X"$as_dir" : 'X\(//\)[^/]' \| \
 	 X"$as_dir" : 'X\(//\)$' \| \
 	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X"$as_dir" |
+printf "%s\n" X"$as_dir" |
     sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
 	    s//\1/
 	    q
@@ -373,12 +383,13 @@ as_fn_executable_p ()
 # advantage of any shell optimizations that allow amortized linear growth over
 # repeated appends, instead of the typical quadratic growth present in naive
 # implementations.
-if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null
+then :
   eval 'as_fn_append ()
   {
     eval $1+=\$2
   }'
-else
+else $as_nop
   as_fn_append ()
   {
     eval $1=\$$1\$2
@@ -390,18 +401,27 @@ fi # as_fn_append
 # Perform arithmetic evaluation on the ARGs, and store the result in the
 # global $as_val. Take advantage of shells that can avoid forks. The arguments
 # must be portable across $(()) and expr.
-if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null
+then :
   eval 'as_fn_arith ()
   {
     as_val=$(( $* ))
   }'
-else
+else $as_nop
   as_fn_arith ()
   {
     as_val=`expr "$@" || test $? -eq 1`
   }
 fi # as_fn_arith
 
+# as_fn_nop
+# ---------
+# Do nothing but, unlike ":", preserve the value of $?.
+as_fn_nop ()
+{
+  return $?
+}
+as_nop=as_fn_nop
 
 # as_fn_error STATUS ERROR [LINENO LOG_FD]
 # ----------------------------------------
@@ -413,9 +433,9 @@ as_fn_error ()
   as_status=$1; test $as_status -eq 0 && as_status=1
   if test "$4"; then
     as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
   fi
-  $as_echo "$as_me: error: $2" >&2
+  printf "%s\n" "$as_me: error: $2" >&2
   as_fn_exit $as_status
 } # as_fn_error
 
@@ -442,7 +462,7 @@ as_me=`$as_basename -- "$0" ||
 $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
 	 X"$0" : 'X\(//\)$' \| \
 	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X/"$0" |
+printf "%s\n" X/"$0" |
     sed '/^.*\/\([^/][^/]*\)\/*$/{
 	    s//\1/
 	    q
@@ -486,7 +506,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits
       s/-\n.*//
     ' >$as_me.lineno &&
   chmod +x "$as_me.lineno" ||
-    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+    { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
 
   # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
   # already done that, so ensure we don't try to do so again and fall
@@ -500,6 +520,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits
   exit
 }
 
+
+# Determine whether it's possible to make 'echo' print without a newline.
+# These variables are no longer used directly by Autoconf, but are AC_SUBSTed
+# for compatibility with existing Makefiles.
 ECHO_C= ECHO_N= ECHO_T=
 case `echo -n x` in #(((((
 -n*)
@@ -513,6 +537,13 @@ case `echo -n x` in #(((((
   ECHO_N='-n';;
 esac
 
+# For backward compatibility with old third-party macros, we provide
+# the shell variables $as_echo and $as_echo_n.  New code should use
+# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively.
+as_echo='printf %s\n'
+as_echo_n='printf %s'
+
+
 rm -f conf$$ conf$$.exe conf$$.file
 if test -d conf$$.dir; then
   rm -f conf$$.dir/conf$$.file
@@ -580,48 +611,44 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unrealircd'
 PACKAGE_TARNAME='unrealircd'
-PACKAGE_VERSION='6.0.4.2'
-PACKAGE_STRING='unrealircd 6.0.4.2'
+PACKAGE_VERSION='6.1.0'
+PACKAGE_STRING='unrealircd 6.1.0'
 PACKAGE_BUGREPORT='https://bugs.unrealircd.org/'
 PACKAGE_URL='https://unrealircd.org/'
 
 ac_unique_file="src/ircd.c"
 # Factoring default headers for most tests.
 ac_includes_default="\
-#include <stdio.h>
-#ifdef HAVE_SYS_TYPES_H
-# include <sys/types.h>
-#endif
-#ifdef HAVE_SYS_STAT_H
-# include <sys/stat.h>
+#include <stddef.h>
+#ifdef HAVE_STDIO_H
+# include <stdio.h>
 #endif
-#ifdef STDC_HEADERS
+#ifdef HAVE_STDLIB_H
 # include <stdlib.h>
-# include <stddef.h>
-#else
-# ifdef HAVE_STDLIB_H
-#  include <stdlib.h>
-# endif
 #endif
 #ifdef HAVE_STRING_H
-# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
-#  include <memory.h>
-# endif
 # include <string.h>
 #endif
-#ifdef HAVE_STRINGS_H
-# include <strings.h>
-#endif
 #ifdef HAVE_INTTYPES_H
 # include <inttypes.h>
 #endif
 #ifdef HAVE_STDINT_H
 # include <stdint.h>
 #endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif"
 
+ac_header_c_list=
 ac_subst_vars='LTLIBOBJS
 LIBOBJS
 UNRLINCDIR
@@ -674,13 +701,13 @@ BUILDDIR
 DYNAMIC_LDFLAGS
 MODULEFLAGS
 CRYPTOLIB
+EGREP
+GREP
+CPP
 HARDEN_BINLDFLAGS
 HARDEN_BINCFLAGS
 HARDEN_LDFLAGS
 HARDEN_CFLAGS
-EGREP
-GREP
-CPP
 GMAKE
 MAKER
 OBJEXT
@@ -865,8 +892,6 @@ do
   *)    ac_optarg=yes ;;
   esac
 
-  # Accept the important Cygnus configure options, so we can diagnose typos.
-
   case $ac_dashdash$ac_option in
   --)
     ac_dashdash=yes ;;
@@ -907,9 +932,9 @@ do
     ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
     # Reject names that are not valid shell variable names.
     expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
-      as_fn_error $? "invalid feature name: $ac_useropt"
+      as_fn_error $? "invalid feature name: \`$ac_useropt'"
     ac_useropt_orig=$ac_useropt
-    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
     case $ac_user_opts in
       *"
 "enable_$ac_useropt"
@@ -933,9 +958,9 @@ do
     ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
     # Reject names that are not valid shell variable names.
     expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
-      as_fn_error $? "invalid feature name: $ac_useropt"
+      as_fn_error $? "invalid feature name: \`$ac_useropt'"
     ac_useropt_orig=$ac_useropt
-    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
     case $ac_user_opts in
       *"
 "enable_$ac_useropt"
@@ -1146,9 +1171,9 @@ do
     ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
     # Reject names that are not valid shell variable names.
     expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
-      as_fn_error $? "invalid package name: $ac_useropt"
+      as_fn_error $? "invalid package name: \`$ac_useropt'"
     ac_useropt_orig=$ac_useropt
-    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
     case $ac_user_opts in
       *"
 "with_$ac_useropt"
@@ -1162,9 +1187,9 @@ do
     ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
     # Reject names that are not valid shell variable names.
     expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
-      as_fn_error $? "invalid package name: $ac_useropt"
+      as_fn_error $? "invalid package name: \`$ac_useropt'"
     ac_useropt_orig=$ac_useropt
-    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'`
     case $ac_user_opts in
       *"
 "with_$ac_useropt"
@@ -1208,9 +1233,9 @@ Try \`$0 --help' for more information"
 
   *)
     # FIXME: should be removed in autoconf 3.0.
-    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2
     expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
-      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+      printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2
     : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
     ;;
 
@@ -1226,7 +1251,7 @@ if test -n "$ac_unrecognized_opts"; then
   case $enable_option_checking in
     no) ;;
     fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
-    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+    *)     printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
   esac
 fi
 
@@ -1290,7 +1315,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
 	 X"$as_myself" : 'X\(//\)[^/]' \| \
 	 X"$as_myself" : 'X\(//\)$' \| \
 	 X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X"$as_myself" |
+printf "%s\n" X"$as_myself" |
     sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
 	    s//\1/
 	    q
@@ -1347,7 +1372,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.4.2 to adapt to many kinds of systems.
+\`configure' configures unrealircd 6.1.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1413,7 +1438,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unrealircd 6.0.4.2:";;
+     short | recursive ) echo "Configuration of unrealircd 6.1.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1542,9 +1567,9 @@ if test "$ac_init_help" = "recursive"; then
 case "$ac_dir" in
 .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
 *)
-  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'`
   # A ".." for each directory in $ac_dir_suffix.
-  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
   case $ac_top_builddir_sub in
   "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
   *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
@@ -1572,7 +1597,8 @@ esac
 ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
 
     cd "$ac_dir" || { ac_status=$?; continue; }
-    # Check for guested configure.
+    # Check for configure.gnu first; this name is used for a wrapper for
+    # Metaconfig's "Configure" on case-insensitive file systems.
     if test -f "$ac_srcdir/configure.gnu"; then
       echo &&
       $SHELL "$ac_srcdir/configure.gnu" --help=recursive
@@ -1580,7 +1606,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
       echo &&
       $SHELL "$ac_srcdir/configure" --help=recursive
     else
-      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+      printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2
     fi || ac_status=$?
     cd "$ac_pwd" || { ac_status=$?; break; }
   done
@@ -1589,10 +1615,10 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unrealircd configure 6.0.4.2
-generated by GNU Autoconf 2.69
+unrealircd configure 6.1.0
+generated by GNU Autoconf 2.71
 
-Copyright (C) 2012 Free Software Foundation, Inc.
+Copyright (C) 2021 Free Software Foundation, Inc.
 This configure script is free software; the Free Software Foundation
 gives unlimited permission to copy, distribute and modify it.
 _ACEOF
@@ -1609,14 +1635,14 @@ fi
 ac_fn_c_try_compile ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  rm -f conftest.$ac_objext
+  rm -f conftest.$ac_objext conftest.beam
   if { { ac_try="$ac_compile"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_compile") 2>conftest.err
   ac_status=$?
   if test -s conftest.err; then
@@ -1624,14 +1650,15 @@ $as_echo "$ac_try_echo"; } >&5
     cat conftest.er1 >&5
     mv -f conftest.er1 conftest.err
   fi
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; } && {
 	 test -z "$ac_c_werror_flag" ||
 	 test ! -s conftest.err
-       } && test -s conftest.$ac_objext; then :
+       } && test -s conftest.$ac_objext
+then :
   ac_retval=0
-else
-  $as_echo "$as_me: failed program was:" >&5
+else $as_nop
+  printf "%s\n" "$as_me: failed program was:" >&5
 sed 's/^/| /' conftest.$ac_ext >&5
 
 	ac_retval=1
@@ -1647,14 +1674,14 @@ fi
 ac_fn_c_try_link ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  rm -f conftest.$ac_objext conftest$ac_exeext
+  rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext
   if { { ac_try="$ac_link"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_link") 2>conftest.err
   ac_status=$?
   if test -s conftest.err; then
@@ -1662,17 +1689,18 @@ $as_echo "$ac_try_echo"; } >&5
     cat conftest.er1 >&5
     mv -f conftest.er1 conftest.err
   fi
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; } && {
 	 test -z "$ac_c_werror_flag" ||
 	 test ! -s conftest.err
        } && test -s conftest$ac_exeext && {
 	 test "$cross_compiling" = yes ||
 	 test -x conftest$ac_exeext
-       }; then :
+       }
+then :
   ac_retval=0
-else
-  $as_echo "$as_me: failed program was:" >&5
+else $as_nop
+  printf "%s\n" "$as_me: failed program was:" >&5
 sed 's/^/| /' conftest.$ac_ext >&5
 
 	ac_retval=1
@@ -1689,8 +1717,8 @@ fi
 
 # ac_fn_c_try_run LINENO
 # ----------------------
-# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
-# that executables *can* be run.
+# Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that
+# executables *can* be run.
 ac_fn_c_try_run ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
@@ -1700,25 +1728,26 @@ case "(($ac_try" in
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_link") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
   { { case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_try") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; }; then :
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then :
   ac_retval=0
-else
-  $as_echo "$as_me: program exited with status $ac_status" >&5
-       $as_echo "$as_me: failed program was:" >&5
+else $as_nop
+  printf "%s\n" "$as_me: program exited with status $ac_status" >&5
+       printf "%s\n" "$as_me: failed program was:" >&5
 sed 's/^/| /' conftest.$ac_ext >&5
 
        ac_retval=$ac_status
@@ -1729,43 +1758,6 @@ fi
 
 } # ac_fn_c_try_run
 
-# ac_fn_c_try_cpp LINENO
-# ----------------------
-# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
-ac_fn_c_try_cpp ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if { { ac_try="$ac_cpp conftest.$ac_ext"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
-  ac_status=$?
-  if test -s conftest.err; then
-    grep -v '^ *+' conftest.err >conftest.er1
-    cat conftest.er1 >&5
-    mv -f conftest.er1 conftest.err
-  fi
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; } > conftest.i && {
-	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
-	 test ! -s conftest.err
-       }; then :
-  ac_retval=0
-else
-  $as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-    ac_retval=1
-fi
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-  as_fn_set_status $ac_retval
-
-} # ac_fn_c_try_cpp
-
 # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
 # -------------------------------------------------------
 # Tests whether HEADER exists and can be compiled using the include files in
@@ -1773,132 +1765,44 @@ fi
 ac_fn_c_check_header_compile ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-$as_echo_n "checking for $2... " >&6; }
-if eval \${$3+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 $4
 #include <$2>
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   eval "$3=yes"
-else
+else $as_nop
   eval "$3=no"
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 fi
 eval ac_res=\$$3
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
   eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
 
 } # ac_fn_c_check_header_compile
 
-# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
-# -------------------------------------------------------
-# Tests whether HEADER exists, giving a warning if it cannot be compiled using
-# the include files in INCLUDES and setting the cache variable VAR
-# accordingly.
-ac_fn_c_check_header_mongrel ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if eval \${$3+:} false; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-$as_echo_n "checking for $2... " >&6; }
-if eval \${$3+:} false; then :
-  $as_echo_n "(cached) " >&6
-fi
-eval ac_res=\$$3
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-else
-  # Is the header compilable?
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
-$as_echo_n "checking $2 usability... " >&6; }
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-#include <$2>
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_header_compiler=yes
-else
-  ac_header_compiler=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
-$as_echo "$ac_header_compiler" >&6; }
-
-# Is the header present?
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
-$as_echo_n "checking $2 presence... " >&6; }
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <$2>
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-  ac_header_preproc=yes
-else
-  ac_header_preproc=no
-fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
-$as_echo "$ac_header_preproc" >&6; }
-
-# So?  What about this header?
-case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
-  yes:no: )
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
-$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
-$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
-    ;;
-  no:yes:* )
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
-$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
-$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
-$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
-$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
-    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
-$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
-( $as_echo "## ------------------------------------------- ##
-## Report this to https://bugs.unrealircd.org/ ##
-## ------------------------------------------- ##"
-     ) | sed "s/^/$as_me: WARNING:     /" >&2
-    ;;
-esac
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-$as_echo_n "checking for $2... " >&6; }
-if eval \${$3+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  eval "$3=\$ac_header_compiler"
-fi
-eval ac_res=\$$3
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-fi
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_header_mongrel
-
 # ac_fn_c_check_func LINENO FUNC VAR
 # ----------------------------------
 # Tests whether FUNC exists, setting the cache variable VAR accordingly
 ac_fn_c_check_func ()
 {
   as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-$as_echo_n "checking for $2... " >&6; }
-if eval \${$3+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 /* Define $2 to an innocuous variant, in case <limits.h> declares $2.
@@ -1906,16 +1810,9 @@ else
 #define $2 innocuous_$2
 
 /* System header to define __stub macros and hopefully few prototypes,
-    which can conflict with char $2 (); below.
-    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
-    <limits.h> exists even on freestanding compilers.  */
-
-#ifdef __STDC__
-# include <limits.h>
-#else
-# include <assert.h>
-#endif
+   which can conflict with char $2 (); below.  */
 
+#include <limits.h>
 #undef $2
 
 /* Override any GCC internal prototype to avoid an error.
@@ -1933,35 +1830,94 @@ choke me
 #endif
 
 int
-main ()
+main (void)
 {
 return $2 ();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   eval "$3=yes"
-else
+else $as_nop
   eval "$3=no"
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 fi
 eval ac_res=\$$3
-	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
   eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
 
 } # ac_fn_c_check_func
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }
+then :
+  ac_retval=0
+else $as_nop
+  printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+ac_configure_args_raw=
+for ac_arg
+do
+  case $ac_arg in
+  *\'*)
+    ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+  esac
+  as_fn_append ac_configure_args_raw " '$ac_arg'"
+done
+
+case $ac_configure_args_raw in
+  *$as_nl*)
+    ac_safe_unquote= ;;
+  *)
+    ac_unsafe_z='|&;<>()$`\\"*?[ ''	' # This string ends in space, tab.
+    ac_unsafe_a="$ac_unsafe_z#~"
+    ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g"
+    ac_configure_args_raw=`      printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;;
+esac
+
 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.4.2, which was
-generated by GNU Autoconf 2.69.  Invocation command line was
+It was created by unrealircd $as_me 6.1.0, which was
+generated by GNU Autoconf 2.71.  Invocation command line was
 
-  $ $0 $@
+  $ $0$ac_configure_args_raw
 
 _ACEOF
 exec 5>>config.log
@@ -1994,8 +1950,12 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    $as_echo "PATH: $as_dir"
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    printf "%s\n" "PATH: $as_dir"
   done
 IFS=$as_save_IFS
 
@@ -2030,7 +1990,7 @@ do
     | -silent | --silent | --silen | --sile | --sil)
       continue ;;
     *\'*)
-      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+      ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
     esac
     case $ac_pass in
     1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
@@ -2065,11 +2025,13 @@ done
 # WARNING: Use '\'' to represent an apostrophe within the trap.
 # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
 trap 'exit_status=$?
+  # Sanitize IFS.
+  IFS=" ""	$as_nl"
   # Save into config.log some information that might help in debugging.
   {
     echo
 
-    $as_echo "## ---------------- ##
+    printf "%s\n" "## ---------------- ##
 ## Cache variables. ##
 ## ---------------- ##"
     echo
@@ -2080,8 +2042,8 @@ trap 'exit_status=$?
     case $ac_val in #(
     *${as_nl}*)
       case $ac_var in #(
-      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
-$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
       esac
       case $ac_var in #(
       _ | IFS | as_nl) ;; #(
@@ -2105,7 +2067,7 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
 )
     echo
 
-    $as_echo "## ----------------- ##
+    printf "%s\n" "## ----------------- ##
 ## Output variables. ##
 ## ----------------- ##"
     echo
@@ -2113,14 +2075,14 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
     do
       eval ac_val=\$$ac_var
       case $ac_val in
-      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
       esac
-      $as_echo "$ac_var='\''$ac_val'\''"
+      printf "%s\n" "$ac_var='\''$ac_val'\''"
     done | sort
     echo
 
     if test -n "$ac_subst_files"; then
-      $as_echo "## ------------------- ##
+      printf "%s\n" "## ------------------- ##
 ## File substitutions. ##
 ## ------------------- ##"
       echo
@@ -2128,15 +2090,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
       do
 	eval ac_val=\$$ac_var
 	case $ac_val in
-	*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+	*\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
 	esac
-	$as_echo "$ac_var='\''$ac_val'\''"
+	printf "%s\n" "$ac_var='\''$ac_val'\''"
       done | sort
       echo
     fi
 
     if test -s confdefs.h; then
-      $as_echo "## ----------- ##
+      printf "%s\n" "## ----------- ##
 ## confdefs.h. ##
 ## ----------- ##"
       echo
@@ -2144,8 +2106,8 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
       echo
     fi
     test "$ac_signal" != 0 &&
-      $as_echo "$as_me: caught signal $ac_signal"
-    $as_echo "$as_me: exit $exit_status"
+      printf "%s\n" "$as_me: caught signal $ac_signal"
+    printf "%s\n" "$as_me: exit $exit_status"
   } >&5
   rm -f core *.core core.conftest.* &&
     rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
@@ -2159,63 +2121,48 @@ ac_signal=0
 # confdefs.h avoids OS command line length limits that DEFS can exceed.
 rm -f -r conftest* confdefs.h
 
-$as_echo "/* confdefs.h */" > confdefs.h
+printf "%s\n" "/* confdefs.h */" > confdefs.h
 
 # Predefined preprocessor variables.
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_NAME "$PACKAGE_NAME"
-_ACEOF
+printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
-_ACEOF
+printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_VERSION "$PACKAGE_VERSION"
-_ACEOF
+printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_STRING "$PACKAGE_STRING"
-_ACEOF
+printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
-_ACEOF
+printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h
 
-cat >>confdefs.h <<_ACEOF
-#define PACKAGE_URL "$PACKAGE_URL"
-_ACEOF
+printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h
 
 
 # Let the site file select an alternate cache file if it wants to.
 # Prefer an explicitly selected file to automatically selected ones.
-ac_site_file1=NONE
-ac_site_file2=NONE
 if test -n "$CONFIG_SITE"; then
-  # We do not want a PATH search for config.site.
-  case $CONFIG_SITE in #((
-    -*)  ac_site_file1=./$CONFIG_SITE;;
-    */*) ac_site_file1=$CONFIG_SITE;;
-    *)   ac_site_file1=./$CONFIG_SITE;;
-  esac
+  ac_site_files="$CONFIG_SITE"
 elif test "x$prefix" != xNONE; then
-  ac_site_file1=$prefix/share/config.site
-  ac_site_file2=$prefix/etc/config.site
+  ac_site_files="$prefix/share/config.site $prefix/etc/config.site"
 else
-  ac_site_file1=$ac_default_prefix/share/config.site
-  ac_site_file2=$ac_default_prefix/etc/config.site
+  ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
 fi
-for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+
+for ac_site_file in $ac_site_files
 do
-  test "x$ac_site_file" = xNONE && continue
-  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
-    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
-$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+  case $ac_site_file in #(
+  */*) :
+     ;; #(
+  *) :
+    ac_site_file=./$ac_site_file ;;
+esac
+  if test -f "$ac_site_file" && test -r "$ac_site_file"; then
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;}
     sed 's/^/| /' "$ac_site_file" >&5
     . "$ac_site_file" \
-      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+      || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "failed to load site script $ac_site_file
 See \`config.log' for more details" "$LINENO" 5; }
   fi
@@ -2225,265 +2172,660 @@ if test -r "$cache_file"; then
   # Some versions of bash will fail to source /dev/null (special files
   # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
   if test /dev/null != "$cache_file" && test -f "$cache_file"; then
-    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
-$as_echo "$as_me: loading cache $cache_file" >&6;}
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+printf "%s\n" "$as_me: loading cache $cache_file" >&6;}
     case $cache_file in
       [\\/]* | ?:[\\/]* ) . "$cache_file";;
       *)                      . "./$cache_file";;
     esac
   fi
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
-$as_echo "$as_me: creating cache $cache_file" >&6;}
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+printf "%s\n" "$as_me: creating cache $cache_file" >&6;}
   >$cache_file
 fi
 
-# Check that the precious variables saved in the cache have kept the same
-# value.
-ac_cache_corrupted=false
-for ac_var in $ac_precious_vars; do
-  eval ac_old_set=\$ac_cv_env_${ac_var}_set
-  eval ac_new_set=\$ac_env_${ac_var}_set
-  eval ac_old_val=\$ac_cv_env_${ac_var}_value
-  eval ac_new_val=\$ac_env_${ac_var}_value
-  case $ac_old_set,$ac_new_set in
-    set,)
-      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
-$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
-      ac_cache_corrupted=: ;;
-    ,set)
-      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
-$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
-      ac_cache_corrupted=: ;;
-    ,);;
-    *)
-      if test "x$ac_old_val" != "x$ac_new_val"; then
-	# differences in whitespace do not lead to failure.
-	ac_old_val_w=`echo x $ac_old_val`
-	ac_new_val_w=`echo x $ac_new_val`
-	if test "$ac_old_val_w" != "$ac_new_val_w"; then
-	  { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
-$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
-	  ac_cache_corrupted=:
-	else
-	  { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
-$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
-	  eval $ac_var=\$ac_old_val
-	fi
-	{ $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
-$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
-	{ $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
-$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
-      fi;;
-  esac
-  # Pass precious variables to config.status.
-  if test "$ac_new_set" = set; then
-    case $ac_new_val in
-    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
-    *) ac_arg=$ac_var=$ac_new_val ;;
-    esac
-    case " $ac_configure_args " in
-      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
-      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
-    esac
-  fi
-done
-if $ac_cache_corrupted; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
-$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
-  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
-fi
-## -------------------- ##
-## Main body of script. ##
-## -------------------- ##
+# Test code for whether the C compiler supports C89 (global declarations)
+ac_c_conftest_c89_globals='
+/* Does the compiler advertise C89 conformance?
+   Do not test the value of __STDC__, because some compilers set it to 0
+   while being otherwise adequately conformant. */
+#if !defined __STDC__
+# error "Compiler does not advertise C89 conformance"
+#endif
 
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
+#include <stddef.h>
+#include <stdarg.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7 src/conf.sh.  */
+struct buf { int x; };
+struct buf * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
 
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not \xHH hex character constants.
+   These do not provoke an error unfortunately, instead are silently treated
+   as an "x".  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously \x00 != x always comes out true, for an
+   array size at least.  It is necessary to write \x00 == 0 to get something
+   that is true only with -std.  */
+int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1];
 
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) '\''x'\''
+int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1];
 
-ac_config_headers="$ac_config_headers include/setup.h"
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int),
+               int, int);'
 
-ac_aux_dir=
-for ac_dir in autoconf "$srcdir"/autoconf; do
-  if test -f "$ac_dir/install-sh"; then
-    ac_aux_dir=$ac_dir
-    ac_install_sh="$ac_aux_dir/install-sh -c"
-    break
-  elif test -f "$ac_dir/install.sh"; then
-    ac_aux_dir=$ac_dir
-    ac_install_sh="$ac_aux_dir/install.sh -c"
-    break
-  elif test -f "$ac_dir/shtool"; then
-    ac_aux_dir=$ac_dir
-    ac_install_sh="$ac_aux_dir/shtool install -c"
-    break
-  fi
-done
-if test -z "$ac_aux_dir"; then
-  as_fn_error $? "cannot find install-sh, install.sh, or shtool in autoconf \"$srcdir\"/autoconf" "$LINENO" 5
-fi
+# Test code for whether the C compiler supports C89 (body of main).
+ac_c_conftest_c89_main='
+ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]);
+'
 
-# These three variables are undocumented and unsupported,
-# and are intended to be withdrawn in a future Autoconf release.
-# They can cause serious problems if a builder's source tree is in a directory
-# whose full name contains unusual characters.
-ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
-ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
-ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+# Test code for whether the C compiler supports C99 (global declarations)
+ac_c_conftest_c99_globals='
+// Does the compiler advertise C99 conformance?
+#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# error "Compiler does not advertise C99 conformance"
+#endif
 
+#include <stdbool.h>
+extern int puts (const char *);
+extern int printf (const char *, ...);
+extern int dprintf (int, const char *, ...);
+extern void *malloc (size_t);
 
+// Check varargs macros.  These examples are taken from C99 6.10.3.5.
+// dprintf is used instead of fprintf to avoid needing to declare
+// FILE and stderr.
+#define debug(...) dprintf (2, __VA_ARGS__)
+#define showlist(...) puts (#__VA_ARGS__)
+#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
+static void
+test_varargs_macros (void)
+{
+  int x = 1234;
+  int y = 5678;
+  debug ("Flag");
+  debug ("X = %d\n", x);
+  showlist (The first, second, and third items.);
+  report (x>y, "x is %d but y is %d", x, y);
+}
 
+// Check long long types.
+#define BIG64 18446744073709551615ull
+#define BIG32 4294967295ul
+#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
+#if !BIG_OK
+  #error "your preprocessor is broken"
+#endif
+#if BIG_OK
+#else
+  #error "your preprocessor is broken"
+#endif
+static long long int bignum = -9223372036854775807LL;
+static unsigned long long int ubignum = BIG64;
 
-if test "x$enable_dynamic_linking" = "x"; then
-	echo "Please use ./Config instead of ./configure"
-	exit 1
-fi
+struct incomplete_array
+{
+  int datasize;
+  double data[];
+};
 
-orig_cflags="$CFLAGS"
+struct named_init {
+  int number;
+  const wchar_t *name;
+  double average;
+};
 
-BUILDDIR_NOW="`pwd`"
+typedef const char *ccp;
 
-# Generation version number (e.g.: X in X.Y.Z)
-UNREAL_VERSION_GENERATION="6"
+static inline int
+test_restrict (ccp restrict text)
+{
+  // See if C++-style comments work.
+  // Iterate through items via the restricted pointer.
+  // Also check for declarations in for loops.
+  for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i)
+    continue;
+  return 0;
+}
 
-cat >>confdefs.h <<_ACEOF
-#define UNREAL_VERSION_GENERATION $UNREAL_VERSION_GENERATION
-_ACEOF
+// Check varargs and va_copy.
+static bool
+test_varargs (const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  va_list args_copy;
+  va_copy (args_copy, args);
 
+  const char *str = "";
+  int number = 0;
+  float fnumber = 0;
 
-# Major version number (e.g.: Y in X.Y.Z)
-UNREAL_VERSION_MAJOR="0"
+  while (*format)
+    {
+      switch (*format++)
+	{
+	case '\''s'\'': // string
+	  str = va_arg (args_copy, const char *);
+	  break;
+	case '\''d'\'': // int
+	  number = va_arg (args_copy, int);
+	  break;
+	case '\''f'\'': // float
+	  fnumber = va_arg (args_copy, double);
+	  break;
+	default:
+	  break;
+	}
+    }
+  va_end (args_copy);
+  va_end (args);
 
-cat >>confdefs.h <<_ACEOF
-#define UNREAL_VERSION_MAJOR $UNREAL_VERSION_MAJOR
-_ACEOF
+  return *str && number && fnumber;
+}
+'
 
+# Test code for whether the C compiler supports C99 (body of main).
+ac_c_conftest_c99_main='
+  // Check bool.
+  _Bool success = false;
+  success |= (argc != 0);
 
-# Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR="4"
+  // Check restrict.
+  if (test_restrict ("String literal") == 0)
+    success = true;
+  char *restrict newvar = "Another string";
 
-cat >>confdefs.h <<_ACEOF
-#define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR
-_ACEOF
+  // Check varargs.
+  success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234);
+  test_varargs_macros ();
 
+  // Check flexible array members.
+  struct incomplete_array *ia =
+    malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
+  ia->datasize = 10;
+  for (int i = 0; i < ia->datasize; ++i)
+    ia->data[i] = i * 1.234;
 
-# 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=".2"
+  // Check named initializers.
+  struct named_init ni = {
+    .number = 34,
+    .name = L"Test wide string",
+    .average = 543.34343,
+  };
 
-cat >>confdefs.h <<_ACEOF
-#define UNREAL_VERSION_SUFFIX "$UNREAL_VERSION_SUFFIX"
-_ACEOF
+  ni.number = 58;
 
+  int dynamic_array[ni.number];
+  dynamic_array[0] = argv[0][0];
+  dynamic_array[ni.number - 1] = 543;
 
-# Extract the first word of "rm", so it can be a program name with args.
-set dummy rm; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_RM+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $RM in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_RM="$RM" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_RM="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
+  // work around unused variable warnings
+  ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\''
+	 || dynamic_array[ni.number - 1] != 543);
+'
 
-  ;;
-esac
-fi
-RM=$ac_cv_path_RM
-if test -n "$RM"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RM" >&5
-$as_echo "$RM" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
+# Test code for whether the C compiler supports C11 (global declarations)
+ac_c_conftest_c11_globals='
+// Does the compiler advertise C11 conformance?
+#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L
+# error "Compiler does not advertise C11 conformance"
+#endif
 
+// Check _Alignas.
+char _Alignas (double) aligned_as_double;
+char _Alignas (0) no_special_alignment;
+extern char aligned_as_int;
+char _Alignas (0) _Alignas (int) aligned_as_int;
 
-# Extract the first word of "cp", so it can be a program name with args.
-set dummy cp; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_CP+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $CP in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_CP="$CP" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
+// Check _Alignof.
+enum
+{
+  int_alignment = _Alignof (int),
+  int_array_alignment = _Alignof (int[100]),
+  char_alignment = _Alignof (char)
+};
+_Static_assert (0 < -_Alignof (int), "_Alignof is signed");
+
+// Check _Noreturn.
+int _Noreturn does_not_return (void) { for (;;) continue; }
+
+// Check _Static_assert.
+struct test_static_assert
+{
+  int x;
+  _Static_assert (sizeof (int) <= sizeof (long int),
+                  "_Static_assert does not work in struct");
+  long int y;
+};
+
+// Check UTF-8 literals.
+#define u8 syntax error!
+char const utf8_literal[] = u8"happens to be ASCII" "another string";
+
+// Check duplicate typedefs.
+typedef long *long_ptr;
+typedef long int *long_ptr;
+typedef long_ptr long_ptr;
+
+// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1.
+struct anonymous
+{
+  union {
+    struct { int i; int j; };
+    struct { int k; long int l; } w;
+  };
+  int m;
+} v1;
+'
+
+# Test code for whether the C compiler supports C11 (body of main).
+ac_c_conftest_c11_main='
+  _Static_assert ((offsetof (struct anonymous, i)
+		   == offsetof (struct anonymous, w.k)),
+		  "Anonymous union alignment botch");
+  v1.i = 2;
+  v1.w.k = 5;
+  ok |= v1.i != 5;
+'
+
+# Test code for whether the C compiler supports C11 (complete).
+ac_c_conftest_c11_program="${ac_c_conftest_c89_globals}
+${ac_c_conftest_c99_globals}
+${ac_c_conftest_c11_globals}
+
+int
+main (int argc, char **argv)
+{
+  int ok = 0;
+  ${ac_c_conftest_c89_main}
+  ${ac_c_conftest_c99_main}
+  ${ac_c_conftest_c11_main}
+  return ok;
+}
+"
+
+# Test code for whether the C compiler supports C99 (complete).
+ac_c_conftest_c99_program="${ac_c_conftest_c89_globals}
+${ac_c_conftest_c99_globals}
+
+int
+main (int argc, char **argv)
+{
+  int ok = 0;
+  ${ac_c_conftest_c89_main}
+  ${ac_c_conftest_c99_main}
+  return ok;
+}
+"
+
+# Test code for whether the C compiler supports C89 (complete).
+ac_c_conftest_c89_program="${ac_c_conftest_c89_globals}
+
+int
+main (int argc, char **argv)
+{
+  int ok = 0;
+  ${ac_c_conftest_c89_main}
+  return ok;
+}
+"
+
+as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H"
+as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H"
+as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H"
+as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H"
+as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H"
+as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H"
+as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H"
+as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H"
+as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H"
+
+# Auxiliary files required by this configure script.
+ac_aux_files="config.guess config.sub"
+
+# Locations in which to look for auxiliary files.
+ac_aux_dir_candidates="${srcdir}/autoconf"
+
+# Search for a directory containing all of the required auxiliary files,
+# $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates.
+# If we don't find one directory that contains all the files we need,
+# we report the set of missing files from the *first* directory in
+# $ac_aux_dir_candidates and give up.
+ac_missing_aux_files=""
+ac_first_candidate=:
+printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in $ac_aux_dir_candidates
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_CP="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+  as_found=:
+
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}:  trying $as_dir" >&5
+  ac_aux_dir_found=yes
+  ac_install_sh=
+  for ac_aux in $ac_aux_files
+  do
+    # As a special case, if "install-sh" is required, that requirement
+    # can be satisfied by any of "install-sh", "install.sh", or "shtool",
+    # and $ac_install_sh is set appropriately for whichever one is found.
+    if test x"$ac_aux" = x"install-sh"
+    then
+      if test -f "${as_dir}install-sh"; then
+        printf "%s\n" "$as_me:${as_lineno-$LINENO}:   ${as_dir}install-sh found" >&5
+        ac_install_sh="${as_dir}install-sh -c"
+      elif test -f "${as_dir}install.sh"; then
+        printf "%s\n" "$as_me:${as_lineno-$LINENO}:   ${as_dir}install.sh found" >&5
+        ac_install_sh="${as_dir}install.sh -c"
+      elif test -f "${as_dir}shtool"; then
+        printf "%s\n" "$as_me:${as_lineno-$LINENO}:   ${as_dir}shtool found" >&5
+        ac_install_sh="${as_dir}shtool install -c"
+      else
+        ac_aux_dir_found=no
+        if $ac_first_candidate; then
+          ac_missing_aux_files="${ac_missing_aux_files} install-sh"
+        else
+          break
+        fi
+      fi
+    else
+      if test -f "${as_dir}${ac_aux}"; then
+        printf "%s\n" "$as_me:${as_lineno-$LINENO}:   ${as_dir}${ac_aux} found" >&5
+      else
+        ac_aux_dir_found=no
+        if $ac_first_candidate; then
+          ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}"
+        else
+          break
+        fi
+      fi
+    fi
+  done
+  if test "$ac_aux_dir_found" = yes; then
+    ac_aux_dir="$as_dir"
+    break
   fi
+  ac_first_candidate=false
+
+  as_found=false
 done
-  done
 IFS=$as_save_IFS
+if $as_found
+then :
 
-  ;;
-esac
-fi
-CP=$ac_cv_path_CP
-if test -n "$CP"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CP" >&5
-$as_echo "$CP" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+else $as_nop
+  as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5
 fi
 
 
-# Extract the first word of "touch", so it can be a program name with args.
-set dummy touch; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_TOUCH+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $TOUCH in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_TOUCH="$TOUCH" # Let the user override the test with a path.
-  ;;
-  *)
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+if test -f "${ac_aux_dir}config.guess"; then
+  ac_config_guess="$SHELL ${ac_aux_dir}config.guess"
+fi
+if test -f "${ac_aux_dir}config.sub"; then
+  ac_config_sub="$SHELL ${ac_aux_dir}config.sub"
+fi
+if test -f "$ac_aux_dir/configure"; then
+  ac_configure="$SHELL ${ac_aux_dir}configure"
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+	# differences in whitespace do not lead to failure.
+	ac_old_val_w=`echo x $ac_old_val`
+	ac_new_val_w=`echo x $ac_new_val`
+	if test "$ac_old_val_w" != "$ac_new_val_w"; then
+	  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+	  ac_cache_corrupted=:
+	else
+	  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+	  eval $ac_var=\$ac_old_val
+	fi
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+printf "%s\n" "$as_me:   former value:  \`$ac_old_val'" >&2;}
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+printf "%s\n" "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file'
+	    and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ac_config_headers="$ac_config_headers include/setup.h"
+
+
+
+
+if test "x$enable_dynamic_linking" = "x"; then
+	echo "Please use ./Config instead of ./configure"
+	exit 1
+fi
+
+orig_cflags="$CFLAGS"
+
+BUILDDIR_NOW="`pwd`"
+
+# Generation version number (e.g.: X in X.Y.Z)
+UNREAL_VERSION_GENERATION="6"
+
+printf "%s\n" "#define UNREAL_VERSION_GENERATION $UNREAL_VERSION_GENERATION" >>confdefs.h
+
+
+# Major version number (e.g.: Y in X.Y.Z)
+UNREAL_VERSION_MAJOR="1"
+
+printf "%s\n" "#define UNREAL_VERSION_MAJOR $UNREAL_VERSION_MAJOR" >>confdefs.h
+
+
+# Minor version number (e.g.: Z in X.Y.Z)
+UNREAL_VERSION_MINOR="0"
+
+printf "%s\n" "#define UNREAL_VERSION_MINOR $UNREAL_VERSION_MINOR" >>confdefs.h
+
+
+# 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=""
+
+printf "%s\n" "#define UNREAL_VERSION_SUFFIX \"$UNREAL_VERSION_SUFFIX\"" >>confdefs.h
+
+
+# Extract the first word of "rm", so it can be a program name with args.
+set dummy rm; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_RM+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  case $RM in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_RM="$RM" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_RM="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+RM=$ac_cv_path_RM
+if test -n "$RM"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RM" >&5
+printf "%s\n" "$RM" >&6; }
+else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+# Extract the first word of "cp", so it can be a program name with args.
+set dummy cp; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_CP+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  case $CP in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_CP="$CP" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_CP="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+CP=$ac_cv_path_CP
+if test -n "$CP"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CP" >&5
+printf "%s\n" "$CP" >&6; }
+else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+# Extract the first word of "touch", so it can be a program name with args.
+set dummy touch; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_TOUCH+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  case $TOUCH in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_TOUCH="$TOUCH" # Let the user override the test with a path.
+  ;;
+  *)
   as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_TOUCH="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_TOUCH="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2495,21 +2837,22 @@ esac
 fi
 TOUCH=$ac_cv_path_TOUCH
 if test -n "$TOUCH"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TOUCH" >&5
-$as_echo "$TOUCH" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TOUCH" >&5
+printf "%s\n" "$TOUCH" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
 # Extract the first word of "openssl", so it can be a program name with args.
 set dummy openssl; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_OPENSSLPATH+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_OPENSSLPATH+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $OPENSSLPATH in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_OPENSSLPATH="$OPENSSLPATH" # Let the user override the test with a path.
@@ -2519,11 +2862,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_OPENSSLPATH="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_OPENSSLPATH="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2535,15 +2882,16 @@ esac
 fi
 OPENSSLPATH=$ac_cv_path_OPENSSLPATH
 if test -n "$OPENSSLPATH"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OPENSSLPATH" >&5
-$as_echo "$OPENSSLPATH" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OPENSSLPATH" >&5
+printf "%s\n" "$OPENSSLPATH" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
-if test x"$OPENSSLPATH" = "x"; then :
+if test x"$OPENSSLPATH" = "x"
+then :
 
 echo ""
 echo "Apparently you do not have both the openssl binary and openssl development libraries installed."
@@ -2558,11 +2906,12 @@ fi
 
 # Extract the first word of "install", so it can be a program name with args.
 set dummy install; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_INSTALL+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_INSTALL+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $INSTALL in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_INSTALL="$INSTALL" # Let the user override the test with a path.
@@ -2572,11 +2921,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_INSTALL="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_INSTALL="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2588,21 +2941,22 @@ esac
 fi
 INSTALL=$ac_cv_path_INSTALL
 if test -n "$INSTALL"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
-$as_echo "$INSTALL" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+printf "%s\n" "$INSTALL" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
 # Extract the first word of "gunzip", so it can be a program name with args.
 set dummy gunzip; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_GUNZIP+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_GUNZIP+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $GUNZIP in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_GUNZIP="$GUNZIP" # Let the user override the test with a path.
@@ -2612,11 +2966,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_GUNZIP="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_GUNZIP="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2628,21 +2986,22 @@ esac
 fi
 GUNZIP=$ac_cv_path_GUNZIP
 if test -n "$GUNZIP"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GUNZIP" >&5
-$as_echo "$GUNZIP" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GUNZIP" >&5
+printf "%s\n" "$GUNZIP" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
 # Extract the first word of "pkg-config", so it can be a program name with args.
 set dummy pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PKGCONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_PKGCONFIG+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $PKGCONFIG in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_PKGCONFIG="$PKGCONFIG" # Let the user override the test with a path.
@@ -2652,11 +3011,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_PKGCONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKGCONFIG="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2668,15 +3031,24 @@ esac
 fi
 PKGCONFIG=$ac_cv_path_PKGCONFIG
 if test -n "$PKGCONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG" >&5
-$as_echo "$PKGCONFIG" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG" >&5
+printf "%s\n" "$PKGCONFIG" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
 
+
+
+
+
+
+
+
+
+
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -2685,11 +3057,12 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 if test -n "$ac_tool_prefix"; then
   # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
 set dummy ${ac_tool_prefix}gcc; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$CC"; then
   ac_cv_prog_CC="$CC" # Let the user override the test.
 else
@@ -2697,11 +3070,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="${ac_tool_prefix}gcc"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2712,11 +3089,11 @@ fi
 fi
 CC=$ac_cv_prog_CC
 if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -2725,11 +3102,12 @@ if test -z "$ac_cv_prog_CC"; then
   ac_ct_CC=$CC
   # Extract the first word of "gcc", so it can be a program name with args.
 set dummy gcc; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$ac_ct_CC"; then
   ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
 else
@@ -2737,11 +3115,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_ac_ct_CC="gcc"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2752,11 +3134,11 @@ fi
 fi
 ac_ct_CC=$ac_cv_prog_ac_ct_CC
 if test -n "$ac_ct_CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
-$as_echo "$ac_ct_CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
   if test "x$ac_ct_CC" = x; then
@@ -2764,8 +3146,8 @@ fi
   else
     case $cross_compiling:$ac_tool_warned in
 yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
 ac_tool_warned=yes ;;
 esac
     CC=$ac_ct_CC
@@ -2778,11 +3160,12 @@ if test -z "$CC"; then
           if test -n "$ac_tool_prefix"; then
     # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
 set dummy ${ac_tool_prefix}cc; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$CC"; then
   ac_cv_prog_CC="$CC" # Let the user override the test.
 else
@@ -2790,11 +3173,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="${ac_tool_prefix}cc"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2805,11 +3192,11 @@ fi
 fi
 CC=$ac_cv_prog_CC
 if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -2818,11 +3205,12 @@ fi
 if test -z "$CC"; then
   # Extract the first word of "cc", so it can be a program name with args.
 set dummy cc; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$CC"; then
   ac_cv_prog_CC="$CC" # Let the user override the test.
 else
@@ -2831,15 +3219,19 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
        ac_prog_rejected=yes
        continue
      fi
     ac_cv_prog_CC="cc"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2855,18 +3247,18 @@ if test $ac_prog_rejected = yes; then
     # However, it has the same basename, so the bogon will be chosen
     # first if we set CC to just the basename; use the full file name.
     shift
-    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+    ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@"
   fi
 fi
 fi
 fi
 CC=$ac_cv_prog_CC
 if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -2877,11 +3269,12 @@ if test -z "$CC"; then
   do
     # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
 set dummy $ac_tool_prefix$ac_prog; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$CC"; then
   ac_cv_prog_CC="$CC" # Let the user override the test.
 else
@@ -2889,11 +3282,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2904,11 +3301,11 @@ fi
 fi
 CC=$ac_cv_prog_CC
 if test -n "$CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
-$as_echo "$CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -2921,11 +3318,12 @@ if test -z "$CC"; then
 do
   # Extract the first word of "$ac_prog", so it can be a program name with args.
 set dummy $ac_prog; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$ac_ct_CC"; then
   ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
 else
@@ -2933,11 +3331,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_ac_ct_CC="$ac_prog"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -2948,11 +3350,11 @@ fi
 fi
 ac_ct_CC=$ac_cv_prog_ac_ct_CC
 if test -n "$ac_ct_CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
-$as_echo "$ac_ct_CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -2964,34 +3366,138 @@ done
   else
     case $cross_compiling:$ac_tool_warned in
 yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args.
+set dummy ${ac_tool_prefix}clang; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}clang"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+printf "%s\n" "$CC" >&6; }
+else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "clang", so it can be a program name with args.
+set dummy clang; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ac_ct_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="clang"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+printf "%s\n" "$ac_ct_CC" >&6; }
+else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
 ac_tool_warned=yes ;;
 esac
     CC=$ac_ct_CC
   fi
+else
+  CC="$ac_cv_prog_CC"
 fi
 
 fi
 
 
-test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "no acceptable C compiler found in \$PATH
 See \`config.log' for more details" "$LINENO" 5; }
 
 # Provide some information about the compiler.
-$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
 set X $ac_compile
 ac_compiler=$2
-for ac_option in --version -v -V -qversion; do
+for ac_option in --version -v -V -qversion -version; do
   { { ac_try="$ac_compiler $ac_option >&5"
 case "(($ac_try" in
   *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_compiler $ac_option >&5") 2>conftest.err
   ac_status=$?
   if test -s conftest.err; then
@@ -3001,7 +3507,7 @@ $as_echo "$ac_try_echo"; } >&5
     cat conftest.er1 >&5
   fi
   rm -f conftest.er1 conftest.err
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }
 done
 
@@ -3009,7 +3515,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
@@ -3021,9 +3527,9 @@ ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
 # Try to create an executable without -o first, disregard a.out.
 # It will help us diagnose broken compilers, and finding out an intuition
 # of exeext.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
-$as_echo_n "checking whether the C compiler works... " >&6; }
-ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+printf %s "checking whether the C compiler works... " >&6; }
+ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
 
 # The possible output files:
 ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
@@ -3044,11 +3550,12 @@ case "(($ac_try" in
   *) ac_try_echo=$ac_try;;
 esac
 eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
+printf "%s\n" "$ac_try_echo"; } >&5
   (eval "$ac_link_default") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then :
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+then :
   # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
 # So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
 # in a Makefile.  We should not override ac_cv_exeext if it was cached,
@@ -3065,7 +3572,7 @@ do
 	# certainly right.
 	break;;
     *.* )
-	if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+	if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no;
 	then :; else
 	   ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
 	fi
@@ -3076,886 +3583,450 @@ do
 	# actually working.
 	break;;
     * )
-	break;;
-  esac
-done
-test "$ac_cv_exeext" = no && ac_cv_exeext=
-
-else
-  ac_file=''
-fi
-if test -z "$ac_file"; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-$as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error 77 "C compiler cannot create executables
-See \`config.log' for more details" "$LINENO" 5; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
-$as_echo_n "checking for C compiler default output file name... " >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
-$as_echo "$ac_file" >&6; }
-ac_exeext=$ac_cv_exeext
-
-rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
-ac_clean_files=$ac_clean_files_save
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
-$as_echo_n "checking for suffix of executables... " >&6; }
-if { { ac_try="$ac_link"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_link") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then :
-  # If both `conftest.exe' and `conftest' are `present' (well, observable)
-# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
-# work properly (i.e., refer to `conftest.exe'), while it won't with
-# `rm'.
-for ac_file in conftest.exe conftest conftest.*; do
-  test -f "$ac_file" || continue
-  case $ac_file in
-    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
-    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
-	  break;;
-    * ) break;;
-  esac
-done
-else
-  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "cannot compute suffix of executables: cannot compile and link
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-rm -f conftest conftest$ac_cv_exeext
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
-$as_echo "$ac_cv_exeext" >&6; }
-
-rm -f conftest.$ac_ext
-EXEEXT=$ac_cv_exeext
-ac_exeext=$EXEEXT
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdio.h>
-int
-main ()
-{
-FILE *f = fopen ("conftest.out", "w");
- return ferror (f) || fclose (f) != 0;
-
-  ;
-  return 0;
-}
-_ACEOF
-ac_clean_files="$ac_clean_files conftest.out"
-# Check that the compiler produces executables we can run.  If not, either
-# the compiler is broken, or we cross compile.
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
-$as_echo_n "checking whether we are cross compiling... " >&6; }
-if test "$cross_compiling" != yes; then
-  { { ac_try="$ac_link"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_link") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }
-  if { ac_try='./conftest$ac_cv_exeext'
-  { { case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_try") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; }; then
-    cross_compiling=no
-  else
-    if test "$cross_compiling" = maybe; then
-	cross_compiling=yes
-    else
-	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "cannot run C compiled programs.
-If you meant to cross compile, use \`--host'.
-See \`config.log' for more details" "$LINENO" 5; }
-    fi
-  fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
-$as_echo "$cross_compiling" >&6; }
-
-rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
-ac_clean_files=$ac_clean_files_save
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
-$as_echo_n "checking for suffix of object files... " >&6; }
-if ${ac_cv_objext+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-rm -f conftest.o conftest.obj
-if { { ac_try="$ac_compile"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_compile") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then :
-  for ac_file in conftest.o conftest.obj conftest.*; do
-  test -f "$ac_file" || continue;
-  case $ac_file in
-    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
-    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
-       break;;
-  esac
-done
-else
-  $as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "cannot compute suffix of object files: cannot compile
-See \`config.log' for more details" "$LINENO" 5; }
-fi
-rm -f conftest.$ac_cv_objext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
-$as_echo "$ac_cv_objext" >&6; }
-OBJEXT=$ac_cv_objext
-ac_objext=$OBJEXT
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
-$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
-if ${ac_cv_c_compiler_gnu+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
-#ifndef __GNUC__
-       choke me
-#endif
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_compiler_gnu=yes
-else
-  ac_compiler_gnu=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-ac_cv_c_compiler_gnu=$ac_compiler_gnu
-
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
-$as_echo "$ac_cv_c_compiler_gnu" >&6; }
-if test $ac_compiler_gnu = yes; then
-  GCC=yes
-else
-  GCC=
-fi
-ac_test_CFLAGS=${CFLAGS+set}
-ac_save_CFLAGS=$CFLAGS
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
-$as_echo_n "checking whether $CC accepts -g... " >&6; }
-if ${ac_cv_prog_cc_g+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_save_c_werror_flag=$ac_c_werror_flag
-   ac_c_werror_flag=yes
-   ac_cv_prog_cc_g=no
-   CFLAGS="-g"
-   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_g=yes
-else
-  CFLAGS=""
-      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-
-else
-  ac_c_werror_flag=$ac_save_c_werror_flag
-	 CFLAGS="-g"
-	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_g=yes
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-   ac_c_werror_flag=$ac_save_c_werror_flag
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
-$as_echo "$ac_cv_prog_cc_g" >&6; }
-if test "$ac_test_CFLAGS" = set; then
-  CFLAGS=$ac_save_CFLAGS
-elif test $ac_cv_prog_cc_g = yes; then
-  if test "$GCC" = yes; then
-    CFLAGS="-g -O2"
-  else
-    CFLAGS="-g"
-  fi
-else
-  if test "$GCC" = yes; then
-    CFLAGS="-O2"
-  else
-    CFLAGS=
-  fi
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
-$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
-if ${ac_cv_prog_cc_c89+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_cv_prog_cc_c89=no
-ac_save_CC=$CC
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdarg.h>
-#include <stdio.h>
-struct stat;
-/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
-struct buf { int x; };
-FILE * (*rcsopen) (struct buf *, struct stat *, int);
-static char *e (p, i)
-     char **p;
-     int i;
-{
-  return p[i];
-}
-static char *f (char * (*g) (char **, int), char **p, ...)
-{
-  char *s;
-  va_list v;
-  va_start (v,p);
-  s = g (p, va_arg (v,int));
-  va_end (v);
-  return s;
-}
-
-/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
-   function prototypes and stuff, but not '\xHH' hex character constants.
-   These don't provoke an error unfortunately, instead are silently treated
-   as 'x'.  The following induces an error, until -std is added to get
-   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
-   array size at least.  It's necessary to write '\x00'==0 to get something
-   that's true only with -std.  */
-int osf4_cc_array ['\x00' == 0 ? 1 : -1];
-
-/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
-   inside strings and character constants.  */
-#define FOO(x) 'x'
-int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
-
-int test (int i, double x);
-struct s1 {int (*f) (int a);};
-struct s2 {int (*f) (double a);};
-int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
-int argc;
-char **argv;
-int
-main ()
-{
-return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
-  ;
-  return 0;
-}
-_ACEOF
-for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
-	-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
-do
-  CC="$ac_save_CC $ac_arg"
-  if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_c89=$ac_arg
-fi
-rm -f core conftest.err conftest.$ac_objext
-  test "x$ac_cv_prog_cc_c89" != "xno" && break
-done
-rm -f conftest.$ac_ext
-CC=$ac_save_CC
-
-fi
-# AC_CACHE_VAL
-case "x$ac_cv_prog_cc_c89" in
-  x)
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
-$as_echo "none needed" >&6; } ;;
-  xno)
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-$as_echo "unsupported" >&6; } ;;
-  *)
-    CC="$CC $ac_cv_prog_cc_c89"
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
-$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
-esac
-if test "x$ac_cv_prog_cc_c89" != xno; then :
-
-fi
-
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-
-   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5
-$as_echo_n "checking for $CC option to accept ISO C99... " >&6; }
-if ${ac_cv_prog_cc_c99+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_cv_prog_cc_c99=no
-ac_save_CC=$CC
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <wchar.h>
-#include <stdio.h>
-
-// Check varargs macros.  These examples are taken from C99 6.10.3.5.
-#define debug(...) fprintf (stderr, __VA_ARGS__)
-#define showlist(...) puts (#__VA_ARGS__)
-#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__))
-static void
-test_varargs_macros (void)
-{
-  int x = 1234;
-  int y = 5678;
-  debug ("Flag");
-  debug ("X = %d\n", x);
-  showlist (The first, second, and third items.);
-  report (x>y, "x is %d but y is %d", x, y);
-}
-
-// Check long long types.
-#define BIG64 18446744073709551615ull
-#define BIG32 4294967295ul
-#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0)
-#if !BIG_OK
-  your preprocessor is broken;
-#endif
-#if BIG_OK
-#else
-  your preprocessor is broken;
-#endif
-static long long int bignum = -9223372036854775807LL;
-static unsigned long long int ubignum = BIG64;
-
-struct incomplete_array
-{
-  int datasize;
-  double data[];
-};
-
-struct named_init {
-  int number;
-  const wchar_t *name;
-  double average;
-};
-
-typedef const char *ccp;
-
-static inline int
-test_restrict (ccp restrict text)
-{
-  // See if C++-style comments work.
-  // Iterate through items via the restricted pointer.
-  // Also check for declarations in for loops.
-  for (unsigned int i = 0; *(text+i) != '\0'; ++i)
-    continue;
-  return 0;
-}
-
-// Check varargs and va_copy.
-static void
-test_varargs (const char *format, ...)
-{
-  va_list args;
-  va_start (args, format);
-  va_list args_copy;
-  va_copy (args_copy, args);
-
-  const char *str;
-  int number;
-  float fnumber;
-
-  while (*format)
-    {
-      switch (*format++)
-	{
-	case 's': // string
-	  str = va_arg (args_copy, const char *);
-	  break;
-	case 'd': // int
-	  number = va_arg (args_copy, int);
-	  break;
-	case 'f': // float
-	  fnumber = va_arg (args_copy, double);
-	  break;
-	default:
-	  break;
-	}
-    }
-  va_end (args_copy);
-  va_end (args);
-}
-
-int
-main ()
-{
-
-  // Check bool.
-  _Bool success = false;
-
-  // Check restrict.
-  if (test_restrict ("String literal") == 0)
-    success = true;
-  char *restrict newvar = "Another string";
-
-  // Check varargs.
-  test_varargs ("s, d' f .", "string", 65, 34.234);
-  test_varargs_macros ();
-
-  // Check flexible array members.
-  struct incomplete_array *ia =
-    malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10));
-  ia->datasize = 10;
-  for (int i = 0; i < ia->datasize; ++i)
-    ia->data[i] = i * 1.234;
+	break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
 
-  // Check named initializers.
-  struct named_init ni = {
-    .number = 34,
-    .name = L"Test wide string",
-    .average = 543.34343,
-  };
+else $as_nop
+  ac_file=''
+fi
+if test -z "$ac_file"
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
 
-  ni.number = 58;
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+printf %s "checking for C compiler default output file name... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+printf "%s\n" "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
 
-  int dynamic_array[ni.number];
-  dynamic_array[ni.number - 1] = 543;
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+printf %s "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	  break;;
+    * ) break;;
+  esac
+done
+else $as_nop
+  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+printf "%s\n" "$ac_cv_exeext" >&6; }
 
-  // work around unused variable warnings
-  return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x'
-	  || dynamic_array[ni.number - 1] != 543);
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main (void)
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
 
   ;
   return 0;
 }
 _ACEOF
-for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99
-do
-  CC="$ac_save_CC $ac_arg"
-  if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_prog_cc_c99=$ac_arg
-fi
-rm -f core conftest.err conftest.$ac_objext
-  test "x$ac_cv_prog_cc_c99" != "xno" && break
-done
-rm -f conftest.$ac_ext
-CC=$ac_save_CC
-
-fi
-# AC_CACHE_VAL
-case "x$ac_cv_prog_cc_c99" in
-  x)
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
-$as_echo "none needed" >&6; } ;;
-  xno)
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-$as_echo "unsupported" >&6; } ;;
-  *)
-    CC="$CC $ac_cv_prog_cc_c99"
-    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
-$as_echo "$ac_cv_prog_cc_c99" >&6; } ;;
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+printf %s "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
 esac
-if test "x$ac_cv_prog_cc_c99" != xno; then :
-
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+	cross_compiling=yes
+    else
+	{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
 fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+printf "%s\n" "$cross_compiling" >&6; }
 
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+printf %s "checking for suffix of object files... " >&6; }
+if test ${ac_cv_objext+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
 
-if test "$ac_cv_prog_cc_c99" = "no"; then :
-  as_fn_error $? "No C99 compiler was found. Please install gcc or clang and other build tools. Eg, on Debian/Ubuntu you probably want to run the following as root: apt-get install build-essential " "$LINENO" 5
-fi
+int
+main (void)
+{
 
-# Extract the first word of "gmake", so it can be a program name with args.
-set dummy gmake; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_MAKER+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  if test -n "$MAKER"; then
-  ac_cv_prog_MAKER="$MAKER" # Let the user override the test.
-else
-as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_prog_MAKER="gmake"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
 done
-  done
-IFS=$as_save_IFS
+else $as_nop
+  printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
 
-  test -z "$ac_cv_prog_MAKER" && ac_cv_prog_MAKER="make"
-fi
+{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
 fi
-MAKER=$ac_cv_prog_MAKER
-if test -n "$MAKER"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAKER" >&5
-$as_echo "$MAKER" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
 fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+printf "%s\n" "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5
+printf %s "checking whether the compiler supports GNU C... " >&6; }
+if test ${ac_cv_c_compiler_gnu+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
 
+int
+main (void)
+{
+#ifndef __GNUC__
+       choke me
+#endif
 
-# Extract the first word of "gmake", so it can be a program name with args.
-set dummy gmake; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_GMAKE+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $GMAKE in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_GMAKE="$GMAKE" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_GMAKE="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  ;;
-esac
-fi
-GMAKE=$ac_cv_path_GMAKE
-if test -n "$GMAKE"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GMAKE" >&5
-$as_echo "$GMAKE" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_compiler_gnu=yes
+else $as_nop
+  ac_compiler_gnu=no
 fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
 
-
-if $MAKER --version | grep -q "GNU Make"; then :
-  GNUMAKE="0"
-else
-  as_fn_error $? "It seems your system does not have make/gmake installed. If you are on Linux then install make, otherwise install gmake." "$LINENO" 5
 fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; }
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -ldescrypt" >&5
-$as_echo_n "checking for crypt in -ldescrypt... " >&6; }
-if ${ac_cv_lib_descrypt_crypt+:} false; then :
-  $as_echo_n "(cached) " >&6
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
 else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-ldescrypt  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+y}
+ac_save_CFLAGS=$CFLAGS
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+printf %s "checking whether $CC accepts -g... " >&6; }
+if test ${ac_cv_prog_cc_g+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char crypt ();
 int
-main ()
+main (void)
 {
-return crypt ();
+
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_descrypt_crypt=yes
-else
-  ac_cv_lib_descrypt_crypt=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_descrypt_crypt" >&5
-$as_echo "$ac_cv_lib_descrypt_crypt" >&6; }
-if test "x$ac_cv_lib_descrypt_crypt" = xyes; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_cv_prog_cc_g=yes
+else $as_nop
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
 
-$as_echo "#define HAVE_CRYPT /**/" >>confdefs.h
+int
+main (void)
+{
 
-		IRCDLIBS="$IRCDLIBS-ldescrypt "
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5
-$as_echo_n "checking for crypt in -lcrypt... " >&6; }
-if ${ac_cv_lib_crypt_crypt+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lcrypt  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+else $as_nop
+  ac_c_werror_flag=$ac_save_c_werror_flag
+	 CFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
 
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char crypt ();
 int
-main ()
+main (void)
 {
-return crypt ();
+
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_crypt_crypt=yes
-else
-  ac_cv_lib_crypt_crypt=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_cv_prog_cc_g=yes
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt" >&5
-$as_echo "$ac_cv_lib_crypt_crypt" >&6; }
-if test "x$ac_cv_lib_crypt_crypt" = xyes; then :
-
-$as_echo "#define HAVE_CRYPT /**/" >>confdefs.h
-
-			IRCDLIBS="$IRCDLIBS-lcrypt "
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 fi
-
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 fi
-
-
-case $host_cpu in #(
-  i?86|amd64|x86_64) :
-    ac_cv_c_bigendian=no
- ;; #(
-  *) :
-     ;;
-esac
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
-$as_echo_n "checking how to run the C preprocessor... " >&6; }
-# On Suns, sometimes $CPP names a directory.
-if test -n "$CPP" && test -d "$CPP"; then
-  CPP=
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
 fi
-if test -z "$CPP"; then
-  if ${ac_cv_prog_CPP+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-      # Double quotes because CPP needs to be expanded
-    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
-    do
-      ac_preproc_ok=false
-for ac_c_preproc_warn_flag in '' yes
-do
-  # Use a header file that comes with gcc, so configuring glibc
-  # with a fresh cross-compiler works.
-  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
-  # <limits.h> exists even on freestanding compilers.
-  # On the NeXT, cc -E runs the code through the compiler's parser,
-  # not just through cpp. "Syntax error" is here to catch this case.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#ifdef __STDC__
-# include <limits.h>
-#else
-# include <assert.h>
-#endif
-		     Syntax error
-_ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+printf "%s\n" "$ac_cv_prog_cc_g" >&6; }
+if test $ac_test_CFLAGS; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
 else
-  # Broken: fails on valid input.
-continue
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
 fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
-  # OK, works on sane cases.  Now check whether nonexistent headers
-  # can be detected and how.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ac_prog_cc_stdc=no
+if test x$ac_prog_cc_stdc = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5
+printf %s "checking for $CC option to enable C11 features... " >&6; }
+if test ${ac_cv_prog_cc_c11+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_cv_prog_cc_c11=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <ac_nonexistent.h>
+$ac_c_conftest_c11_program
 _ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-  # Broken: success on invalid input.
-continue
-else
-  # Passes both tests.
-ac_preproc_ok=:
-break
+for ac_arg in '' -std=gnu11
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_cv_prog_cc_c11=$ac_arg
 fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+  test "x$ac_cv_prog_cc_c11" != "xno" && break
 done
-# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
-rm -f conftest.i conftest.err conftest.$ac_ext
-if $ac_preproc_ok; then :
-  break
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
 fi
 
-    done
-    ac_cv_prog_CPP=$CPP
-
-fi
-  CPP=$ac_cv_prog_CPP
-else
-  ac_cv_prog_CPP=$CPP
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
-$as_echo "$CPP" >&6; }
-ac_preproc_ok=false
-for ac_c_preproc_warn_flag in '' yes
-do
-  # Use a header file that comes with gcc, so configuring glibc
-  # with a fresh cross-compiler works.
-  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
-  # <limits.h> exists even on freestanding compilers.
-  # On the NeXT, cc -E runs the code through the compiler's parser,
-  # not just through cpp. "Syntax error" is here to catch this case.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+if test "x$ac_cv_prog_cc_c11" = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+  if test "x$ac_cv_prog_cc_c11" = x
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
+printf "%s\n" "$ac_cv_prog_cc_c11" >&6; }
+     CC="$CC $ac_cv_prog_cc_c11"
+fi
+  ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11
+  ac_prog_cc_stdc=c11
+fi
+fi
+if test x$ac_prog_cc_stdc = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5
+printf %s "checking for $CC option to enable C99 features... " >&6; }
+if test ${ac_cv_prog_cc_c99+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_cv_prog_cc_c99=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#ifdef __STDC__
-# include <limits.h>
-#else
-# include <assert.h>
-#endif
-		     Syntax error
+$ac_c_conftest_c99_program
 _ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-
-else
-  # Broken: fails on valid input.
-continue
+for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99=
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_cv_prog_cc_c99=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+  test "x$ac_cv_prog_cc_c99" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
 fi
-rm -f conftest.err conftest.i conftest.$ac_ext
 
-  # OK, works on sane cases.  Now check whether nonexistent headers
-  # can be detected and how.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+if test "x$ac_cv_prog_cc_c99" = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+  if test "x$ac_cv_prog_cc_c99" = x
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
+printf "%s\n" "$ac_cv_prog_cc_c99" >&6; }
+     CC="$CC $ac_cv_prog_cc_c99"
+fi
+  ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
+  ac_prog_cc_stdc=c99
+fi
+fi
+if test x$ac_prog_cc_stdc = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5
+printf %s "checking for $CC option to enable C89 features... " >&6; }
+if test ${ac_cv_prog_cc_c89+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <ac_nonexistent.h>
+$ac_c_conftest_c89_program
 _ACEOF
-if ac_fn_c_try_cpp "$LINENO"; then :
-  # Broken: success on invalid input.
-continue
-else
-  # Passes both tests.
-ac_preproc_ok=:
-break
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"
+then :
+  ac_cv_prog_cc_c89=$ac_arg
 fi
-rm -f conftest.err conftest.i conftest.$ac_ext
-
+rm -f core conftest.err conftest.$ac_objext conftest.beam
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
 done
-# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
-rm -f conftest.i conftest.err conftest.$ac_ext
-if $ac_preproc_ok; then :
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+fi
 
-else
-  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
-as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
-See \`config.log' for more details" "$LINENO" 5; }
+if test "x$ac_cv_prog_cc_c89" = xno
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+printf "%s\n" "unsupported" >&6; }
+else $as_nop
+  if test "x$ac_cv_prog_cc_c89" = x
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+printf "%s\n" "none needed" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+printf "%s\n" "$ac_cv_prog_cc_c89" >&6; }
+     CC="$CC $ac_cv_prog_cc_c89"
+fi
+  ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
+  ac_prog_cc_stdc=c89
+fi
 fi
 
 ac_ext=c
@@ -3965,270 +4036,237 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
-$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
-if ${ac_cv_path_GREP+:} false; then :
-  $as_echo_n "(cached) " >&6
+if test "$ac_cv_prog_cc_c99" = "no"
+then :
+  as_fn_error $? "No C99 compiler was found. Please install gcc or clang and other build tools. Eg, on Debian/Ubuntu you probably want to run the following as root: apt-get install build-essential " "$LINENO" 5
+fi
+
+# Extract the first word of "gmake", so it can be a program name with args.
+set dummy gmake; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_MAKER+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if test -n "$MAKER"; then
+  ac_cv_prog_MAKER="$MAKER" # Let the user override the test.
 else
-  if test -z "$GREP"; then
-  ac_path_GREP_found=false
-  # Loop through the user's path and test for each of PROGNAME-LIST
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_prog in grep ggrep; do
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
-      as_fn_executable_p "$ac_path_GREP" || continue
-# Check for GNU ac_path_GREP and select it if it is found.
-  # Check for GNU $ac_path_GREP
-case `"$ac_path_GREP" --version 2>&1` in
-*GNU*)
-  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
-*)
-  ac_count=0
-  $as_echo_n 0123456789 >"conftest.in"
-  while :
-  do
-    cat "conftest.in" "conftest.in" >"conftest.tmp"
-    mv "conftest.tmp" "conftest.in"
-    cp "conftest.in" "conftest.nl"
-    $as_echo 'GREP' >> "conftest.nl"
-    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
-    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
-    as_fn_arith $ac_count + 1 && ac_count=$as_val
-    if test $ac_count -gt ${ac_path_GREP_max-0}; then
-      # Best one so far, save it but keep looking for a better one
-      ac_cv_path_GREP="$ac_path_GREP"
-      ac_path_GREP_max=$ac_count
-    fi
-    # 10*(2^10) chars as input seems more than enough
-    test $ac_count -gt 10 && break
-  done
-  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
-      $ac_path_GREP_found && break 3
-    done
-  done
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_prog_MAKER="gmake"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
   done
 IFS=$as_save_IFS
-  if test -z "$ac_cv_path_GREP"; then
-    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
-  fi
-else
-  ac_cv_path_GREP=$GREP
-fi
 
+  test -z "$ac_cv_prog_MAKER" && ac_cv_prog_MAKER="make"
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
-$as_echo "$ac_cv_path_GREP" >&6; }
- GREP="$ac_cv_path_GREP"
-
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
-$as_echo_n "checking for egrep... " >&6; }
-if ${ac_cv_path_EGREP+:} false; then :
-  $as_echo_n "(cached) " >&6
+fi
+MAKER=$ac_cv_prog_MAKER
+if test -n "$MAKER"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAKER" >&5
+printf "%s\n" "$MAKER" >&6; }
 else
-  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
-   then ac_cv_path_EGREP="$GREP -E"
-   else
-     if test -z "$EGREP"; then
-  ac_path_EGREP_found=false
-  # Loop through the user's path and test for each of PROGNAME-LIST
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_prog in egrep; do
-    for ac_exec_ext in '' $ac_executable_extensions; do
-      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
-      as_fn_executable_p "$ac_path_EGREP" || continue
-# Check for GNU ac_path_EGREP and select it if it is found.
-  # Check for GNU $ac_path_EGREP
-case `"$ac_path_EGREP" --version 2>&1` in
-*GNU*)
-  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
-*)
-  ac_count=0
-  $as_echo_n 0123456789 >"conftest.in"
-  while :
-  do
-    cat "conftest.in" "conftest.in" >"conftest.tmp"
-    mv "conftest.tmp" "conftest.in"
-    cp "conftest.in" "conftest.nl"
-    $as_echo 'EGREP' >> "conftest.nl"
-    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
-    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
-    as_fn_arith $ac_count + 1 && ac_count=$as_val
-    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
-      # Best one so far, save it but keep looking for a better one
-      ac_cv_path_EGREP="$ac_path_EGREP"
-      ac_path_EGREP_max=$ac_count
-    fi
-    # 10*(2^10) chars as input seems more than enough
-    test $ac_count -gt 10 && break
-  done
-  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
-esac
-
-      $ac_path_EGREP_found && break 3
-    done
-  done
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+# Extract the first word of "gmake", so it can be a program name with args.
+set dummy gmake; ac_word=$2
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_GMAKE+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  case $GMAKE in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_GMAKE="$GMAKE" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_GMAKE="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
   done
 IFS=$as_save_IFS
-  if test -z "$ac_cv_path_EGREP"; then
-    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
-  fi
+
+  ;;
+esac
+fi
+GMAKE=$ac_cv_path_GMAKE
+if test -n "$GMAKE"; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GMAKE" >&5
+printf "%s\n" "$GMAKE" >&6; }
 else
-  ac_cv_path_EGREP=$EGREP
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
-   fi
+
+if $MAKER --version | grep -q "GNU Make"
+then :
+  GNUMAKE="0"
+else $as_nop
+  as_fn_error $? "It seems your system does not have make/gmake installed. If you are on Linux then install make, otherwise install gmake." "$LINENO" 5
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
-$as_echo "$ac_cv_path_EGREP" >&6; }
- EGREP="$ac_cv_path_EGREP"
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
-$as_echo_n "checking for ANSI C header files... " >&6; }
-if ${ac_cv_header_stdc+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for crypt in -ldescrypt" >&5
+printf %s "checking for crypt in -ldescrypt... " >&6; }
+if test ${ac_cv_lib_descrypt_crypt+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldescrypt  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <float.h>
 
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+char crypt ();
 int
-main ()
+main (void)
 {
-
+return crypt ();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ac_cv_header_stdc=yes
-else
-  ac_cv_header_stdc=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-
-if test $ac_cv_header_stdc = yes; then
-  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <string.h>
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
-  $EGREP "memchr" >/dev/null 2>&1; then :
-
-else
-  ac_cv_header_stdc=no
-fi
-rm -f conftest*
-
+if ac_fn_c_try_link "$LINENO"
+then :
+  ac_cv_lib_descrypt_crypt=yes
+else $as_nop
+  ac_cv_lib_descrypt_crypt=no
 fi
-
-if test $ac_cv_header_stdc = yes; then
-  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-#include <stdlib.h>
-
-_ACEOF
-if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
-  $EGREP "free" >/dev/null 2>&1; then :
-
-else
-  ac_cv_header_stdc=no
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
 fi
-rm -f conftest*
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_descrypt_crypt" >&5
+printf "%s\n" "$ac_cv_lib_descrypt_crypt" >&6; }
+if test "x$ac_cv_lib_descrypt_crypt" = xyes
+then :
 
-fi
+printf "%s\n" "#define HAVE_CRYPT /**/" >>confdefs.h
 
-if test $ac_cv_header_stdc = yes; then
-  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
-  if test "$cross_compiling" = yes; then :
-  :
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+		IRCDLIBS="$IRCDLIBS-ldescrypt "
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5
+printf %s "checking for crypt in -lcrypt... " >&6; }
+if test ${ac_cv_lib_crypt_crypt+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcrypt  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-#include <ctype.h>
-#include <stdlib.h>
-#if ((' ' & 0x0FF) == 0x020)
-# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
-# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
-#else
-# define ISLOWER(c) \
-		   (('a' <= (c) && (c) <= 'i') \
-		     || ('j' <= (c) && (c) <= 'r') \
-		     || ('s' <= (c) && (c) <= 'z'))
-# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
-#endif
 
-#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+char crypt ();
 int
-main ()
+main (void)
 {
-  int i;
-  for (i = 0; i < 256; i++)
-    if (XOR (islower (i), ISLOWER (i))
-	|| toupper (i) != TOUPPER (i))
-      return 2;
+return crypt ();
+  ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
-
-else
-  ac_cv_header_stdc=no
+if ac_fn_c_try_link "$LINENO"
+then :
+  ac_cv_lib_crypt_crypt=yes
+else $as_nop
+  ac_cv_lib_crypt_crypt=no
 fi
-rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
-  conftest.$ac_objext conftest.beam conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
 fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt" >&5
+printf "%s\n" "$ac_cv_lib_crypt_crypt" >&6; }
+if test "x$ac_cv_lib_crypt_crypt" = xyes
+then :
 
+printf "%s\n" "#define HAVE_CRYPT /**/" >>confdefs.h
+
+			IRCDLIBS="$IRCDLIBS-lcrypt "
 fi
+
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
-$as_echo "$ac_cv_header_stdc" >&6; }
-if test $ac_cv_header_stdc = yes; then
 
-$as_echo "#define STDC_HEADERS 1" >>confdefs.h
 
-fi
+case $host_cpu in #(
+  i?86|amd64|x86_64) :
+    ac_cv_c_bigendian=no
+ ;; #(
+  *) :
+     ;;
+esac
+ac_header= ac_cache=
+for ac_item in $ac_header_c_list
+do
+  if test $ac_cache; then
+    ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default"
+    if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then
+      printf "%s\n" "#define $ac_item 1" >> confdefs.h
+    fi
+    ac_header= ac_cache=
+  elif test $ac_header; then
+    ac_cache=$ac_item
+  else
+    ac_header=$ac_item
+  fi
+done
 
-# On IRIX 5.3, sys/types and inttypes.h are conflicting.
-for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
-		  inttypes.h stdint.h unistd.h
-do :
-  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
-"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
 
-fi
 
-done
 
 
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
-$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
-if ${ac_cv_c_bigendian+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+
+
+
+if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes
+then :
+
+printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+printf %s "checking whether byte ordering is bigendian... " >&6; }
+if test ${ac_cv_c_bigendian+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   ac_cv_c_bigendian=unknown
     # See if we're dealing with a universal compiler.
     cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -4239,7 +4277,8 @@ else
 	     typedef int dummy;
 
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
 
 	# Check for potential -arch flags.  It is not universal unless
 	# there are at least two -arch flags with different values.
@@ -4263,7 +4302,7 @@ if ac_fn_c_try_compile "$LINENO"; then :
 	 fi
        done
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
     if test $ac_cv_c_bigendian = unknown; then
       # See if sys/param.h defines the BYTE_ORDER macro.
       cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -4272,7 +4311,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
 	     #include <sys/param.h>
 
 int
-main ()
+main (void)
 {
 #if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
 		     && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
@@ -4284,7 +4323,8 @@ main ()
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   # It does; now see whether it defined to BIG_ENDIAN or not.
 	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
@@ -4292,7 +4332,7 @@ if ac_fn_c_try_compile "$LINENO"; then :
 		#include <sys/param.h>
 
 int
-main ()
+main (void)
 {
 #if BYTE_ORDER != BIG_ENDIAN
 		 not big endian
@@ -4302,14 +4342,15 @@ main ()
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ac_cv_c_bigendian=yes
-else
+else $as_nop
   ac_cv_c_bigendian=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
     fi
     if test $ac_cv_c_bigendian = unknown; then
       # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
@@ -4318,7 +4359,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
 #include <limits.h>
 
 int
-main ()
+main (void)
 {
 #if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
 	      bogus endian macros
@@ -4328,14 +4369,15 @@ main ()
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   # It does; now see whether it defined to _BIG_ENDIAN or not.
 	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <limits.h>
 
 int
-main ()
+main (void)
 {
 #ifndef _BIG_ENDIAN
 		 not big endian
@@ -4345,31 +4387,33 @@ main ()
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ac_cv_c_bigendian=yes
-else
+else $as_nop
   ac_cv_c_bigendian=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
     fi
     if test $ac_cv_c_bigendian = unknown; then
       # Compile a test program.
-      if test "$cross_compiling" = yes; then :
+      if test "$cross_compiling" = yes
+then :
   # Try to guess by grepping values from an object file.
 	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
-short int ascii_mm[] =
+unsigned short int ascii_mm[] =
 		  { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
-		short int ascii_ii[] =
+		unsigned short int ascii_ii[] =
 		  { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
 		int use_ascii (int i) {
 		  return ascii_mm[i] + ascii_ii[i];
 		}
-		short int ebcdic_ii[] =
+		unsigned short int ebcdic_ii[] =
 		  { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
-		short int ebcdic_mm[] =
+		unsigned short int ebcdic_mm[] =
 		  { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
 		int use_ebcdic (int i) {
 		  return ebcdic_mm[i] + ebcdic_ii[i];
@@ -4377,14 +4421,15 @@ short int ascii_mm[] =
 		extern int foo;
 
 int
-main ()
+main (void)
 {
 return use_ascii (foo) == use_ebcdic (foo);
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
 	      ac_cv_c_bigendian=yes
 	    fi
@@ -4397,13 +4442,13 @@ if ac_fn_c_try_compile "$LINENO"; then :
 	      fi
 	    fi
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-else
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+else $as_nop
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 $ac_includes_default
 int
-main ()
+main (void)
 {
 
 	     /* Are we little or big endian?  From Harbison&Steele.  */
@@ -4419,9 +4464,10 @@ main ()
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
+if ac_fn_c_try_run "$LINENO"
+then :
   ac_cv_c_bigendian=no
-else
+else $as_nop
   ac_cv_c_bigendian=yes
 fi
 rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
@@ -4430,16 +4476,16 @@ fi
 
     fi
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
-$as_echo "$ac_cv_c_bigendian" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+printf "%s\n" "$ac_cv_c_bigendian" >&6; }
  case $ac_cv_c_bigendian in #(
    yes)
 
-$as_echo "#define NATIVE_BIG_ENDIAN 1" >>confdefs.h
+printf "%s\n" "#define NATIVE_BIG_ENDIAN 1" >>confdefs.h
 ;; #(
    no)
 
-$as_echo "#define NATIVE_LITTLE_ENDIAN 1" >>confdefs.h
+printf "%s\n" "#define NATIVE_LITTLE_ENDIAN 1" >>confdefs.h
  ;; #(
    universal)
      as_fn_error $? "universal endianness is not supported - compile separately and use lipo(1)" "$LINENO" 5
@@ -4468,16 +4514,18 @@ LD="$flag_wrap $LD"
 
 
 # Check whether --enable-hardening was given.
-if test "${enable_hardening+set}" = set; then :
+if test ${enable_hardening+y}
+then :
   enableval=$enable_hardening; hardening="$enableval"
-else
+else $as_nop
   hardening="yes"
 fi
 
 
 HARDEN_CFLAGS=""
 HARDEN_LDFLAGS=""
-if test x"$hardening" != x"no"; then :
+if test x"$hardening" != x"no"
+then :
 
   ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -4485,11 +4533,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-strict-overflow" >&5
-$as_echo_n "checking whether C compiler accepts -fno-strict-overflow... " >&6; }
-if ${ax_cv_check_cflags__Werror___fno_strict_overflow+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-strict-overflow" >&5
+printf %s "checking whether C compiler accepts -fno-strict-overflow... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fno_strict_overflow+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fno-strict-overflow"
@@ -4497,26 +4546,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fno_strict_overflow=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fno_strict_overflow=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_strict_overflow" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fno_strict_overflow" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fno_strict_overflow" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_strict_overflow" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fno_strict_overflow" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fno_strict_overflow" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -fno-strict-overflow"
-else
+else $as_nop
   :
 fi
 
@@ -4534,11 +4585,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -D_FORTIFY_SOURCE=2" >&5
-$as_echo_n "checking whether C compiler accepts -D_FORTIFY_SOURCE=2... " >&6; }
-if ${ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -D_FORTIFY_SOURCE=2" >&5
+printf %s "checking whether C compiler accepts -D_FORTIFY_SOURCE=2... " >&6; }
+if test ${ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -D_FORTIFY_SOURCE=2"
@@ -4546,26 +4598,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" >&5
-$as_echo "$ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" >&6; }
-if test x"$ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" >&6; }
+if test x"$ax_cv_check_cflags__Werror___D_FORTIFY_SOURCE_2" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -D_FORTIFY_SOURCE=2"
-else
+else $as_nop
   :
 fi
 
@@ -4582,11 +4636,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-protector-all" >&5
-$as_echo_n "checking whether C compiler accepts -fstack-protector-all... " >&6; }
-if ${ax_cv_check_cflags__Werror___fstack_protector_all+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-protector-all" >&5
+printf %s "checking whether C compiler accepts -fstack-protector-all... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fstack_protector_all+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fstack-protector-all"
@@ -4594,29 +4649,32 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fstack_protector_all=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fstack_protector_all=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fstack_protector_all" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fstack_protector_all" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fstack_protector_all" = xyes; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fstack-protector-all" >&5
-$as_echo_n "checking whether the linker accepts -fstack-protector-all... " >&6; }
-if ${ax_cv_check_ldflags__Werror___fstack_protector_all+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fstack_protector_all" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fstack_protector_all" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fstack_protector_all" = xyes
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fstack-protector-all" >&5
+printf %s "checking whether the linker accepts -fstack-protector-all... " >&6; }
+if test ${ax_cv_check_ldflags__Werror___fstack_protector_all+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$LDFLAGS
   LDFLAGS="$LDFLAGS -Werror  -fstack-protector-all"
@@ -4624,25 +4682,27 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_check_ldflags__Werror___fstack_protector_all=yes
-else
+else $as_nop
   ax_cv_check_ldflags__Werror___fstack_protector_all=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
   LDFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fstack_protector_all" >&5
-$as_echo "$ax_cv_check_ldflags__Werror___fstack_protector_all" >&6; }
-if test x"$ax_cv_check_ldflags__Werror___fstack_protector_all" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fstack_protector_all" >&5
+printf "%s\n" "$ax_cv_check_ldflags__Werror___fstack_protector_all" >&6; }
+if test x"$ax_cv_check_ldflags__Werror___fstack_protector_all" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -fstack-protector-all"
       ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -4650,11 +4710,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wstack-protector" >&5
-$as_echo_n "checking whether C compiler accepts -Wstack-protector... " >&6; }
-if ${ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wstack-protector" >&5
+printf %s "checking whether C compiler accepts -Wstack-protector... " >&6; }
+if test ${ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror -fstack-protector-all -Wstack-protector"
@@ -4662,26 +4723,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" >&5
-$as_echo "$ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" >&6; }
-if test x"$ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" >&6; }
+if test x"$ax_cv_check_cflags__Werror__fstack_protector_all__Wstack_protector" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -Wstack-protector"
-else
+else $as_nop
   :
 fi
 
@@ -4697,11 +4760,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts --param ssp-buffer-size=1" >&5
-$as_echo_n "checking whether C compiler accepts --param ssp-buffer-size=1... " >&6; }
-if ${ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts --param ssp-buffer-size=1" >&5
+printf %s "checking whether C compiler accepts --param ssp-buffer-size=1... " >&6; }
+if test ${ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror -fstack-protector-all --param ssp-buffer-size=1"
@@ -4709,26 +4773,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" >&5
-$as_echo "$ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" >&6; }
-if test x"$ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" >&6; }
+if test x"$ax_cv_check_cflags__Werror__fstack_protector_all___param_ssp_buffer_size_1" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS --param ssp-buffer-size=1"
-else
+else $as_nop
   :
 fi
 
@@ -4738,11 +4804,11 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-else
+else $as_nop
   :
 fi
 
-else
+else $as_nop
   :
 fi
 
@@ -4760,11 +4826,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-clash-protection" >&5
-$as_echo_n "checking whether C compiler accepts -fstack-clash-protection... " >&6; }
-if ${ax_cv_check_cflags__Werror___fstack_clash_protection+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fstack-clash-protection" >&5
+printf %s "checking whether C compiler accepts -fstack-clash-protection... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fstack_clash_protection+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fstack-clash-protection"
@@ -4772,26 +4839,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fstack_clash_protection=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fstack_clash_protection=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fstack_clash_protection" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fstack_clash_protection" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fstack_clash_protection" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fstack_clash_protection" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fstack_clash_protection" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fstack_clash_protection" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -fstack-clash-protection"
-else
+else $as_nop
   :
 fi
 
@@ -4809,11 +4878,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fcf-protection" >&5
-$as_echo_n "checking whether C compiler accepts -fcf-protection... " >&6; }
-if ${ax_cv_check_cflags__Werror___fcf_protection+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fcf-protection" >&5
+printf %s "checking whether C compiler accepts -fcf-protection... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fcf_protection+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fcf-protection"
@@ -4821,26 +4891,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fcf_protection=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fcf_protection=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fcf_protection" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fcf_protection" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fcf_protection" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fcf_protection" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fcf_protection" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fcf_protection" = xyes
+then :
   HARDEN_CFLAGS="$HARDEN_CFLAGS -fcf-protection"
-else
+else $as_nop
   :
 fi
 
@@ -4860,11 +4932,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fPIE" >&5
-$as_echo_n "checking whether C compiler accepts -fPIE... " >&6; }
-if ${ax_cv_check_cflags__Werror___fPIE+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fPIE" >&5
+printf %s "checking whether C compiler accepts -fPIE... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fPIE+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fPIE"
@@ -4872,29 +4945,32 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fPIE=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fPIE=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fPIE" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fPIE" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fPIE" = xyes; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fPIE -pie" >&5
-$as_echo_n "checking whether the linker accepts -fPIE -pie... " >&6; }
-if ${ax_cv_check_ldflags__Werror___fPIE__pie+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fPIE" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fPIE" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fPIE" = xyes
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fPIE -pie" >&5
+printf %s "checking whether the linker accepts -fPIE -pie... " >&6; }
+if test ${ax_cv_check_ldflags__Werror___fPIE__pie+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$LDFLAGS
   LDFLAGS="$LDFLAGS -Werror  -fPIE -pie"
@@ -4902,33 +4978,36 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_check_ldflags__Werror___fPIE__pie=yes
-else
+else $as_nop
   ax_cv_check_ldflags__Werror___fPIE__pie=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
   LDFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fPIE__pie" >&5
-$as_echo "$ax_cv_check_ldflags__Werror___fPIE__pie" >&6; }
-if test x"$ax_cv_check_ldflags__Werror___fPIE__pie" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fPIE__pie" >&5
+printf "%s\n" "$ax_cv_check_ldflags__Werror___fPIE__pie" >&6; }
+if test x"$ax_cv_check_ldflags__Werror___fPIE__pie" = xyes
+then :
   HARDEN_BINCFLAGS="-fPIE"
       HARDEN_BINLDFLAGS="-pie"
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fPIE -Wl,-pie" >&5
-$as_echo_n "checking whether the linker accepts -fPIE -Wl,-pie... " >&6; }
-if ${ax_cv_check_ldflags__Werror___fPIE__Wl__pie+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -fPIE -Wl,-pie" >&5
+printf %s "checking whether the linker accepts -fPIE -Wl,-pie... " >&6; }
+if test ${ax_cv_check_ldflags__Werror___fPIE__Wl__pie+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$LDFLAGS
   LDFLAGS="$LDFLAGS -Werror  -fPIE -Wl,-pie"
@@ -4936,34 +5015,36 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_check_ldflags__Werror___fPIE__Wl__pie=yes
-else
+else $as_nop
   ax_cv_check_ldflags__Werror___fPIE__Wl__pie=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
   LDFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fPIE__Wl__pie" >&5
-$as_echo "$ax_cv_check_ldflags__Werror___fPIE__Wl__pie" >&6; }
-if test x"$ax_cv_check_ldflags__Werror___fPIE__Wl__pie" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___fPIE__Wl__pie" >&5
+printf "%s\n" "$ax_cv_check_ldflags__Werror___fPIE__Wl__pie" >&6; }
+if test x"$ax_cv_check_ldflags__Werror___fPIE__Wl__pie" = xyes
+then :
   HARDEN_BINCFLAGS="-fPIE"
         HARDEN_BINLDFLAGS="-Wl,-pie"
-else
+else $as_nop
   :
 fi
 
 fi
 
-else
+else $as_nop
   :
 fi
 
@@ -4974,11 +5055,12 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -Wl,-z,relro" >&5
-$as_echo_n "checking whether the linker accepts -Wl,-z,relro... " >&6; }
-if ${ax_cv_check_ldflags__Werror___Wl__z_relro+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -Wl,-z,relro" >&5
+printf %s "checking whether the linker accepts -Wl,-z,relro... " >&6; }
+if test ${ax_cv_check_ldflags__Werror___Wl__z_relro+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$LDFLAGS
   LDFLAGS="$LDFLAGS -Werror  -Wl,-z,relro"
@@ -4986,31 +5068,34 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_check_ldflags__Werror___Wl__z_relro=yes
-else
+else $as_nop
   ax_cv_check_ldflags__Werror___Wl__z_relro=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
   LDFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___Wl__z_relro" >&5
-$as_echo "$ax_cv_check_ldflags__Werror___Wl__z_relro" >&6; }
-if test x"$ax_cv_check_ldflags__Werror___Wl__z_relro" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___Wl__z_relro" >&5
+printf "%s\n" "$ax_cv_check_ldflags__Werror___Wl__z_relro" >&6; }
+if test x"$ax_cv_check_ldflags__Werror___Wl__z_relro" = xyes
+then :
   HARDEN_LDFLAGS="$HARDEN_LDFLAGS -Wl,-z,relro"
-    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -Wl,-z,now" >&5
-$as_echo_n "checking whether the linker accepts -Wl,-z,now... " >&6; }
-if ${ax_cv_check_ldflags__Werror___Wl__z_now+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the linker accepts -Wl,-z,now" >&5
+printf %s "checking whether the linker accepts -Wl,-z,now... " >&6; }
+if test ${ax_cv_check_ldflags__Werror___Wl__z_now+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$LDFLAGS
   LDFLAGS="$LDFLAGS -Werror  -Wl,-z,now"
@@ -5018,31 +5103,33 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_check_ldflags__Werror___Wl__z_now=yes
-else
+else $as_nop
   ax_cv_check_ldflags__Werror___Wl__z_now=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
   LDFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___Wl__z_now" >&5
-$as_echo "$ax_cv_check_ldflags__Werror___Wl__z_now" >&6; }
-if test x"$ax_cv_check_ldflags__Werror___Wl__z_now" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_ldflags__Werror___Wl__z_now" >&5
+printf "%s\n" "$ax_cv_check_ldflags__Werror___Wl__z_now" >&6; }
+if test x"$ax_cv_check_ldflags__Werror___Wl__z_now" = xyes
+then :
   HARDEN_LDFLAGS="$HARDEN_LDFLAGS -Wl,-z,now"
-else
+else $as_nop
   :
 fi
 
-else
+else $as_nop
   :
 fi
 
@@ -5063,11 +5150,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-strict-aliasing" >&5
-$as_echo_n "checking whether C compiler accepts -fno-strict-aliasing... " >&6; }
-if ${ax_cv_check_cflags__Werror___fno_strict_aliasing+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-strict-aliasing" >&5
+printf %s "checking whether C compiler accepts -fno-strict-aliasing... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fno_strict_aliasing+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fno-strict-aliasing"
@@ -5075,26 +5163,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fno_strict_aliasing=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fno_strict_aliasing=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_strict_aliasing" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fno_strict_aliasing" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fno_strict_aliasing" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_strict_aliasing" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fno_strict_aliasing" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fno_strict_aliasing" = xyes
+then :
   CFLAGS="$CFLAGS -fno-strict-aliasing"
-else
+else $as_nop
   :
 fi
 
@@ -5111,11 +5201,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-common" >&5
-$as_echo_n "checking whether C compiler accepts -fno-common... " >&6; }
-if ${ax_cv_check_cflags__Werror___fno_common+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-common" >&5
+printf %s "checking whether C compiler accepts -fno-common... " >&6; }
+if test ${ax_cv_check_cflags__Werror___fno_common+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -fno-common"
@@ -5123,26 +5214,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___fno_common=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___fno_common=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_common" >&5
-$as_echo "$ax_cv_check_cflags__Werror___fno_common" >&6; }
-if test x"$ax_cv_check_cflags__Werror___fno_common" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___fno_common" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___fno_common" >&6; }
+if test x"$ax_cv_check_cflags__Werror___fno_common" = xyes
+then :
   CFLAGS="$CFLAGS -fno-common"
-else
+else $as_nop
   :
 fi
 
@@ -5165,11 +5258,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wextra" >&5
-$as_echo_n "checking whether C compiler accepts -Wextra... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wextra+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wextra" >&5
+printf %s "checking whether C compiler accepts -Wextra... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wextra+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wextra"
@@ -5177,26 +5271,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wextra=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wextra=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wextra" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wextra" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wextra" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wextra" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wextra" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wextra" = xyes
+then :
   CFLAGS="$CFLAGS -Wextra"
-else
+else $as_nop
   :
 fi
 
@@ -5212,11 +5308,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Waggregate-return" >&5
-$as_echo_n "checking whether C compiler accepts -Waggregate-return... " >&6; }
-if ${ax_cv_check_cflags__Werror___Waggregate_return+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Waggregate-return" >&5
+printf %s "checking whether C compiler accepts -Waggregate-return... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Waggregate_return+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Waggregate-return"
@@ -5224,26 +5321,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Waggregate_return=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Waggregate_return=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Waggregate_return" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Waggregate_return" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Waggregate_return" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Waggregate_return" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Waggregate_return" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Waggregate_return" = xyes
+then :
   CFLAGS="$CFLAGS -Waggregate-return"
-else
+else $as_nop
   :
 fi
 
@@ -5259,11 +5358,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-nonliteral" >&5
-$as_echo_n "checking whether C compiler accepts -Wformat-nonliteral... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wformat_nonliteral+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-nonliteral" >&5
+printf %s "checking whether C compiler accepts -Wformat-nonliteral... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wformat_nonliteral+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wformat-nonliteral"
@@ -5271,26 +5371,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wformat_nonliteral=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wformat_nonliteral=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_nonliteral" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wformat_nonliteral" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wformat_nonliteral" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_nonliteral" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wformat_nonliteral" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wformat_nonliteral" = xyes
+then :
   CFLAGS="$CFLAGS -Wformat-nonliteral"
-else
+else $as_nop
   :
 fi
 
@@ -5307,11 +5409,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wduplicated-cond" >&5
-$as_echo_n "checking whether C compiler accepts -Wduplicated-cond... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wduplicated_cond+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wduplicated-cond" >&5
+printf %s "checking whether C compiler accepts -Wduplicated-cond... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wduplicated_cond+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wduplicated-cond"
@@ -5319,26 +5422,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wduplicated_cond=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wduplicated_cond=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wduplicated_cond" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wduplicated_cond" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wduplicated_cond" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wduplicated_cond" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wduplicated_cond" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wduplicated_cond" = xyes
+then :
   CFLAGS="$CFLAGS -Wduplicated-cond"
-else
+else $as_nop
   :
 fi
 
@@ -5354,11 +5459,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wduplicated-branches" >&5
-$as_echo_n "checking whether C compiler accepts -Wduplicated-branches... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wduplicated_branches+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wduplicated-branches" >&5
+printf %s "checking whether C compiler accepts -Wduplicated-branches... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wduplicated_branches+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wduplicated-branches"
@@ -5366,26 +5472,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wduplicated_branches=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wduplicated_branches=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wduplicated_branches" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wduplicated_branches" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wduplicated_branches" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wduplicated_branches" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wduplicated_branches" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wduplicated_branches" = xyes
+then :
   CFLAGS="$CFLAGS -Wduplicated-branches"
-else
+else $as_nop
   :
 fi
 
@@ -5402,11 +5510,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wparentheses" >&5
-$as_echo_n "checking whether C compiler accepts -Wparentheses... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wparentheses+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wparentheses" >&5
+printf %s "checking whether C compiler accepts -Wparentheses... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wparentheses+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wparentheses"
@@ -5414,26 +5523,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wparentheses=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wparentheses=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wparentheses" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wparentheses" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wparentheses" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wparentheses" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wparentheses" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wparentheses" = xyes
+then :
   CFLAGS="$CFLAGS -Wparentheses"
-else
+else $as_nop
   :
 fi
 
@@ -5452,11 +5563,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wpointer-sign" >&5
-$as_echo_n "checking whether C compiler accepts -Wpointer-sign... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wpointer_sign+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wpointer-sign" >&5
+printf %s "checking whether C compiler accepts -Wpointer-sign... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wpointer_sign+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wpointer-sign"
@@ -5464,26 +5576,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wpointer_sign=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wpointer_sign=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wpointer_sign" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wpointer_sign" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wpointer_sign" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wpointer_sign" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wpointer_sign" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wpointer_sign" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-pointer-sign"
-else
+else $as_nop
   :
 fi
 
@@ -5500,11 +5614,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Winvalid-source-encoding" >&5
-$as_echo_n "checking whether C compiler accepts -Winvalid-source-encoding... " >&6; }
-if ${ax_cv_check_cflags__Werror___Winvalid_source_encoding+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Winvalid-source-encoding" >&5
+printf %s "checking whether C compiler accepts -Winvalid-source-encoding... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Winvalid_source_encoding+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Winvalid-source-encoding"
@@ -5512,26 +5627,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Winvalid_source_encoding=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Winvalid_source_encoding=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Winvalid_source_encoding" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Winvalid_source_encoding" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Winvalid_source_encoding" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Winvalid_source_encoding" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Winvalid_source_encoding" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Winvalid_source_encoding" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-invalid-source-encoding"
-else
+else $as_nop
   :
 fi
 
@@ -5548,11 +5665,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-zero-length" >&5
-$as_echo_n "checking whether C compiler accepts -Wformat-zero-length... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wformat_zero_length+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-zero-length" >&5
+printf %s "checking whether C compiler accepts -Wformat-zero-length... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wformat_zero_length+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wformat-zero-length"
@@ -5560,26 +5678,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wformat_zero_length=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wformat_zero_length=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_zero_length" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wformat_zero_length" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wformat_zero_length" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_zero_length" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wformat_zero_length" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wformat_zero_length" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-format-zero-length"
-else
+else $as_nop
   :
 fi
 
@@ -5596,11 +5716,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-truncation" >&5
-$as_echo_n "checking whether C compiler accepts -Wformat-truncation... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wformat_truncation+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-truncation" >&5
+printf %s "checking whether C compiler accepts -Wformat-truncation... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wformat_truncation+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wformat-truncation"
@@ -5608,26 +5729,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wformat_truncation=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wformat_truncation=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_truncation" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wformat_truncation" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wformat_truncation" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_truncation" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wformat_truncation" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wformat_truncation" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-format-truncation"
-else
+else $as_nop
   :
 fi
 
@@ -5644,11 +5767,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-overflow" >&5
-$as_echo_n "checking whether C compiler accepts -Wformat-overflow... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wformat_overflow+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wformat-overflow" >&5
+printf %s "checking whether C compiler accepts -Wformat-overflow... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wformat_overflow+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wformat-overflow"
@@ -5656,26 +5780,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wformat_overflow=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wformat_overflow=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_overflow" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wformat_overflow" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wformat_overflow" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wformat_overflow" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wformat_overflow" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wformat_overflow" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-format-overflow"
-else
+else $as_nop
   :
 fi
 
@@ -5692,11 +5818,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused" >&5
-$as_echo_n "checking whether C compiler accepts -Wunused... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wunused+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused" >&5
+printf %s "checking whether C compiler accepts -Wunused... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wunused+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wunused"
@@ -5704,26 +5831,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wunused=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wunused=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wunused" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wunused" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wunused" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wunused" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-unused"
-else
+else $as_nop
   :
 fi
 
@@ -5739,11 +5868,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused-parameter" >&5
-$as_echo_n "checking whether C compiler accepts -Wunused-parameter... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wunused_parameter+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused-parameter" >&5
+printf %s "checking whether C compiler accepts -Wunused-parameter... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wunused_parameter+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wunused-parameter"
@@ -5751,26 +5881,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wunused_parameter=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wunused_parameter=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused_parameter" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wunused_parameter" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wunused_parameter" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused_parameter" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wunused_parameter" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wunused_parameter" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-unused-parameter"
-else
+else $as_nop
   :
 fi
 
@@ -5786,11 +5918,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused-but-set-parameter" >&5
-$as_echo_n "checking whether C compiler accepts -Wunused-but-set-parameter... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wunused_but_set_parameter+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunused-but-set-parameter" >&5
+printf %s "checking whether C compiler accepts -Wunused-but-set-parameter... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wunused_but_set_parameter+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wunused-but-set-parameter"
@@ -5798,26 +5931,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wunused_but_set_parameter=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wunused_but_set_parameter=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused_but_set_parameter" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wunused_but_set_parameter" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wunused_but_set_parameter" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunused_but_set_parameter" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wunused_but_set_parameter" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wunused_but_set_parameter" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-unused-but-set-parameter"
-else
+else $as_nop
   :
 fi
 
@@ -5834,11 +5969,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wchar-subscripts" >&5
-$as_echo_n "checking whether C compiler accepts -Wchar-subscripts... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wchar_subscripts+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wchar-subscripts" >&5
+printf %s "checking whether C compiler accepts -Wchar-subscripts... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wchar_subscripts+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wchar-subscripts"
@@ -5846,26 +5982,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wchar_subscripts=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wchar_subscripts=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wchar_subscripts" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wchar_subscripts" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wchar_subscripts" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wchar_subscripts" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wchar_subscripts" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wchar_subscripts" = xyes
+then :
   CFLAGS="$CFLAGS -Wno-char-subscripts"
-else
+else $as_nop
   :
 fi
 
@@ -5882,11 +6020,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wsign-compare" >&5
-$as_echo_n "checking whether C compiler accepts -Wsign-compare... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wsign_compare+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wsign-compare" >&5
+printf %s "checking whether C compiler accepts -Wsign-compare... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wsign_compare+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wsign-compare"
@@ -5894,26 +6033,79 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ax_cv_check_cflags__Werror___Wsign_compare=yes
+else $as_nop
+  ax_cv_check_cflags__Werror___Wsign_compare=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wsign_compare" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wsign_compare" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wsign_compare" = xyes
+then :
+  CFLAGS="$CFLAGS -Wno-sign-compare"
+else $as_nop
+  :
+fi
+
+  ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wempty-body" >&5
+printf %s "checking whether C compiler accepts -Wempty-body... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wempty_body+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror  -Wempty-body"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ax_cv_check_cflags__Werror___Wsign_compare=yes
-else
-  ax_cv_check_cflags__Werror___Wsign_compare=no
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ax_cv_check_cflags__Werror___Wempty_body=yes
+else $as_nop
+  ax_cv_check_cflags__Werror___Wempty_body=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wsign_compare" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wsign_compare" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wsign_compare" = xyes; then :
-  CFLAGS="$CFLAGS -Wno-sign-compare"
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wempty_body" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wempty_body" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wempty_body" = xyes
+then :
+  CFLAGS="$CFLAGS -Wno-empty-body"
+else $as_nop
   :
 fi
 
@@ -5930,38 +6122,41 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wempty-body" >&5
-$as_echo_n "checking whether C compiler accepts -Wempty-body... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wempty_body+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wdeprecated-non-prototype" >&5
+printf %s "checking whether C compiler accepts -Wdeprecated-non-prototype... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wdeprecated_non_prototype+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
-  CFLAGS="$CFLAGS -Werror  -Wempty-body"
+  CFLAGS="$CFLAGS -Werror  -Wdeprecated-non-prototype"
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  ax_cv_check_cflags__Werror___Wempty_body=yes
-else
-  ax_cv_check_cflags__Werror___Wempty_body=no
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ax_cv_check_cflags__Werror___Wdeprecated_non_prototype=yes
+else $as_nop
+  ax_cv_check_cflags__Werror___Wdeprecated_non_prototype=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wempty_body" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wempty_body" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wempty_body" = xyes; then :
-  CFLAGS="$CFLAGS -Wno-empty-body"
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wdeprecated_non_prototype" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wdeprecated_non_prototype" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wdeprecated_non_prototype" = xyes
+then :
+  CFLAGS="$CFLAGS -Wno-deprecated-non-prototype"
+else $as_nop
   :
 fi
 
@@ -5972,7 +6167,8 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
-if $CC --version | grep -q "clang version 3."; then :
+if $CC --version | grep -q "clang version 3."
+then :
   CFLAGS="$CFLAGS -Wno-tautological-compare -Wno-pragmas"
 fi
 
@@ -5982,11 +6178,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wpragmas" >&5
-$as_echo_n "checking whether C compiler accepts -Wpragmas... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wpragmas+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wpragmas" >&5
+printf %s "checking whether C compiler accepts -Wpragmas... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wpragmas+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wpragmas"
@@ -5994,26 +6191,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wpragmas=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wpragmas=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wpragmas" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wpragmas" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wpragmas" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wpragmas" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wpragmas" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wpragmas" = xyes
+then :
   no_pragmas=1
-else
+else $as_nop
   no_pragmas=0
 fi
 
@@ -6029,11 +6228,12 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunknown-warning-option" >&5
-$as_echo_n "checking whether C compiler accepts -Wunknown-warning-option... " >&6; }
-if ${ax_cv_check_cflags__Werror___Wunknown_warning_option+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -Wunknown-warning-option" >&5
+printf %s "checking whether C compiler accepts -Wunknown-warning-option... " >&6; }
+if test ${ax_cv_check_cflags__Werror___Wunknown_warning_option+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
   ax_check_save_flags=$CFLAGS
   CFLAGS="$CFLAGS -Werror  -Wunknown-warning-option"
@@ -6041,26 +6241,28 @@ else
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+if ac_fn_c_try_compile "$LINENO"
+then :
   ax_cv_check_cflags__Werror___Wunknown_warning_option=yes
-else
+else $as_nop
   ax_cv_check_cflags__Werror___Wunknown_warning_option=no
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
   CFLAGS=$ax_check_save_flags
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunknown_warning_option" >&5
-$as_echo "$ax_cv_check_cflags__Werror___Wunknown_warning_option" >&6; }
-if test x"$ax_cv_check_cflags__Werror___Wunknown_warning_option" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror___Wunknown_warning_option" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror___Wunknown_warning_option" >&6; }
+if test x"$ax_cv_check_cflags__Werror___Wunknown_warning_option" = xyes
+then :
   unknown_warning_option=1
-else
+else $as_nop
   unknown_warning_option=0
 fi
 
@@ -6083,32 +6285,36 @@ fi
 
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if your system has IPv6 support" >&5
-$as_echo_n "checking if your system has IPv6 support... " >&6; }
-if ${ac_cv_ip6+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if your system has IPv6 support" >&5
+printf %s "checking if your system has IPv6 support... " >&6; }
+if test ${ac_cv_ip6+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
-if test "$cross_compiling" = yes; then :
-  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+if test "$cross_compiling" = yes
+then :
+  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "cannot run test program while cross compiling
 See \`config.log' for more details" "$LINENO" 5; }
-else
+else $as_nop
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
 #include <sys/types.h>
 #include <sys/socket.h>
-int main() {
+#include <stdlib.h>
+int main(void) {
 int s = socket(AF_INET6, SOCK_STREAM, 0);
 exit(0); /* We only check if the code compiles, that's enough. We can deal with missing runtime IPv6 */
 }
 
 _ACEOF
-if ac_fn_c_try_run "$LINENO"; then :
+if ac_fn_c_try_run "$LINENO"
+then :
   ac_cv_ip6=yes
-else
+else $as_nop
   ac_cv_ip6=no
 fi
 rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
@@ -6117,146 +6323,422 @@ fi
 
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ip6" >&5
-$as_echo "$ac_cv_ip6" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ip6" >&5
+printf "%s\n" "$ac_cv_ip6" >&6; }
 if test "$ac_cv_ip6" = "no"; then
 	as_fn_error $? "Your system does not support IPv6" "$LINENO" 5
 fi
 
-ac_fn_c_check_header_mongrel "$LINENO" "sys/syslog.h" "ac_cv_header_sys_syslog_h" "$ac_includes_default"
-if test "x$ac_cv_header_sys_syslog_h" = xyes; then :
+ac_fn_c_check_header_compile "$LINENO" "sys/syslog.h" "ac_cv_header_sys_syslog_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_syslog_h" = xyes
+then :
+
+printf "%s\n" "#define SYSSYSLOGH /**/" >>confdefs.h
+
+fi
+
+ac_fn_c_check_header_compile "$LINENO" "sys/rusage.h" "ac_cv_header_sys_rusage_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_rusage_h" = xyes
+then :
 
-$as_echo "#define SYSSYSLOGH /**/" >>confdefs.h
+printf "%s\n" "#define RUSAGEH /**/" >>confdefs.h
 
 fi
 
+ac_fn_c_check_header_compile "$LINENO" "glob.h" "ac_cv_header_glob_h" "$ac_includes_default"
+if test "x$ac_cv_header_glob_h" = xyes
+then :
+
+printf "%s\n" "#define GLOBH /**/" >>confdefs.h
+
+fi
 
-ac_fn_c_check_header_mongrel "$LINENO" "sys/rusage.h" "ac_cv_header_sys_rusage_h" "$ac_includes_default"
-if test "x$ac_cv_header_sys_rusage_h" = xyes; then :
+ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default"
+if test "x$ac_cv_header_stdint_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h
 
-$as_echo "#define RUSAGEH /**/" >>confdefs.h
+fi
+ac_fn_c_check_header_compile "$LINENO" "inttypes.h" "ac_cv_header_inttypes_h" "$ac_includes_default"
+if test "x$ac_cv_header_inttypes_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_INTTYPES_H 1" >>confdefs.h
 
 fi
 
 
-ac_fn_c_check_header_mongrel "$LINENO" "glob.h" "ac_cv_header_glob_h" "$ac_includes_default"
-if test "x$ac_cv_header_glob_h" = xyes; then :
 
-$as_echo "#define GLOBH /**/" >>confdefs.h
+  for ac_func in strlcpy
+do :
+  ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy"
+if test "x$ac_cv_func_strlcpy" = xyes
+then :
+  printf "%s\n" "#define HAVE_STRLCPY 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_STRLCPY /**/" >>confdefs.h
 
 fi
 
+done
 
-for ac_header in stdint.h inttypes.h
+  for ac_func in strlcat
 do :
-  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
-ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
-if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
-  cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
-_ACEOF
+  ac_fn_c_check_func "$LINENO" "strlcat" "ac_cv_func_strlcat"
+if test "x$ac_cv_func_strlcat" = xyes
+then :
+  printf "%s\n" "#define HAVE_STRLCAT 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_STRLCAT /**/" >>confdefs.h
 
 fi
 
 done
 
+  for ac_func in strlncat
+do :
+  ac_fn_c_check_func "$LINENO" "strlncat" "ac_cv_func_strlncat"
+if test "x$ac_cv_func_strlncat" = xyes
+then :
+  printf "%s\n" "#define HAVE_STRLNCAT 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_STRLNCAT /**/" >>confdefs.h
+
+fi
+
+done
 
-for ac_func in strlcpy
+  for ac_func in strlncpy
 do :
-  ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy"
-if test "x$ac_cv_func_strlcpy" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_STRLCPY 1
-_ACEOF
+  ac_fn_c_check_func "$LINENO" "strlncpy" "ac_cv_func_strlncpy"
+if test "x$ac_cv_func_strlncpy" = xyes
+then :
+  printf "%s\n" "#define HAVE_STRLNCPY 1" >>confdefs.h
 
-$as_echo "#define HAVE_STRLCPY /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_STRLNCPY /**/" >>confdefs.h
 
 fi
+
 done
 
-for ac_func in strlcat
+
+  for ac_func in getrusage
 do :
-  ac_fn_c_check_func "$LINENO" "strlcat" "ac_cv_func_strlcat"
-if test "x$ac_cv_func_strlcat" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_STRLCAT 1
+  ac_fn_c_check_func "$LINENO" "getrusage" "ac_cv_func_getrusage"
+if test "x$ac_cv_func_getrusage" = xyes
+then :
+  printf "%s\n" "#define HAVE_GETRUSAGE 1" >>confdefs.h
+
+printf "%s\n" "#define GETRUSAGE_2 /**/" >>confdefs.h
+
+else $as_nop
+
+  for ac_func in times
+do :
+  ac_fn_c_check_func "$LINENO" "times" "ac_cv_func_times"
+if test "x$ac_cv_func_times" = xyes
+then :
+  printf "%s\n" "#define HAVE_TIMES 1" >>confdefs.h
+
+printf "%s\n" "#define TIMES_2 /**/" >>confdefs.h
+
+fi
+
+done
+fi
+
+done
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+printf %s "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if test ${ac_cv_prog_CPP+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+      # Double quotes because $CC needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+		     Syntax error
 _ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
 
-$as_echo "#define HAVE_STRLCAT /**/" >>confdefs.h
+else $as_nop
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
 
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+  # Broken: success on invalid input.
+continue
+else $as_nop
+  # Passes both tests.
+ac_preproc_ok=:
+break
 fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
 done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok
+then :
+  break
+fi
 
-for ac_func in strlncat
-do :
-  ac_fn_c_check_func "$LINENO" "strlncat" "ac_cv_func_strlncat"
-if test "x$ac_cv_func_strlncat" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_STRLNCAT 1
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+printf "%s\n" "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+
+else $as_nop
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
 _ACEOF
+if ac_fn_c_try_cpp "$LINENO"
+then :
+  # Broken: success on invalid input.
+continue
+else $as_nop
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok
+then :
+
+else $as_nop
+  { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+printf %s "checking for grep that handles long lines and -e... " >&6; }
+if test ${ac_cv_path_GREP+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_prog in grep ggrep
+   do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  printf %s 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    printf "%s\n" 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+printf "%s\n" "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
 
-$as_echo "#define HAVE_STRLNCAT /**/" >>confdefs.h
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+printf %s "checking for egrep... " >&6; }
+if test ${ac_cv_path_EGREP+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    for ac_prog in egrep
+   do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  printf %s 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    printf "%s\n" 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
 
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
 fi
-done
-
-for ac_func in strlncpy
-do :
-  ac_fn_c_check_func "$LINENO" "strlncpy" "ac_cv_func_strlncpy"
-if test "x$ac_cv_func_strlncpy" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_STRLNCPY 1
-_ACEOF
-
-$as_echo "#define HAVE_STRLNCPY /**/" >>confdefs.h
 
+   fi
 fi
-done
-
-
-for ac_func in getrusage
-do :
-  ac_fn_c_check_func "$LINENO" "getrusage" "ac_cv_func_getrusage"
-if test "x$ac_cv_func_getrusage" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_GETRUSAGE 1
-_ACEOF
-
-$as_echo "#define GETRUSAGE_2 /**/" >>confdefs.h
-
-else
-  for ac_func in times
-do :
-  ac_fn_c_check_func "$LINENO" "times" "ac_cv_func_times"
-if test "x$ac_cv_func_times" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_TIMES 1
-_ACEOF
-
-$as_echo "#define TIMES_2 /**/" >>confdefs.h
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+printf "%s\n" "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
 
-fi
-done
 
-fi
-done
 
-for ac_func in setproctitle
+  for ac_func in setproctitle
 do :
   ac_fn_c_check_func "$LINENO" "setproctitle" "ac_cv_func_setproctitle"
-if test "x$ac_cv_func_setproctitle" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_SETPROCTITLE 1
-_ACEOF
-
-$as_echo "#define HAVE_SETPROCTITLE /**/" >>confdefs.h
-
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lutil" >&5
-$as_echo_n "checking for setproctitle in -lutil... " >&6; }
-if ${ac_cv_lib_util_setproctitle+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+if test "x$ac_cv_func_setproctitle" = xyes
+then :
+  printf "%s\n" "#define HAVE_SETPROCTITLE 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_SETPROCTITLE /**/" >>confdefs.h
+
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for setproctitle in -lutil" >&5
+printf %s "checking for setproctitle in -lutil... " >&6; }
+if test ${ac_cv_lib_util_setproctitle+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   ac_check_lib_save_LIBS=$LIBS
 LIBS="-lutil  $LIBS"
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -6265,35 +6747,34 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* Override any GCC internal prototype to avoid an error.
    Use char because int might match the return type of a GCC
    builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
 char setproctitle ();
 int
-main ()
+main (void)
 {
 return setproctitle ();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ac_cv_lib_util_setproctitle=yes
-else
+else $as_nop
   ac_cv_lib_util_setproctitle=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS=$ac_check_lib_save_LIBS
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_util_setproctitle" >&5
-$as_echo "$ac_cv_lib_util_setproctitle" >&6; }
-if test "x$ac_cv_lib_util_setproctitle" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_util_setproctitle" >&5
+printf "%s\n" "$ac_cv_lib_util_setproctitle" >&6; }
+if test "x$ac_cv_lib_util_setproctitle" = xyes
+then :
 
-$as_echo "#define HAVE_SETPROCTITLE /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_SETPROCTITLE /**/" >>confdefs.h
 
 		IRCDLIBS="$IRCDLIBS-lutil"
-else
+else $as_nop
 
 		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
@@ -6301,26 +6782,27 @@ else
 
 _ACEOF
 if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
-  $EGREP "#define.*PS_STRINGS.*" >/dev/null 2>&1; then :
+  $EGREP "#define.*PS_STRINGS.*" >/dev/null 2>&1
+then :
 
-$as_echo "#define HAVE_PSSTRINGS /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_PSSTRINGS /**/" >>confdefs.h
+
+else $as_nop
 
-else
   for ac_func in pstat
 do :
   ac_fn_c_check_func "$LINENO" "pstat" "ac_cv_func_pstat"
-if test "x$ac_cv_func_pstat" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_PSTAT 1
-_ACEOF
+if test "x$ac_cv_func_pstat" = xyes
+then :
+  printf "%s\n" "#define HAVE_PSTAT 1" >>confdefs.h
 
-$as_echo "#define HAVE_PSTAT /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_PSTAT /**/" >>confdefs.h
 
 fi
-done
 
+done
 fi
-rm -f conftest*
+rm -rf conftest*
 
 
 fi
@@ -6328,81 +6810,89 @@ fi
 
 
 fi
+
 done
 
 
-for ac_func in explicit_bzero
+  for ac_func in explicit_bzero
 do :
   ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero"
-if test "x$ac_cv_func_explicit_bzero" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_EXPLICIT_BZERO 1
-_ACEOF
+if test "x$ac_cv_func_explicit_bzero" = xyes
+then :
+  printf "%s\n" "#define HAVE_EXPLICIT_BZERO 1" >>confdefs.h
 
-$as_echo "#define HAVE_EXPLICIT_BZERO /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_EXPLICIT_BZERO /**/" >>confdefs.h
 
 fi
+
 done
 
-for ac_func in syslog
+  for ac_func in syslog
 do :
   ac_fn_c_check_func "$LINENO" "syslog" "ac_cv_func_syslog"
-if test "x$ac_cv_func_syslog" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_SYSLOG 1
-_ACEOF
+if test "x$ac_cv_func_syslog" = xyes
+then :
+  printf "%s\n" "#define HAVE_SYSLOG 1" >>confdefs.h
 
-$as_echo "#define HAVE_SYSLOG /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_SYSLOG /**/" >>confdefs.h
 
 fi
+
 done
 
+  for ac_func in strnlen
+do :
+  ac_fn_c_check_func "$LINENO" "strnlen" "ac_cv_func_strnlen"
+if test "x$ac_cv_func_strnlen" = xyes
+then :
+  printf "%s\n" "#define HAVE_STRNLEN 1" >>confdefs.h
+
+printf "%s\n" "#define HAVE_STRNLEN /**/" >>confdefs.h
+
+fi
+
+done
 
 
 
 
 # Check whether --with-nick-history was given.
-if test "${with_nick_history+set}" = set; then :
+if test ${with_nick_history+y}
+then :
   withval=$with_nick_history;
-cat >>confdefs.h <<_ACEOF
-#define NICKNAMEHISTORYLENGTH $withval
-_ACEOF
+printf "%s\n" "#define NICKNAMEHISTORYLENGTH $withval" >>confdefs.h
 
-else
+else $as_nop
 
-$as_echo "#define NICKNAMEHISTORYLENGTH 2000" >>confdefs.h
+printf "%s\n" "#define NICKNAMEHISTORYLENGTH 2000" >>confdefs.h
 
 fi
 
 
 # Check whether --with-permissions was given.
-if test "${with_permissions+set}" = set; then :
+if test ${with_permissions+y}
+then :
   withval=$with_permissions;
-cat >>confdefs.h <<_ACEOF
-#define DEFAULT_PERMISSIONS 0$withval
-_ACEOF
+printf "%s\n" "#define DEFAULT_PERMISSIONS 0$withval" >>confdefs.h
 
-else
+else $as_nop
 
-$as_echo "#define DEFAULT_PERMISSIONS 0600" >>confdefs.h
+printf "%s\n" "#define DEFAULT_PERMISSIONS 0600" >>confdefs.h
 
 fi
 
 
 
 # Check whether --with-bindir was given.
-if test "${with_bindir+set}" = set; then :
+if test ${with_bindir+y}
+then :
   withval=$with_bindir;
-cat >>confdefs.h <<_ACEOF
-#define BINDIR "$withval"
-_ACEOF
+printf "%s\n" "#define BINDIR \"$withval\"" >>confdefs.h
 
 		BINDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define BINDIR "$HOME/unrealircd/bin"
-_ACEOF
+printf "%s\n" "#define BINDIR \"$HOME/unrealircd/bin\"" >>confdefs.h
 
 		BINDIR="$HOME/unrealircd/bin"
 fi
@@ -6410,18 +6900,15 @@ fi
 
 
 # Check whether --with-scriptdir was given.
-if test "${with_scriptdir+set}" = set; then :
+if test ${with_scriptdir+y}
+then :
   withval=$with_scriptdir;
-cat >>confdefs.h <<_ACEOF
-#define SCRIPTDIR "$withval"
-_ACEOF
+printf "%s\n" "#define SCRIPTDIR \"$withval\"" >>confdefs.h
 
 		SCRIPTDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define SCRIPTDIR "$HOME/unrealircd"
-_ACEOF
+printf "%s\n" "#define SCRIPTDIR \"$HOME/unrealircd\"" >>confdefs.h
 
 		SCRIPTDIR="$HOME/unrealircd"
 fi
@@ -6429,18 +6916,15 @@ fi
 
 
 # Check whether --with-confdir was given.
-if test "${with_confdir+set}" = set; then :
+if test ${with_confdir+y}
+then :
   withval=$with_confdir;
-cat >>confdefs.h <<_ACEOF
-#define CONFDIR "$withval"
-_ACEOF
+printf "%s\n" "#define CONFDIR \"$withval\"" >>confdefs.h
 
 		CONFDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define CONFDIR "$HOME/unrealircd/conf"
-_ACEOF
+printf "%s\n" "#define CONFDIR \"$HOME/unrealircd/conf\"" >>confdefs.h
 
 		CONFDIR="$HOME/unrealircd/conf"
 fi
@@ -6448,18 +6932,15 @@ fi
 
 
 # Check whether --with-builddir was given.
-if test "${with_builddir+set}" = set; then :
+if test ${with_builddir+y}
+then :
   withval=$with_builddir;
-cat >>confdefs.h <<_ACEOF
-#define BUILDDIR "$withval"
-_ACEOF
+printf "%s\n" "#define BUILDDIR \"$withval\"" >>confdefs.h
 
 		BUILDDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define BUILDDIR "$BUILDDIR_NOW"
-_ACEOF
+printf "%s\n" "#define BUILDDIR \"$BUILDDIR_NOW\"" >>confdefs.h
 
 		BUILDDIR="$BUILDDIR_NOW"
 fi
@@ -6467,18 +6948,15 @@ fi
 
 
 # Check whether --with-modulesdir was given.
-if test "${with_modulesdir+set}" = set; then :
+if test ${with_modulesdir+y}
+then :
   withval=$with_modulesdir;
-cat >>confdefs.h <<_ACEOF
-#define MODULESDIR "$withval"
-_ACEOF
+printf "%s\n" "#define MODULESDIR \"$withval\"" >>confdefs.h
 
 		MODULESDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define MODULESDIR "$HOME/unrealircd/modules"
-_ACEOF
+printf "%s\n" "#define MODULESDIR \"$HOME/unrealircd/modules\"" >>confdefs.h
 
 		MODULESDIR="$HOME/unrealircd/modules"
 fi
@@ -6486,18 +6964,15 @@ fi
 
 
 # Check whether --with-logdir was given.
-if test "${with_logdir+set}" = set; then :
+if test ${with_logdir+y}
+then :
   withval=$with_logdir;
-cat >>confdefs.h <<_ACEOF
-#define LOGDIR "$withval"
-_ACEOF
+printf "%s\n" "#define LOGDIR \"$withval\"" >>confdefs.h
 
 		LOGDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define LOGDIR "$HOME/unrealircd/logs"
-_ACEOF
+printf "%s\n" "#define LOGDIR \"$HOME/unrealircd/logs\"" >>confdefs.h
 
 		LOGDIR="$HOME/unrealircd/logs"
 fi
@@ -6505,18 +6980,15 @@ fi
 
 
 # Check whether --with-cachedir was given.
-if test "${with_cachedir+set}" = set; then :
+if test ${with_cachedir+y}
+then :
   withval=$with_cachedir;
-cat >>confdefs.h <<_ACEOF
-#define CACHEDIR "$withval"
-_ACEOF
+printf "%s\n" "#define CACHEDIR \"$withval\"" >>confdefs.h
 
 		CACHEDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define CACHEDIR "$HOME/unrealircd/cache"
-_ACEOF
+printf "%s\n" "#define CACHEDIR \"$HOME/unrealircd/cache\"" >>confdefs.h
 
 		CACHEDIR="$HOME/unrealircd/cache"
 fi
@@ -6524,18 +6996,15 @@ fi
 
 
 # Check whether --with-tmpdir was given.
-if test "${with_tmpdir+set}" = set; then :
+if test ${with_tmpdir+y}
+then :
   withval=$with_tmpdir;
-cat >>confdefs.h <<_ACEOF
-#define TMPDIR "$withval"
-_ACEOF
+printf "%s\n" "#define TMPDIR \"$withval\"" >>confdefs.h
 
 		TMPDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define TMPDIR "$HOME/unrealircd/tmp"
-_ACEOF
+printf "%s\n" "#define TMPDIR \"$HOME/unrealircd/tmp\"" >>confdefs.h
 
 		TMPDIR="$HOME/unrealircd/tmp"
 fi
@@ -6543,18 +7012,15 @@ fi
 
 
 # Check whether --with-datadir was given.
-if test "${with_datadir+set}" = set; then :
+if test ${with_datadir+y}
+then :
   withval=$with_datadir;
-cat >>confdefs.h <<_ACEOF
-#define PERMDATADIR "$withval"
-_ACEOF
+printf "%s\n" "#define PERMDATADIR \"$withval\"" >>confdefs.h
 
 		PERMDATADIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define DATADIR "$HOME/unrealircd/data"
-_ACEOF
+printf "%s\n" "#define DATADIR \"$HOME/unrealircd/data\"" >>confdefs.h
 
 		PERMDATADIR="$HOME/unrealircd/data"
 fi
@@ -6562,18 +7028,15 @@ fi
 
 
 # Check whether --with-docdir was given.
-if test "${with_docdir+set}" = set; then :
+if test ${with_docdir+y}
+then :
   withval=$with_docdir;
-cat >>confdefs.h <<_ACEOF
-#define DOCDIR "$withval"
-_ACEOF
+printf "%s\n" "#define DOCDIR \"$withval\"" >>confdefs.h
 
 		DOCDIR="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define DOCDIR "$HOME/unrealircd/doc"
-_ACEOF
+printf "%s\n" "#define DOCDIR \"$HOME/unrealircd/doc\"" >>confdefs.h
 
 		DOCDIR="$HOME/unrealircd/doc"
 fi
@@ -6581,18 +7044,15 @@ fi
 
 
 # Check whether --with-pidfile was given.
-if test "${with_pidfile+set}" = set; then :
+if test ${with_pidfile+y}
+then :
   withval=$with_pidfile;
-cat >>confdefs.h <<_ACEOF
-#define PIDFILE "$withval"
-_ACEOF
+printf "%s\n" "#define PIDFILE \"$withval\"" >>confdefs.h
 
 		PIDFILE="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define PIDFILE "$HOME/unrealircd/data/unrealircd.pid"
-_ACEOF
+printf "%s\n" "#define PIDFILE \"$HOME/unrealircd/data/unrealircd.pid\"" >>confdefs.h
 
 		PIDFILE="$HOME/unrealircd/data/unrealircd.pid"
 fi
@@ -6600,18 +7060,15 @@ fi
 
 
 # Check whether --with-controlfile was given.
-if test "${with_controlfile+set}" = set; then :
+if test ${with_controlfile+y}
+then :
   withval=$with_controlfile;
-cat >>confdefs.h <<_ACEOF
-#define CONTROLFILE "$withval"
-_ACEOF
+printf "%s\n" "#define CONTROLFILE \"$withval\"" >>confdefs.h
 
 		CONTROLFILE="$withval"
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define CONTROLFILE "$HOME/unrealircd/data/unrealircd.ctl"
-_ACEOF
+printf "%s\n" "#define CONTROLFILE \"$HOME/unrealircd/data/unrealircd.ctl\"" >>confdefs.h
 
 		CONTROLFILE="$HOME/unrealircd/data/unrealircd.ctl"
 fi
@@ -6619,26 +7076,28 @@ fi
 
 
 # Check whether --with-privatelibdir was given.
-if test "${with_privatelibdir+set}" = set; then :
+if test ${with_privatelibdir+y}
+then :
   withval=$with_privatelibdir;
-else
+else $as_nop
   with_privatelibdir="yes"
 fi
 
-if test "x$with_privatelibdir" = "xno"; then :
+if test "x$with_privatelibdir" = "xno"
+then :
   PRIVATELIBDIR=
-elif test "x$with_privatelibdir" = "xyes"; then :
+elif test "x$with_privatelibdir" = "xyes"
+then :
   PRIVATELIBDIR="$HOME/unrealircd/lib"
-else
+else $as_nop
   PRIVATELIBDIR="$with_privatelibdir"
 fi
-if test "x$PRIVATELIBDIR" = "x"; then :
+if test "x$PRIVATELIBDIR" = "x"
+then :
   LDFLAGS_PRIVATELIBS=""
-else
+else $as_nop
 
-cat >>confdefs.h <<_ACEOF
-#define PRIVATELIBDIR "$PRIVATELIBDIR"
-_ACEOF
+printf "%s\n" "#define PRIVATELIBDIR \"$PRIVATELIBDIR\"" >>confdefs.h
 
 		LDFLAGS_PRIVATELIBS="-Wl,-rpath,$PRIVATELIBDIR"
 		LDFLAGS="$LDFLAGS $LDFLAGS_PRIVATELIBS"
@@ -6661,95 +7120,105 @@ fi
 
 
 # Check whether --with-maxconnections was given.
-if test "${with_maxconnections+set}" = set; then :
+if test ${with_maxconnections+y}
+then :
   withval=$with_maxconnections; ac_fd=$withval
-else
+else $as_nop
   ac_fd=0
 fi
 
 
-cat >>confdefs.h <<_ACEOF
-#define MAXCONNECTIONS_REQUEST $ac_fd
-_ACEOF
+printf "%s\n" "#define MAXCONNECTIONS_REQUEST $ac_fd" >>confdefs.h
 
 
 
 # Check whether --with-no-operoverride was given.
-if test "${with_no_operoverride+set}" = set; then :
-  withval=$with_no_operoverride; if test $withval = "yes"; then :
+if test ${with_no_operoverride+y}
+then :
+  withval=$with_no_operoverride; if test $withval = "yes"
+then :
 
-$as_echo "#define NO_OPEROVERRIDE /**/" >>confdefs.h
+printf "%s\n" "#define NO_OPEROVERRIDE /**/" >>confdefs.h
 
 fi
 fi
 
 
 # Check whether --with-operoverride-verify was given.
-if test "${with_operoverride_verify+set}" = set; then :
-  withval=$with_operoverride_verify; if test $withval = "yes"; then :
+if test ${with_operoverride_verify+y}
+then :
+  withval=$with_operoverride_verify; if test $withval = "yes"
+then :
 
-$as_echo "#define OPEROVERRIDE_VERIFY /**/" >>confdefs.h
+printf "%s\n" "#define OPEROVERRIDE_VERIFY /**/" >>confdefs.h
 
 fi
 fi
 
 
 # Check whether --with-system-pcre2 was given.
-if test "${with_system_pcre2+set}" = set; then :
+if test ${with_system_pcre2+y}
+then :
   withval=$with_system_pcre2;
-else
+else $as_nop
   with_system_pcre2=yes
 fi
 
 
 # Check whether --with-system-argon2 was given.
-if test "${with_system_argon2+set}" = set; then :
+if test ${with_system_argon2+y}
+then :
   withval=$with_system_argon2;
-else
+else $as_nop
   with_system_argon2=yes
 fi
 
 
 # Check whether --with-system-sodium was given.
-if test "${with_system_sodium+set}" = set; then :
+if test ${with_system_sodium+y}
+then :
   withval=$with_system_sodium;
-else
+else $as_nop
   with_system_sodium=yes
 fi
 
 
 # Check whether --with-system-cares was given.
-if test "${with_system_cares+set}" = set; then :
+if test ${with_system_cares+y}
+then :
   withval=$with_system_cares;
-else
+else $as_nop
   with_system_cares=yes
 fi
 
 
 # Check whether --with-system-jansson was given.
-if test "${with_system_jansson+set}" = set; then :
+if test ${with_system_jansson+y}
+then :
   withval=$with_system_jansson;
-else
+else $as_nop
   with_system_jansson=yes
 fi
 
 
 # Check whether --enable-ssl was given.
-if test "${enable_ssl+set}" = set; then :
+if test ${enable_ssl+y}
+then :
   enableval=$enable_ssl;
-else
+else $as_nop
   enable_ssl=no
 fi
 
-if test $enable_ssl != "no"; then :
+if test $enable_ssl != "no"
+then :
 
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for OpenSSL" >&5
-$as_echo_n "checking for OpenSSL... " >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for OpenSSL" >&5
+printf %s "checking for OpenSSL... " >&6; }
 	for dir in $enable_ssl /usr/local/opt/openssl /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/sfw /usr/local /usr; do
 		ssldir="$dir"
 		if test -f "$dir/include/openssl/ssl.h"; then
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: found in $ssldir/include/openssl" >&5
-$as_echo "found in $ssldir/include/openssl" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found in $ssldir/include/openssl" >&5
+printf "%s\n" "found in $ssldir/include/openssl" >&6; }
 			found_ssl="yes";
 			if test ! "$ssldir" = "/usr" ; then
 				CFLAGS="$CFLAGS -I$ssldir/include";
@@ -6757,8 +7226,8 @@ $as_echo "found in $ssldir/include/openssl" >&6; }
 			break
 		fi
 		if test -f "$dir/include/ssl.h"; then
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: found in $ssldir/include" >&5
-$as_echo "found in $ssldir/include" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found in $ssldir/include" >&5
+printf "%s\n" "found in $ssldir/include" >&6; }
 			found_ssl="yes";
 			if test ! "$ssldir" = "/usr" ; then
 				CFLAGS="$CFLAGS -I$ssldir/include";
@@ -6767,8 +7236,8 @@ $as_echo "found in $ssldir/include" >&6; }
 		fi
 	done
 	if test x_$found_ssl != x_yes; then
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5
-$as_echo "not found" >&6; }
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not found" >&5
+printf "%s\n" "not found" >&6; }
 		echo ""
 		echo "Apparently you do not have both the openssl binary and openssl development libraries installed."
 		echo "The following packages are required:"
@@ -6789,63 +7258,65 @@ $as_echo "not found" >&6; }
 			    OPENSSLPATH="$ssldir/bin/openssl";
 			fi
 		fi
-				{ $as_echo "$as_me:${as_lineno-$LINENO}: checking OpenSSL linking with -ldl" >&5
-$as_echo_n "checking OpenSSL linking with -ldl... " >&6; }
+				{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking OpenSSL linking with -ldl" >&5
+printf %s "checking OpenSSL linking with -ldl... " >&6; }
 		SAVE_LIBS="$LIBS"
 		LIBS="$LIBS $CRYPTOLIB -ldl"
 		cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/err.h>
 int
-main ()
+main (void)
 {
 ERR_clear_error();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
 
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 			CRYPTOLIB="$CRYPTOLIB -ldl"
 
-else
+else $as_nop
 
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
-						{ $as_echo "$as_me:${as_lineno-$LINENO}: checking OpenSSL linking with -ldl and -lpthread" >&5
-$as_echo_n "checking OpenSSL linking with -ldl and -lpthread... " >&6; }
+						{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking OpenSSL linking with -ldl and -lpthread" >&5
+printf %s "checking OpenSSL linking with -ldl and -lpthread... " >&6; }
 			LIBS="$SAVE_LIBS $CRYPTOLIB -ldl -lpthread"
 			cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/err.h>
 int
-main ()
+main (void)
 {
 ERR_clear_error();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
 
-				{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+				{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 				CRYPTOLIB="$CRYPTOLIB -ldl -lpthread"
 
-else
+else $as_nop
 
-				{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+				{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 		LIBS="$SAVE_LIBS"
 	fi
@@ -6853,8 +7324,8 @@ rm -f core conftest.err conftest.$ac_objext \
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set1_curves_list in SSL library" >&5
-$as_echo_n "checking for SSL_CTX_set1_curves_list in SSL library... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set1_curves_list in SSL library" >&5
+printf %s "checking for SSL_CTX_set1_curves_list in SSL library... " >&6; }
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -6867,19 +7338,20 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/ssl.h>
 int
-main ()
+main (void)
 {
 SSL_CTX *ctx = NULL; SSL_CTX_set1_curves_list(ctx, "test");
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   has_function=1
-else
+else $as_nop
   has_function=0
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS="$SAVE_LIBS"
 ac_ext=c
@@ -6889,19 +7361,19 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 if test $has_function = 1; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 
-$as_echo "#define HAS_SSL_CTX_SET1_CURVES_LIST /**/" >>confdefs.h
+printf "%s\n" "#define HAS_SSL_CTX_SET1_CURVES_LIST /**/" >>confdefs.h
 
 else
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set_min_proto_version in SSL library" >&5
-$as_echo_n "checking for SSL_CTX_set_min_proto_version in SSL library... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set_min_proto_version in SSL library" >&5
+printf %s "checking for SSL_CTX_set_min_proto_version in SSL library... " >&6; }
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -6914,19 +7386,20 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/ssl.h>
 int
-main ()
+main (void)
 {
 SSL_CTX *ctx = NULL; SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   has_function=1
-else
+else $as_nop
   has_function=0
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS="$SAVE_LIBS"
 ac_ext=c
@@ -6936,19 +7409,19 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 if test $has_function = 1; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 
-$as_echo "#define HAS_SSL_CTX_SET_MIN_PROTO_VERSION /**/" >>confdefs.h
+printf "%s\n" "#define HAS_SSL_CTX_SET_MIN_PROTO_VERSION /**/" >>confdefs.h
 
 else
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set_security_level in SSL library" >&5
-$as_echo_n "checking for SSL_CTX_set_security_level in SSL library... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SSL_CTX_set_security_level in SSL library" >&5
+printf %s "checking for SSL_CTX_set_security_level in SSL library... " >&6; }
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -6961,19 +7434,20 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/ssl.h>
 int
-main ()
+main (void)
 {
 SSL_CTX *ctx = NULL; SSL_CTX_set_security_level(ctx, 1);
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   has_function=1
-else
+else $as_nop
   has_function=0
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS="$SAVE_LIBS"
 ac_ext=c
@@ -6983,19 +7457,19 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 if test $has_function = 1; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 
-$as_echo "#define HAS_SSL_CTX_SET_SECURITY_LEVEL /**/" >>confdefs.h
+printf "%s\n" "#define HAS_SSL_CTX_SET_SECURITY_LEVEL /**/" >>confdefs.h
 
 else
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ASN1_TIME_diff in SSL library" >&5
-$as_echo_n "checking for ASN1_TIME_diff in SSL library... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ASN1_TIME_diff in SSL library" >&5
+printf %s "checking for ASN1_TIME_diff in SSL library... " >&6; }
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -7008,19 +7482,20 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/ssl.h>
 int
-main ()
+main (void)
 {
 int one, two; ASN1_TIME_diff(&one, &two, NULL, NULL);
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   has_function=1
-else
+else $as_nop
   has_function=0
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS="$SAVE_LIBS"
 ac_ext=c
@@ -7030,19 +7505,19 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 if test $has_function = 1; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 
-$as_echo "#define HAS_ASN1_TIME_diff /**/" >>confdefs.h
+printf "%s\n" "#define HAS_ASN1_TIME_diff /**/" >>confdefs.h
 
 else
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for X509_get0_notAfter in SSL library" >&5
-$as_echo_n "checking for X509_get0_notAfter in SSL library... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for X509_get0_notAfter in SSL library" >&5
+printf %s "checking for X509_get0_notAfter in SSL library... " >&6; }
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -7055,19 +7530,68 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <openssl/ssl.h>
 int
-main ()
+main (void)
 {
 X509_get0_notAfter(NULL);
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   has_function=1
+else $as_nop
+  has_function=0
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS="$SAVE_LIBS"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test $has_function = 1; then
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+
+printf "%s\n" "#define HAS_X509_get0_notAfter /**/" >>confdefs.h
+
 else
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for X509_check_host in SSL library" >&5
+printf %s "checking for X509_check_host in SSL library... " >&6; }
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+SAVE_LIBS="$LIBS"
+LIBS="$LIBS $CRYPTOLIB"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <openssl/x509v3.h>
+int
+main (void)
+{
+X509_check_host(NULL, NULL, 0, 0, NULL);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  has_function=1
+else $as_nop
   has_function=0
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS="$SAVE_LIBS"
 ac_ext=c
@@ -7077,34 +7601,38 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 if test $has_function = 1; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 
-$as_echo "#define HAS_X509_get0_notAfter /**/" >>confdefs.h
+printf "%s\n" "#define HAS_X509_check_host /**/" >>confdefs.h
 
 else
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 # Check whether --enable-dynamic-linking was given.
-if test "${enable_dynamic_linking+set}" = set; then :
+if test ${enable_dynamic_linking+y}
+then :
   enableval=$enable_dynamic_linking; enable_dynamic_linking=$enableval
-else
+else $as_nop
   enable_dynamic_linking="yes"
 fi
 
-if test $enable_dynamic_linking = "yes"; then :
+if test $enable_dynamic_linking = "yes"
+then :
 
 ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
-if test "x$ac_cv_func_dlopen" = xyes; then :
-
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
-$as_echo_n "checking for dlopen in -ldl... " >&6; }
-if ${ac_cv_lib_dl_dlopen+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+if test "x$ac_cv_func_dlopen" = xyes
+then :
+
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+printf %s "checking for dlopen in -ldl... " >&6; }
+if test ${ac_cv_lib_dl_dlopen+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   ac_check_lib_save_LIBS=$LIBS
 LIBS="-ldl  $LIBS"
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -7113,36 +7641,35 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* Override any GCC internal prototype to avoid an error.
    Use char because int might match the return type of a GCC
    builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
 char dlopen ();
 int
-main ()
+main (void)
 {
 return dlopen ();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ac_cv_lib_dl_dlopen=yes
-else
+else $as_nop
   ac_cv_lib_dl_dlopen=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 LIBS=$ac_check_lib_save_LIBS
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
-$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
-if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes
+then :
   IRCDLIBS="$IRCDLIBS -ldl"
-else
+else $as_nop
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Dynamic linking is not enabled because dlopen was not found" >&5
-$as_echo "$as_me: WARNING: Dynamic linking is not enabled because dlopen was not found" >&2;}
-$as_echo "#define STATIC_LINKING 1" >>confdefs.h
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Dynamic linking is not enabled because dlopen was not found" >&5
+printf "%s\n" "$as_me: WARNING: Dynamic linking is not enabled because dlopen was not found" >&2;}
+printf "%s\n" "#define STATIC_LINKING 1" >>confdefs.h
 
 
 fi
@@ -7153,43 +7680,46 @@ fi
 hold_cflags=$CFLAGS
 DYNAMIC_LDFLAGS=""
 CFLAGS="$CFLAGS -Wl,-export-dynamic"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if we need the -export-dynamic flag" >&5
-$as_echo_n "checking if we need the -export-dynamic flag... " >&6; }
-if ${ac_cv_export_dynamic+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we need the -export-dynamic flag" >&5
+printf %s "checking if we need the -export-dynamic flag... " >&6; }
+if test ${ac_cv_export_dynamic+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
 int
-main ()
+main (void)
 {
 int i;
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ac_cv_export_dynamic=yes
-else
+else $as_nop
   ac_cv_export_dynamic=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_export_dynamic" >&5
-$as_echo "$ac_cv_export_dynamic" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_export_dynamic" >&5
+printf "%s\n" "$ac_cv_export_dynamic" >&6; }
 CFLAGS=$hold_cflags
 if test "$ac_cv_export_dynamic" = "yes"; then
 	DYNAMIC_LDFLAGS="-Wl,-export-dynamic"
 fi
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler option to produce PIC" >&5
-$as_echo_n "checking for compiler option to produce PIC... " >&6; }
-if ${ac_cv_pic+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for compiler option to produce PIC" >&5
+printf %s "checking for compiler option to produce PIC... " >&6; }
+if test ${ac_cv_pic+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
 if test "$ac_cv_c_compiler_gnu" = "yes"; then
 	ac_cv_pic="-fPIC -DPIC -shared"
@@ -7210,16 +7740,17 @@ esac
 fi
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_pic" >&5
-$as_echo "$ac_cv_pic" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if your system prepends an underscore on symbols" >&5
-$as_echo_n "checking if your system prepends an underscore on symbols... " >&6; }
-if ${ac_cv_underscore+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_pic" >&5
+printf "%s\n" "$ac_cv_pic" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if your system prepends an underscore on symbols" >&5
+printf %s "checking if your system prepends an underscore on symbols... " >&6; }
+if test ${ac_cv_underscore+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
 cat >uscore.c << __EOF__
-int main() {
+int main(void) {
 	return 0;
 }
 __EOF__
@@ -7232,79 +7763,85 @@ fi
 rm -f uscore uscore.c
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_underscore" >&5
-$as_echo "$ac_cv_underscore" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_underscore" >&5
+printf "%s\n" "$ac_cv_underscore" >&6; }
 if test "$ac_cv_underscore" = "yes"; then
 
-$as_echo "#define UNDERSCORE /**/" >>confdefs.h
+printf "%s\n" "#define UNDERSCORE /**/" >>confdefs.h
 
 fi
 
 MODULEFLAGS="$ac_cv_pic $DYNAMIC_LDFLAGS"
 
-else
+else $as_nop
 
-$as_echo "#define STATIC_LINKING /**/" >>confdefs.h
+printf "%s\n" "#define STATIC_LINKING /**/" >>confdefs.h
 
 fi
 
 # Check whether --enable-werror was given.
-if test "${enable_werror+set}" = set; then :
+if test ${enable_werror+y}
+then :
   enableval=$enable_werror; ac_cv_werror="$enableval"
-else
+else $as_nop
   ac_cv_werror="no"
 fi
 
 
 # Check whether --enable-asan was given.
-if test "${enable_asan+set}" = set; then :
+if test ${enable_asan+y}
+then :
   enableval=$enable_asan; ac_cv_asan="$enableval"
-else
+else $as_nop
   ac_cv_asan="no"
 fi
 
 
-for ac_func in poll
+
+  for ac_func in poll
 do :
   ac_fn_c_check_func "$LINENO" "poll" "ac_cv_func_poll"
-if test "x$ac_cv_func_poll" = xyes; then :
-  cat >>confdefs.h <<_ACEOF
-#define HAVE_POLL 1
-_ACEOF
+if test "x$ac_cv_func_poll" = xyes
+then :
+  printf "%s\n" "#define HAVE_POLL 1" >>confdefs.h
 
-$as_echo "#define HAVE_POLL /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_POLL /**/" >>confdefs.h
 
 fi
+
 done
 
-for ac_func in epoll_create epoll_ctl epoll_wait
+  for ac_func in epoll_create epoll_ctl epoll_wait
 do :
-  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+  as_ac_var=`printf "%s\n" "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
-if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+if eval test \"x\$"$as_ac_var"\" = x"yes"
+then :
   cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+#define `printf "%s\n" "HAVE_$ac_func" | $as_tr_cpp` 1
 _ACEOF
 
-$as_echo "#define HAVE_EPOLL /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_EPOLL /**/" >>confdefs.h
 
 fi
+
 done
 
-for ac_func in kqueue kevent
+  for ac_func in kqueue kevent
 do :
-  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+  as_ac_var=`printf "%s\n" "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
-if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+if eval test \"x\$"$as_ac_var"\" = x"yes"
+then :
   cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+#define `printf "%s\n" "HAVE_$ac_func" | $as_tr_cpp` 1
 _ACEOF
 
-$as_echo "#define HAVE_KQUEUE /**/" >>confdefs.h
+printf "%s\n" "#define HAVE_KQUEUE /**/" >>confdefs.h
 
 fi
-done
 
+done
 
 export PATH_SEPARATOR
 
@@ -7320,11 +7857,12 @@ if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
 	if test -n "$ac_tool_prefix"; then
   # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
 set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_PKG_CONFIG+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $PKG_CONFIG in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
@@ -7334,11 +7872,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -7350,11 +7892,11 @@ esac
 fi
 PKG_CONFIG=$ac_cv_path_PKG_CONFIG
 if test -n "$PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
-$as_echo "$PKG_CONFIG" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+printf "%s\n" "$PKG_CONFIG" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -7363,11 +7905,12 @@ if test -z "$ac_cv_path_PKG_CONFIG"; then
   ac_pt_PKG_CONFIG=$PKG_CONFIG
   # Extract the first word of "pkg-config", so it can be a program name with args.
 set dummy pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_path_ac_pt_PKG_CONFIG+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   case $ac_pt_PKG_CONFIG in
   [\\/]* | ?:[\\/]*)
   ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
@@ -7377,11 +7920,15 @@ else
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext"
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -7393,11 +7940,11 @@ esac
 fi
 ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
 if test -n "$ac_pt_PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
-$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+printf "%s\n" "$ac_pt_PKG_CONFIG" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
   if test "x$ac_pt_PKG_CONFIG" = x; then
@@ -7405,8 +7952,8 @@ fi
   else
     case $cross_compiling:$ac_tool_warned in
 yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
 ac_tool_warned=yes ;;
 esac
     PKG_CONFIG=$ac_pt_PKG_CONFIG
@@ -7418,34 +7965,35 @@ fi
 fi
 if test -n "$PKG_CONFIG"; then
 	_pkg_min_version=0.9.0
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
-$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+printf %s "checking pkg-config is at least version $_pkg_min_version... " >&6; }
 	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	else
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 		PKG_CONFIG=""
 	fi
 fi
-if test "x$with_system_pcre2" = "xyes"; then :
+if test "x$with_system_pcre2" = "xyes"
+then :
 
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PCRE2" >&5
-$as_echo_n "checking for PCRE2... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PCRE2" >&5
+printf %s "checking for PCRE2... " >&6; }
 
 if test -n "$PCRE2_CFLAGS"; then
     pkg_cv_PCRE2_CFLAGS="$PCRE2_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.00\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.00") 2>&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.36\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.36") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_PCRE2_CFLAGS=`$PKG_CONFIG --cflags "libpcre2-8 >= 10.00" 2>/dev/null`
+  pkg_cv_PCRE2_CFLAGS=`$PKG_CONFIG --cflags "libpcre2-8 >= 10.36" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -7457,12 +8005,12 @@ if test -n "$PCRE2_LIBS"; then
     pkg_cv_PCRE2_LIBS="$PCRE2_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.00\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.00") 2>&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre2-8 >= 10.36\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpcre2-8 >= 10.36") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_PCRE2_LIBS=`$PKG_CONFIG --libs "libpcre2-8 >= 10.00" 2>/dev/null`
+  pkg_cv_PCRE2_LIBS=`$PKG_CONFIG --libs "libpcre2-8 >= 10.36" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -7474,8 +8022,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -7483,35 +8031,37 @@ else
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre2-8 >= 10.00" 2>&1`
+	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre2-8 >= 10.36" 2>&1`
         else
-	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre2-8 >= 10.00" 2>&1`
+	        PCRE2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre2-8 >= 10.36" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$PCRE2_PKG_ERRORS" >&5
 
 	has_system_pcre2=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_pcre2=no
 else
 	PCRE2_CFLAGS=$pkg_cv_PCRE2_CFLAGS
 	PCRE2_LIBS=$pkg_cv_PCRE2_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_pcre2=yes
-if test "x$PRIVATELIBDIR" != "x"; then :
+if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libpcre2*
 fi
 fi
 fi
 
-if test "$has_system_pcre2" = "no"; then :
+if test "$has_system_pcre2" = "no"
+then :
 
-pcre2_version="10.39"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting PCRE2 regex library" >&5
-$as_echo "extracting PCRE2 regex library" >&6; }
+pcre2_version="10.42"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting PCRE2 regex library" >&5
+printf "%s\n" "extracting PCRE2 regex library" >&6; }
 cur_dir=`pwd`
 cd extras
 rm -rf pcre2-$pcre2_version pcre2
@@ -7523,23 +8073,26 @@ else
 	cp pcre2.tar.gz.bak pcre2.tar.gz
 	tar xf pcre2.tar
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring PCRE2 regex library" >&5
-$as_echo "configuring PCRE2 regex library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: configuring PCRE2 regex library" >&5
+printf "%s\n" "configuring PCRE2 regex library" >&6; }
 cd pcre2-$pcre2_version
 ./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; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling PCRE2 regex library" >&5
+printf "%s\n" "compiling PCRE2 regex library" >&6; }
 $ac_cv_prog_MAKER || exit 1
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing PCRE2 regex library" >&5
-$as_echo "installing PCRE2 regex library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing PCRE2 regex library" >&5
+printf "%s\n" "installing PCRE2 regex library" >&6; }
+rm -f "$PRIVATELIBDIR/"libpcre2*
 $ac_cv_prog_MAKER install || exit 1
 PCRE2_CFLAGS="-I$cur_dir/extras/pcre2/include"
 
 PCRE2_LIBS=
-if test -n "$ac_cv_path_PKGCONFIG"; then :
+if test -n "$ac_cv_path_PKGCONFIG"
+then :
   PCRE2_LIBS="`$ac_cv_path_PKGCONFIG --libs libpcre2-8.pc`"
 fi
-if test -z "$PCRE2_LIBS"; then :
+if test -z "$PCRE2_LIBS"
+then :
   PCRE2_LIBS="$PRIVATELIBDIR/libpcre2-8.so"
 fi
 
@@ -7548,21 +8101,22 @@ cd $cur_dir
 fi
 
 has_system_argon2="no"
-if test "x$with_system_argon2" = "xyes"; then :
+if test "x$with_system_argon2" = "xyes"
+then :
 
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ARGON2" >&5
-$as_echo_n "checking for ARGON2... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ARGON2" >&5
+printf %s "checking for ARGON2... " >&6; }
 
 if test -n "$ARGON2_CFLAGS"; then
     pkg_cv_ARGON2_CFLAGS="$ARGON2_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0~20161029\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0~20161029\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libargon2 >= 0~20161029") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_ARGON2_CFLAGS=`$PKG_CONFIG --cflags "libargon2 >= 0~20161029" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7576,10 +8130,10 @@ if test -n "$ARGON2_LIBS"; then
     pkg_cv_ARGON2_LIBS="$ARGON2_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0~20161029\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libargon2 >= 0~20161029\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libargon2 >= 0~20161029") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_ARGON2_LIBS=`$PKG_CONFIG --libs "libargon2 >= 0~20161029" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7593,8 +8147,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -7611,43 +8165,45 @@ fi
 
 	has_system_argon2=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_argon2=no
 else
 	ARGON2_CFLAGS=$pkg_cv_ARGON2_CFLAGS
 	ARGON2_LIBS=$pkg_cv_ARGON2_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_argon2=yes
-if test "x$PRIVATELIBDIR" != "x"; then :
+if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libargon2*
 fi
 fi
 fi
 
-if test "$has_system_argon2" = "no"; then :
+if test "$has_system_argon2" = "no"
+then :
 
-argon2_version="20181209"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting Argon2 library" >&5
-$as_echo "extracting Argon2 library" >&6; }
+argon2_version="20190702"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting Argon2 library" >&5
+printf "%s\n" "extracting Argon2 library" >&6; }
 cur_dir=`pwd`
 cd extras
 rm -rf argon2-$argon2_version argon2
 if test "x$ac_cv_path_GUNZIP" = "x" ; then
-	tar xfz argon2-$argon2_version.tar.gz
+	tar xfz argon2.tar.gz
 else
-	cp argon2-$argon2_version.tar.gz argon2-$argon2_version.tar.gz.bak
-	gunzip -f argon2-$argon2_version.tar.gz
-	cp argon2-$argon2_version.tar.gz.bak argon2-$argon2_version.tar.gz
-	tar xf argon2-$argon2_version.tar
+	cp argon2.tar.gz argon2.tar.gz.bak
+	gunzip -f argon2.tar.gz
+	cp argon2.tar.gz.bak argon2.tar.gz
+	tar xf argon2.tar
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling Argon2 library" >&5
-$as_echo "compiling Argon2 library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling Argon2 library" >&5
+printf "%s\n" "compiling Argon2 library" >&6; }
 cd argon2-$argon2_version
 $ac_cv_prog_MAKER || exit 1
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing Argon2 library" >&5
-$as_echo "installing Argon2 library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing Argon2 library" >&5
+printf "%s\n" "installing Argon2 library" >&6; }
 $ac_cv_prog_MAKER install PREFIX=$cur_dir/extras/argon2 || exit 1
 # We need to manually copy the libs to PRIVATELIBDIR because
 # there is no way to tell make install in libargon2 to do so.
@@ -7666,21 +8222,22 @@ cd $cur_dir
 fi
 
 has_system_sodium="no"
-if test "x$with_system_sodium" = "xyes"; then :
+if test "x$with_system_sodium" = "xyes"
+then :
 
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SODIUM" >&5
-$as_echo_n "checking for SODIUM... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for SODIUM" >&5
+printf %s "checking for SODIUM... " >&6; }
 
 if test -n "$SODIUM_CFLAGS"; then
     pkg_cv_SODIUM_CFLAGS="$SODIUM_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libsodium >= 1.0.16") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_SODIUM_CFLAGS=`$PKG_CONFIG --cflags "libsodium >= 1.0.16" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7694,10 +8251,10 @@ if test -n "$SODIUM_LIBS"; then
     pkg_cv_SODIUM_LIBS="$SODIUM_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsodium >= 1.0.16\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libsodium >= 1.0.16") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_SODIUM_LIBS=`$PKG_CONFIG --libs "libsodium >= 1.0.16" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7711,8 +8268,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -7729,26 +8286,28 @@ fi
 
 	has_system_sodium=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_sodium=no
 else
 	SODIUM_CFLAGS=$pkg_cv_SODIUM_CFLAGS
 	SODIUM_LIBS=$pkg_cv_SODIUM_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_sodium=yes
-if test "x$PRIVATELIBDIR" != "x"; then :
+if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libsodium*
 fi
 fi
 fi
 
-if test "$has_system_sodium" = "no"; then :
+if test "$has_system_sodium" = "no"
+then :
 
 sodium_version="1.0.18"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting sodium library" >&5
-$as_echo "extracting sodium library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting sodium library" >&5
+printf "%s\n" "extracting sodium library" >&6; }
 cur_dir=`pwd`
 cd extras
 rm -rf sodium-$sodium_version sodium
@@ -7760,27 +8319,30 @@ else
 	cp libsodium.tar.gz.bak libsodium.tar.gz
 	tar xf libsodium.tar
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling sodium library" >&5
-$as_echo "compiling sodium library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling sodium library" >&5
+printf "%s\n" "compiling sodium library" >&6; }
 cd libsodium-$sodium_version
 save_cflags="$CFLAGS"
 CFLAGS="$orig_cflags"
 export CFLAGS
 ./configure --prefix=$cur_dir/extras/sodium --libdir=$PRIVATELIBDIR --enable-shared --disable-static --enable-opt || exit 1
 CFLAGS="$save_cflags"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling sodium resolver library" >&5
-$as_echo "compiling sodium resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling sodium resolver library" >&5
+printf "%s\n" "compiling sodium resolver library" >&6; }
 $ac_cv_prog_MAKER || exit 1
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing sodium resolver library" >&5
-$as_echo "installing sodium resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing sodium resolver library" >&5
+printf "%s\n" "installing sodium resolver library" >&6; }
+rm -f "$PRIVATELIBDIR/"libsodium*
 $ac_cv_prog_MAKER install || exit 1
 SODIUM_CFLAGS="-I$cur_dir/extras/sodium/include"
 
 SODIUM_LIBS=
-if test -n "$ac_cv_path_PKGCONFIG"; then :
+if test -n "$ac_cv_path_PKGCONFIG"
+then :
   SODIUM_LIBS="`$ac_cv_path_PKGCONFIG --libs libsodium.pc`"
 fi
-if test -z "$SODIUM_LIBS"; then :
+if test -z "$SODIUM_LIBS"
+then :
   SODIUM_LIBS="-L$PRIVATELIBDIR -lsodium"
 fi
 
@@ -7789,21 +8351,22 @@ cd $cur_dir
 fi
 
 has_system_cares="no"
-if test "x$with_system_cares" = "xyes"; then :
+if test "x$with_system_cares" = "xyes"
+then :
 
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for CARES" >&5
-$as_echo_n "checking for CARES... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for CARES" >&5
+printf %s "checking for CARES... " >&6; }
 
 if test -n "$CARES_CFLAGS"; then
     pkg_cv_CARES_CFLAGS="$CARES_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares >= 1.6.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares >= 1.6.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libcares >= 1.6.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_CARES_CFLAGS=`$PKG_CONFIG --cflags "libcares >= 1.6.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7817,10 +8380,10 @@ if test -n "$CARES_LIBS"; then
     pkg_cv_CARES_LIBS="$CARES_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares >= 1.6.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcares >= 1.6.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libcares >= 1.6.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_CARES_LIBS=`$PKG_CONFIG --libs "libcares >= 1.6.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7834,8 +8397,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -7852,26 +8415,28 @@ fi
 
 	has_system_cares=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_cares=no
 else
 	CARES_CFLAGS=$pkg_cv_CARES_CFLAGS
 	CARES_LIBS=$pkg_cv_CARES_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_cares=yes
-if test "x$PRIVATELIBDIR" != "x"; then :
+if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libcares*
 fi
 fi
 fi
 
-if test "$has_system_cares" = "no"; then :
+if test "$has_system_cares" = "no"
+then :
 
-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; }
+cares_version="1.19.0"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting c-ares resolver library" >&5
+printf "%s\n" "extracting c-ares resolver library" >&6; }
 cur_dir=`pwd`
 cd extras
 rm -rf c-ares-$cares_version c-ares
@@ -7883,19 +8448,20 @@ else
 	cp c-ares.tar.gz.bak c-ares.tar.gz
 	tar xf c-ares.tar
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring c-ares library" >&5
-$as_echo "configuring c-ares library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: configuring c-ares library" >&5
+printf "%s\n" "configuring c-ares library" >&6; }
 cd c-ares-$cares_version
 save_cflags="$CFLAGS"
 CFLAGS="$orig_cflags"
 export CFLAGS
 ./configure --prefix=$cur_dir/extras/c-ares --libdir=$PRIVATELIBDIR --enable-shared --disable-tests || exit 1
 CFLAGS="$save_cflags"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling c-ares resolver library" >&5
-$as_echo "compiling c-ares resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling c-ares resolver library" >&5
+printf "%s\n" "compiling c-ares resolver library" >&6; }
 $ac_cv_prog_MAKER || exit 1
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing c-ares resolver library" >&5
-$as_echo "installing c-ares resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing c-ares resolver library" >&5
+printf "%s\n" "installing c-ares resolver library" >&6; }
+rm -f "$PRIVATELIBDIR/"libcares*
 $ac_cv_prog_MAKER install || exit 1
 CARES_CFLAGS="-I$cur_dir/extras/c-ares/include"
 
@@ -7931,21 +8497,22 @@ cd $cur_dir
 fi
 
 has_system_jansson="no"
-if test "x$with_system_jansson" = "xyes"; then :
+if test "x$with_system_jansson" = "xyes"
+then :
 
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for JANSSON" >&5
-$as_echo_n "checking for JANSSON... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for JANSSON" >&5
+printf %s "checking for JANSSON... " >&6; }
 
 if test -n "$JANSSON_CFLAGS"; then
     pkg_cv_JANSSON_CFLAGS="$JANSSON_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson >= 2.0.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson >= 2.0.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "jansson >= 2.0.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_JANSSON_CFLAGS=`$PKG_CONFIG --cflags "jansson >= 2.0.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7959,10 +8526,10 @@ if test -n "$JANSSON_LIBS"; then
     pkg_cv_JANSSON_LIBS="$JANSSON_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson >= 2.0.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson >= 2.0.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "jansson >= 2.0.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_JANSSON_LIBS=`$PKG_CONFIG --libs "jansson >= 2.0.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -7976,8 +8543,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -7994,26 +8561,28 @@ fi
 
 	has_system_jansson=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_jansson=no
 else
 	JANSSON_CFLAGS=$pkg_cv_JANSSON_CFLAGS
 	JANSSON_LIBS=$pkg_cv_JANSSON_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_jansson=yes
-if test "x$PRIVATELIBDIR" != "x"; then :
+if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libjansson*
 fi
 fi
 fi
 
-if test "$has_system_jansson" = "no"; then :
+if test "$has_system_jansson" = "no"
+then :
 
-jansson_version="2.13.1"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting jansson library" >&5
-$as_echo "extracting jansson library" >&6; }
+jansson_version="2.14"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting jansson library" >&5
+printf "%s\n" "extracting jansson library" >&6; }
 cur_dir=`pwd`
 cd extras
 rm -rf jansson-$jansson_version jansson
@@ -8025,27 +8594,30 @@ else
 	cp jansson.tar.gz.bak jansson.tar.gz
 	tar xf jansson.tar
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling jansson library" >&5
-$as_echo "compiling jansson library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling jansson library" >&5
+printf "%s\n" "compiling jansson library" >&6; }
 cd jansson-$jansson_version
 save_cflags="$CFLAGS"
 CFLAGS="$orig_cflags"
 export CFLAGS
-./configure --prefix=$cur_dir/extras/jansson --libdir=$PRIVATELIBDIR --enable-shared --disable-static --enable-opt || exit 1
+./configure --prefix=$cur_dir/extras/jansson --libdir=$PRIVATELIBDIR --enable-shared --disable-static || exit 1
 CFLAGS="$save_cflags"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling jansson resolver library" >&5
-$as_echo "compiling jansson resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling jansson resolver library" >&5
+printf "%s\n" "compiling jansson resolver library" >&6; }
 $ac_cv_prog_MAKER || exit 1
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing jansson resolver library" >&5
-$as_echo "installing jansson resolver library" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing jansson resolver library" >&5
+printf "%s\n" "installing jansson resolver library" >&6; }
+rm -f "$PRIVATELIBDIR/"libjansson*
 $ac_cv_prog_MAKER install || exit 1
 JANSSON_CFLAGS="-I$cur_dir/extras/jansson/include"
 
 JANSSON_LIBS=
-if test -n "$ac_cv_path_PKGCONFIG"; then :
+if test -n "$ac_cv_path_PKGCONFIG"
+then :
   JANSSON_LIBS="`$ac_cv_path_PKGCONFIG --libs jansson.pc`"
 fi
-if test -z "$JANSSON_LIBS"; then :
+if test -z "$JANSSON_LIBS"
+then :
   JANSSON_LIBS="-L$PRIVATELIBDIR -ljansson"
 fi
 
@@ -8054,26 +8626,30 @@ cd $cur_dir
 fi
 
 
-# Make sure we can run config.sub.
-$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
-  as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
-$as_echo_n "checking build system type... " >&6; }
-if ${ac_cv_build+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+
+
+  # Make sure we can run config.sub.
+$SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 ||
+  as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+printf %s "checking build system type... " >&6; }
+if test ${ac_cv_build+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   ac_build_alias=$build_alias
 test "x$ac_build_alias" = x &&
-  ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+  ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"`
 test "x$ac_build_alias" = x &&
   as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
-ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
-  as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
+ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` ||
+  as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
-$as_echo "$ac_cv_build" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+printf "%s\n" "$ac_cv_build" >&6; }
 case $ac_cv_build in
 *-*-*) ;;
 *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
@@ -8092,21 +8668,22 @@ IFS=$ac_save_IFS
 case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
 
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
-$as_echo_n "checking host system type... " >&6; }
-if ${ac_cv_host+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+printf %s "checking host system type... " >&6; }
+if test ${ac_cv_host+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test "x$host_alias" = x; then
   ac_cv_host=$ac_cv_build
 else
-  ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
-    as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
+  ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` ||
+    as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5
 fi
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
-$as_echo "$ac_cv_host" >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+printf "%s\n" "$ac_cv_host" >&6; }
 case $ac_cv_host in
 *-*-*) ;;
 *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
@@ -8147,33 +8724,31 @@ if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
         CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
         save_LIBS="$LIBS"
         LIBS="$PTHREAD_LIBS $LIBS"
-        { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS" >&5
-$as_echo_n "checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS... " >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS" >&5
+printf %s "checking for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS... " >&6; }
         cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
 /* Override any GCC internal prototype to avoid an error.
    Use char because int might match the return type of a GCC
    builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
 char pthread_join ();
 int
-main ()
+main (void)
 {
 return pthread_join ();
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_pthread_ok=yes
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5
-$as_echo "$ax_pthread_ok" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5
+printf "%s\n" "$ax_pthread_ok" >&6; }
         if test x"$ax_pthread_ok" = xno; then
                 PTHREAD_LIBS=""
                 PTHREAD_CFLAGS=""
@@ -8237,8 +8812,8 @@ esac
 # -Werror. We throw in some extra Clang-specific options to ensure that
 # this doesn't happen for GCC, which also accepts -Werror.
 
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler needs -Werror to reject unknown flags" >&5
-$as_echo_n "checking if compiler needs -Werror to reject unknown flags... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler needs -Werror to reject unknown flags" >&5
+printf %s "checking if compiler needs -Werror to reject unknown flags... " >&6; }
 save_CFLAGS="$CFLAGS"
 ax_pthread_extra_flags="-Werror"
 CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument"
@@ -8246,22 +8821,23 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 int foo(void);
 int
-main ()
+main (void)
 {
 foo()
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-else
+if ac_fn_c_try_compile "$LINENO"
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
   ax_pthread_extra_flags=
-                   { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+                   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 CFLAGS="$save_CFLAGS"
 
 if test x"$ax_pthread_ok" = xno; then
@@ -8269,24 +8845,25 @@ for flag in $ax_pthread_flags; do
 
         case $flag in
                 none)
-                { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5
-$as_echo_n "checking whether pthreads work without any flags... " >&6; }
+                { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5
+printf %s "checking whether pthreads work without any flags... " >&6; }
                 ;;
 
                 -*)
-                { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $flag" >&5
-$as_echo_n "checking whether pthreads work with $flag... " >&6; }
+                { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $flag" >&5
+printf %s "checking whether pthreads work with $flag... " >&6; }
                 PTHREAD_CFLAGS="$flag"
                 ;;
 
                 pthread-config)
                 # Extract the first word of "pthread-config", so it can be a program name with args.
 set dummy pthread-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ax_pthread_config+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_ax_pthread_config+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$ax_pthread_config"; then
   ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test.
 else
@@ -8294,11 +8871,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_ax_pthread_config="yes"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -8310,11 +8891,11 @@ fi
 fi
 ax_pthread_config=$ac_cv_prog_ax_pthread_config
 if test -n "$ax_pthread_config"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5
-$as_echo "$ax_pthread_config" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5
+printf "%s\n" "$ax_pthread_config" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -8324,8 +8905,8 @@ fi
                 ;;
 
                 *)
-                { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$flag" >&5
-$as_echo_n "checking for the pthreads library -l$flag... " >&6; }
+                { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$flag" >&5
+printf %s "checking for the pthreads library -l$flag... " >&6; }
                 PTHREAD_LIBS="-l$flag"
                 ;;
         esac
@@ -8350,7 +8931,7 @@ $as_echo_n "checking for the pthreads library -l$flag... " >&6; }
                         static void routine(void *a) { a = 0; }
                         static void *start_routine(void *a) { return a; }
 int
-main ()
+main (void)
 {
 pthread_t th; pthread_attr_t attr;
                         pthread_create(&th, 0, start_routine, 0);
@@ -8362,17 +8943,18 @@ pthread_t th; pthread_attr_t attr;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_pthread_ok=yes
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 
         LIBS="$save_LIBS"
         CFLAGS="$save_CFLAGS"
 
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5
-$as_echo "$ax_pthread_ok" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5
+printf "%s\n" "$ax_pthread_ok" >&6; }
         if test "x$ax_pthread_ok" = xyes; then
                 break;
         fi
@@ -8390,39 +8972,38 @@ if test "x$ax_pthread_ok" = xyes; then
         CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
 
         # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
-        { $as_echo "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5
-$as_echo_n "checking for joinable pthread attribute... " >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5
+printf %s "checking for joinable pthread attribute... " >&6; }
         attr_name=unknown
         for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
             cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <pthread.h>
 int
-main ()
+main (void)
 {
 int attr = $attr; return attr /* ; */
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   attr_name=$attr; break
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
         done
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: $attr_name" >&5
-$as_echo "$attr_name" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $attr_name" >&5
+printf "%s\n" "$attr_name" >&6; }
         if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
 
-cat >>confdefs.h <<_ACEOF
-#define PTHREAD_CREATE_JOINABLE $attr_name
-_ACEOF
+printf "%s\n" "#define PTHREAD_CREATE_JOINABLE $attr_name" >>confdefs.h
 
         fi
 
-        { $as_echo "$as_me:${as_lineno-$LINENO}: checking if more special flags are required for pthreads" >&5
-$as_echo_n "checking if more special flags are required for pthreads... " >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if more special flags are required for pthreads" >&5
+printf %s "checking if more special flags are required for pthreads... " >&6; }
         flag=no
         case ${host_os} in
             aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";;
@@ -8436,43 +9017,46 @@ $as_echo_n "checking if more special flags are required for pthreads... " >&6; }
             fi
             ;;
         esac
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: $flag" >&5
-$as_echo "$flag" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $flag" >&5
+printf "%s\n" "$flag" >&6; }
         if test "x$flag" != xno; then
             PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
         fi
 
-        { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5
-$as_echo_n "checking for PTHREAD_PRIO_INHERIT... " >&6; }
-if ${ax_cv_PTHREAD_PRIO_INHERIT+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5
+printf %s "checking for PTHREAD_PRIO_INHERIT... " >&6; }
+if test ${ax_cv_PTHREAD_PRIO_INHERIT+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
 
                 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 #include <pthread.h>
 int
-main ()
+main (void)
 {
 int i = PTHREAD_PRIO_INHERIT;
   ;
   return 0;
 }
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
+if ac_fn_c_try_link "$LINENO"
+then :
   ax_cv_PTHREAD_PRIO_INHERIT=yes
-else
+else $as_nop
   ax_cv_PTHREAD_PRIO_INHERIT=no
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5
-$as_echo "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; }
-        if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"; then :
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5
+printf "%s\n" "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; }
+        if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"
+then :
 
-$as_echo "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h
+printf "%s\n" "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h
 
 fi
 
@@ -8488,7 +9072,8 @@ fi
     #handle absolute path differently from PATH based program lookup
                    case "x$CC" in #(
   x/*) :
-    if as_fn_executable_p ${CC}_r; then :
+    if as_fn_executable_p ${CC}_r
+then :
   PTHREAD_CC="${CC}_r"
 fi ;; #(
   *) :
@@ -8496,11 +9081,12 @@ fi ;; #(
 do
   # Extract the first word of "$ac_prog", so it can be a program name with args.
 set dummy $ac_prog; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_PTHREAD_CC+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+printf %s "checking for $ac_word... " >&6; }
+if test ${ac_cv_prog_PTHREAD_CC+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
   if test -n "$PTHREAD_CC"; then
   ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test.
 else
@@ -8508,11 +9094,15 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
     for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+  if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
     ac_cv_prog_PTHREAD_CC="$ac_prog"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
@@ -8523,11 +9113,11 @@ fi
 fi
 PTHREAD_CC=$ac_cv_prog_PTHREAD_CC
 if test -n "$PTHREAD_CC"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5
-$as_echo "$PTHREAD_CC" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5
+printf "%s\n" "$PTHREAD_CC" >&6; }
 else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 fi
 
 
@@ -8553,7 +9143,7 @@ test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
 # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
 if test x"$ax_pthread_ok" = xyes; then
 
-$as_echo "#define HAVE_PTHREAD 1" >>confdefs.h
+printf "%s\n" "#define HAVE_PTHREAD 1" >>confdefs.h
 
         :
 else
@@ -8570,30 +9160,34 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 	# Check whether --enable-libcurl was given.
-if test "${enable_libcurl+set}" = set; then :
+if test ${enable_libcurl+y}
+then :
   enableval=$enable_libcurl; enable_curl=$enableval
-else
+else $as_nop
   enable_curl=no
 fi
 
 
-	if test "x$enable_curl" != "xno"; then :
+	if test "x$enable_curl" != "xno"
+then :
 
 												CURLCONFIG="curl-config"
-		if test "x$enable_curl" != "xyes"; then :
+		if test "x$enable_curl" != "xyes"
+then :
   CURLCONFIG="$enable_curl/bin/curl-config"
 fi
 
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $CURLCONFIG" >&5
-$as_echo_n "checking $CURLCONFIG... " >&6; }
-		if $CURLCONFIG --version 2>/dev/null >/dev/null; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-				{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking $CURLCONFIG" >&5
+printf %s "checking $CURLCONFIG... " >&6; }
+		if $CURLCONFIG --version 2>/dev/null >/dev/null
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+				{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "Could not find curl-config, try editing --enable-libcurl
 See \`config.log' for more details" "$LINENO" 5; }
 fi
@@ -8601,19 +9195,22 @@ fi
 		CURLCFLAG="`$CURLCONFIG --cflags`"
 		CURLLIBS="`$CURLCONFIG --libs`"
 
-				if $CURLCONFIG --libs | grep -q -e ares; then :
+				if $CURLCONFIG --libs | grep -q -e ares
+then :
   CURLUSESCARES="1"
-else
+else $as_nop
   CURLUSESCARES="0"
 fi
 
-				if test -z "${CURLLIBS}"; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: CURLLIBS is empty, that probably means that I could not find $enable_curl/bin/curl-config" >&5
-$as_echo "$as_me: WARNING: CURLLIBS is empty, that probably means that I could not find $enable_curl/bin/curl-config" >&2;}
+				if test -z "${CURLLIBS}"
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: CURLLIBS is empty, that probably means that I could not find $enable_curl/bin/curl-config" >&5
+printf "%s\n" "$as_me: WARNING: CURLLIBS is empty, that probably means that I could not find $enable_curl/bin/curl-config" >&2;}
 fi
 
 
-		if test "x$has_system_cares" = "xno" && test "x$BUILDDIR/extras/curl" != "x$enable_curl" && test "$CURLUSESCARES" != "0" ; then :
+		if test "x$has_system_cares" = "xno" && test "x$BUILDDIR/extras/curl" != "x$enable_curl" && test "$CURLUSESCARES" != "0"
+then :
 
 			as_fn_error $? "
 
@@ -8624,7 +9221,7 @@ fi
   with the system-installed libcURL, this is a bad idea which may result in error
   messages looking like:
 
-  	\`\`[error] unrealircd.conf:9: include: error downloading '(http://example.net/ex.conf)': Could not resolve host: example.net (Successful completion)''
+  	error downloading ... Could not resolve host: example.net (Successful completion)
 
   Or UnrealIRCd might even crash.
 
@@ -8636,11 +9233,11 @@ fi
 						IRCDLIBS="$IRCDLIBS $CURLLIBS"
 		CFLAGS="$CFLAGS $CURLCFLAG"
 
-$as_echo "#define USE_LIBCURL /**/" >>confdefs.h
+printf "%s\n" "#define USE_LIBCURL /**/" >>confdefs.h
 
 
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking curl_easy_init() in $CURLLIBS" >&5
-$as_echo_n "checking curl_easy_init() in $CURLLIBS... " >&6; }
+		{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking curl_easy_init() in $CURLLIBS" >&5
+printf %s "checking curl_easy_init() in $CURLLIBS... " >&6; }
 		LIBS_SAVEDA="$LIBS"
 		CFLAGS_SAVEDA="$CFLAGS"
 
@@ -8651,7 +9248,7 @@ $as_echo_n "checking curl_easy_init() in $CURLLIBS... " >&6; }
 
 			#include <curl/curl.h>
 int
-main ()
+main (void)
 {
 CURL *curl = curl_easy_init();
   ;
@@ -8659,26 +9256,27 @@ CURL *curl = curl_easy_init();
 }
 
 _ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-			{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+if ac_fn_c_try_link "$LINENO"
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+			{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "You asked for libcURL (remote includes) support, but it can't be found at $enable_curl
 See \`config.log' for more details" "$LINENO" 5; }
 
 fi
-rm -f core conftest.err conftest.$ac_objext \
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
     conftest$ac_exeext conftest.$ac_ext
 		LIBS="$LIBS_SAVEDA"
 		CFLAGS="$CFLAGS_SAVEDA"
 
 				URL="url_curl.o"
 
-else
+else $as_nop
 
 				URL="url_unreal.o"
 
@@ -8687,29 +9285,31 @@ fi
 
 
 	# Check whether --enable-geoip_classic was given.
-if test "${enable_geoip_classic+set}" = set; then :
+if test ${enable_geoip_classic+y}
+then :
   enableval=$enable_geoip_classic; enable_geoip_classic=$enableval
-else
+else $as_nop
   enable_geoip_classic=no
 fi
 
 
-	if test "x$enable_geoip_classic" = "xyes"; then :
+	if test "x$enable_geoip_classic" = "xyes"
+then :
 
 				has_system_geoip_classic="no"
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for GEOIP_CLASSIC" >&5
-$as_echo_n "checking for GEOIP_CLASSIC... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GEOIP_CLASSIC" >&5
+printf %s "checking for GEOIP_CLASSIC... " >&6; }
 
 if test -n "$GEOIP_CLASSIC_CFLAGS"; then
     pkg_cv_GEOIP_CLASSIC_CFLAGS="$GEOIP_CLASSIC_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"geoip >= 1.6.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"geoip >= 1.6.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "geoip >= 1.6.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_GEOIP_CLASSIC_CFLAGS=`$PKG_CONFIG --cflags "geoip >= 1.6.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -8723,10 +9323,10 @@ if test -n "$GEOIP_CLASSIC_LIBS"; then
     pkg_cv_GEOIP_CLASSIC_LIBS="$GEOIP_CLASSIC_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"geoip >= 1.6.0\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"geoip >= 1.6.0\""; } >&5
   ($PKG_CONFIG --exists --print-errors "geoip >= 1.6.0") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_GEOIP_CLASSIC_LIBS=`$PKG_CONFIG --libs "geoip >= 1.6.0" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -8740,8 +9340,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -8758,25 +9358,27 @@ fi
 
 	has_system_geoip_classic=no
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 	has_system_geoip_classic=no
 else
 	GEOIP_CLASSIC_CFLAGS=$pkg_cv_GEOIP_CLASSIC_CFLAGS
 	GEOIP_CLASSIC_LIBS=$pkg_cv_GEOIP_CLASSIC_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_geoip_classic=yes
-		                   if test "x$PRIVATELIBDIR" != "x"; then :
+		                   if test "x$PRIVATELIBDIR" != "x"
+then :
   rm -f "$PRIVATELIBDIR/"libGeoIP.*
 fi
 fi
 
-				if test "$has_system_geoip_classic" = "no"; then :
+				if test "$has_system_geoip_classic" = "no"
+then :
 
 						geoip_classic_version="1.6.12"
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: extracting GeoIP Classic library" >&5
-$as_echo "extracting GeoIP Classic library" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: extracting GeoIP Classic library" >&5
+printf "%s\n" "extracting GeoIP Classic library" >&6; }
 			cur_dir=`pwd`
 			cd extras
 									rm -rf GeoIP-$geoip_classic_version geoip-classic
@@ -8788,25 +9390,28 @@ $as_echo "extracting GeoIP Classic library" >&6; }
 				cp geoip-classic.tar.gz.bak geoip-classic.tar.gz
 				tar xf geoip-classic.tar
 			fi
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring GeoIP Classic library" >&5
-$as_echo "configuring GeoIP Classic library" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: configuring GeoIP Classic library" >&5
+printf "%s\n" "configuring GeoIP Classic library" >&6; }
 			cd GeoIP-$geoip_classic_version
 			save_cflags="$CFLAGS"
 			CFLAGS="$orig_cflags"
 			export CFLAGS
 			./configure --prefix=$cur_dir/extras/geoip-classic --libdir=$PRIVATELIBDIR --enable-shared --disable-static || exit 1
 			CFLAGS="$save_cflags"
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: compiling GeoIP Classic library" >&5
-$as_echo "compiling GeoIP Classic library" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: compiling GeoIP Classic library" >&5
+printf "%s\n" "compiling GeoIP Classic library" >&6; }
 			$ac_cv_prog_MAKER || exit 1
-			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: installing GeoIP Classic library" >&5
-$as_echo "installing GeoIP Classic library" >&6; }
+			{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: installing GeoIP Classic library" >&5
+printf "%s\n" "installing GeoIP Classic library" >&6; }
+			rm -f "$PRIVATELIBDIR/"libGeoIP.so*
 			$ac_cv_prog_MAKER install || exit 1
-						if test -n "$ac_cv_path_PKGCONFIG"; then :
+						if test -n "$ac_cv_path_PKGCONFIG"
+then :
   GEOIP_CLASSIC_LIBS="`$ac_cv_path_PKGCONFIG --libs geoip.pc`"
 			        GEOIP_CLASSIC_CFLAGS="`$ac_cv_path_PKGCONFIG --cflags geoip.pc`"
 fi
-						if test -z "$GEOIP_CLASSIC_LIBS"; then :
+						if test -z "$GEOIP_CLASSIC_LIBS"
+then :
   GEOIP_CLASSIC_LIBS="-L$PRIVATELIBDIR -lGeoIP"
 			        GEOIP_CLASSIC_CFLAGS="-I$cur_dir/extras/geoip-classic/include"
 fi
@@ -8824,29 +9429,31 @@ fi
 
 
 	# Check whether --enable-libmaxminddb was given.
-if test "${enable_libmaxminddb+set}" = set; then :
+if test ${enable_libmaxminddb+y}
+then :
   enableval=$enable_libmaxminddb; enable_libmaxminddb=$enableval
-else
+else $as_nop
   enable_libmaxminddb=no
 fi
 
 
-	if test "x$enable_libmaxminddb" = "xyes"; then :
+	if test "x$enable_libmaxminddb" = "xyes"
+then :
 
 				has_system_libmaxminddb="no"
 
 pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBMAXMINDDB" >&5
-$as_echo_n "checking for LIBMAXMINDDB... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for LIBMAXMINDDB" >&5
+printf %s "checking for LIBMAXMINDDB... " >&6; }
 
 if test -n "$LIBMAXMINDDB_CFLAGS"; then
     pkg_cv_LIBMAXMINDDB_CFLAGS="$LIBMAXMINDDB_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.4.3\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.4.3\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.4.3") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_LIBMAXMINDDB_CFLAGS=`$PKG_CONFIG --cflags "libmaxminddb >= 1.4.3" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -8860,10 +9467,10 @@ if test -n "$LIBMAXMINDDB_LIBS"; then
     pkg_cv_LIBMAXMINDDB_LIBS="$LIBMAXMINDDB_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.4.3\""; } >&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libmaxminddb >= 1.4.3\""; } >&5
   ($PKG_CONFIG --exists --print-errors "libmaxminddb >= 1.4.3") 2>&5
   ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
   pkg_cv_LIBMAXMINDDB_LIBS=`$PKG_CONFIG --libs "libmaxminddb >= 1.4.3" 2>/dev/null`
 		      test "x$?" != "x0" && pkg_failed=yes
@@ -8877,8 +9484,8 @@ fi
 
 
 if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+   	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
 
 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
         _pkg_short_errors_supported=yes
@@ -8904,10 +9511,10 @@ Alternatively, you may set the environment variables LIBMAXMINDDB_CFLAGS
 and LIBMAXMINDDB_LIBS to avoid the need to call pkg-config.
 See the pkg-config man page for more details." "$LINENO" 5
 elif test $pkg_failed = untried; then
-     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
-$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+     	{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+	{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;}
 as_fn_error $? "The pkg-config script could not be found or is too old.  Make sure it
 is in your PATH or set the PKG_CONFIG environment variable to the full
 path to pkg-config.
@@ -8921,11 +9528,12 @@ See \`config.log' for more details" "$LINENO" 5; }
 else
 	LIBMAXMINDDB_CFLAGS=$pkg_cv_LIBMAXMINDDB_CFLAGS
 	LIBMAXMINDDB_LIBS=$pkg_cv_LIBMAXMINDDB_LIBS
-        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
 	has_system_libmaxminddb=yes
 fi
-		if test "x$has_system_libmaxminddb" = "xyes"; then :
+		if test "x$has_system_libmaxminddb" = "xyes"
+then :
 
 
 
@@ -8954,7 +9562,7 @@ fi
 
 
 
-ac_config_files="$ac_config_files Makefile src/Makefile src/modules/Makefile src/modules/chanmodes/Makefile src/modules/usermodes/Makefile src/modules/extbans/Makefile src/modules/third/Makefile extras/unrealircd-upgrade-script unrealircd"
+ac_config_files="$ac_config_files Makefile src/Makefile src/modules/Makefile src/modules/chanmodes/Makefile src/modules/usermodes/Makefile src/modules/extbans/Makefile src/modules/rpc/Makefile src/modules/third/Makefile extras/unrealircd-upgrade-script unrealircd"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -8983,8 +9591,8 @@ _ACEOF
     case $ac_val in #(
     *${as_nl}*)
       case $ac_var in #(
-      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
-$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
       esac
       case $ac_var in #(
       _ | IFS | as_nl) ;; #(
@@ -9014,15 +9622,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
      /^ac_cv_env_/b end
      t clear
      :clear
-     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/
      t end
      s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
      :end' >>confcache
 if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
   if test -w "$cache_file"; then
     if test "x$cache_file" != "x/dev/null"; then
-      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
-$as_echo "$as_me: updating cache $cache_file" >&6;}
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+printf "%s\n" "$as_me: updating cache $cache_file" >&6;}
       if test ! -f "$cache_file" || test -h "$cache_file"; then
 	cat confcache >"$cache_file"
       else
@@ -9036,8 +9644,8 @@ $as_echo "$as_me: updating cache $cache_file" >&6;}
       fi
     fi
   else
-    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
-$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;}
   fi
 fi
 rm -f confcache
@@ -9054,7 +9662,7 @@ U=
 for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
   # 1. Remove the extension, and $U if already installed.
   ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
-  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"`
   # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
   #    will be set to the directory where LIBOBJS objects are built.
   as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
@@ -9070,8 +9678,8 @@ LTLIBOBJS=$ac_ltlibobjs
 ac_write_fail=0
 ac_clean_files_save=$ac_clean_files
 ac_clean_files="$ac_clean_files $CONFIG_STATUS"
-{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
-$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;}
 as_write_fail=0
 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
 #! $SHELL
@@ -9094,14 +9702,16 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
 
 # Be more Bourne compatible
 DUALCASE=1; export DUALCASE # for MKS sh
-if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+as_nop=:
+if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1
+then :
   emulate sh
   NULLCMD=:
   # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
   # is contrary to our usage.  Disable this feature.
   alias -g '${1+"$@"}'='"$@"'
   setopt NO_GLOB_SUBST
-else
+else $as_nop
   case `(set -o) 2>/dev/null` in #(
   *posix*) :
     set -o posix ;; #(
@@ -9111,46 +9721,46 @@ esac
 fi
 
 
+
+# Reset variables that may have inherited troublesome values from
+# the environment.
+
+# IFS needs to be set, to space, tab, and newline, in precisely that order.
+# (If _AS_PATH_WALK were called with IFS unset, it would have the
+# side effect of setting IFS to empty, thus disabling word splitting.)
+# Quoting is to prevent editors from complaining about space-tab.
 as_nl='
 '
 export as_nl
-# Printing a long string crashes Solaris 7 /usr/bin/printf.
-as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
-as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
-as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
-# Prefer a ksh shell builtin over an external printf program on Solaris,
-# but without wasting forks for bash or zsh.
-if test -z "$BASH_VERSION$ZSH_VERSION" \
-    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
-  as_echo='print -r --'
-  as_echo_n='print -rn --'
-elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
-  as_echo='printf %s\n'
-  as_echo_n='printf %s'
-else
-  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
-    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
-    as_echo_n='/usr/ucb/echo -n'
-  else
-    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
-    as_echo_n_body='eval
-      arg=$1;
-      case $arg in #(
-      *"$as_nl"*)
-	expr "X$arg" : "X\\(.*\\)$as_nl";
-	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
-      esac;
-      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
-    '
-    export as_echo_n_body
-    as_echo_n='sh -c $as_echo_n_body as_echo'
-  fi
-  export as_echo_body
-  as_echo='sh -c $as_echo_body as_echo'
-fi
+IFS=" ""	$as_nl"
+
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# Ensure predictable behavior from utilities with locale-dependent output.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# We cannot yet rely on "unset" to work, but we need these variables
+# to be unset--not just set to an empty or harmless value--now, to
+# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh).  This construct
+# also avoids known problems related to "unset" and subshell syntax
+# in other old shells (e.g. bash 2.01 and pdksh 5.2.14).
+for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH
+do eval test \${$as_var+y} \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+
+# Ensure that fds 0, 1, and 2 are open.
+if (exec 3>&0) 2>/dev/null; then :; else exec 0</dev/null; fi
+if (exec 3>&1) 2>/dev/null; then :; else exec 1>/dev/null; fi
+if (exec 3>&2)            ; then :; else exec 2>/dev/null; fi
 
 # The user is always right.
-if test "${PATH_SEPARATOR+set}" != set; then
+if ${PATH_SEPARATOR+false} :; then
   PATH_SEPARATOR=:
   (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
     (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
@@ -9159,13 +9769,6 @@ if test "${PATH_SEPARATOR+set}" != set; then
 fi
 
 
-# IFS
-# We need space, tab and new line, in precisely that order.  Quoting is
-# there to prevent editors from complaining about space-tab.
-# (If _AS_PATH_WALK were called with IFS unset, it would disable word
-# splitting by setting IFS to empty value.)
-IFS=" ""	$as_nl"
-
 # Find who we are.  Look in the path if we contain no directory separator.
 as_myself=
 case $0 in #((
@@ -9174,8 +9777,12 @@ case $0 in #((
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  case $as_dir in #(((
+    '') as_dir=./ ;;
+    */) ;;
+    *) as_dir=$as_dir/ ;;
+  esac
+    test -r "$as_dir$0" && as_myself=$as_dir$0 && break
   done
 IFS=$as_save_IFS
 
@@ -9187,30 +9794,10 @@ if test "x$as_myself" = x; then
   as_myself=$0
 fi
 if test ! -f "$as_myself"; then
-  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
   exit 1
 fi
 
-# Unset variables that we do not need and which cause bugs (e.g. in
-# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
-# suppresses any "Segmentation fault" message there.  '((' could
-# trigger a bug in pdksh 5.2.14.
-for as_var in BASH_ENV ENV MAIL MAILPATH
-do eval test x\${$as_var+set} = xset \
-  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
-done
-PS1='$ '
-PS2='> '
-PS4='+ '
-
-# NLS nuisances.
-LC_ALL=C
-export LC_ALL
-LANGUAGE=C
-export LANGUAGE
-
-# CDPATH.
-(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
 
 
 # as_fn_error STATUS ERROR [LINENO LOG_FD]
@@ -9223,13 +9810,14 @@ as_fn_error ()
   as_status=$1; test $as_status -eq 0 && as_status=1
   if test "$4"; then
     as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+    printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
   fi
-  $as_echo "$as_me: error: $2" >&2
+  printf "%s\n" "$as_me: error: $2" >&2
   as_fn_exit $as_status
 } # as_fn_error
 
 
+
 # as_fn_set_status STATUS
 # -----------------------
 # Set $? to STATUS, without forking.
@@ -9256,18 +9844,20 @@ as_fn_unset ()
   { eval $1=; unset $1;}
 }
 as_unset=as_fn_unset
+
 # as_fn_append VAR VALUE
 # ----------------------
 # Append the text in VALUE to the end of the definition contained in VAR. Take
 # advantage of any shell optimizations that allow amortized linear growth over
 # repeated appends, instead of the typical quadratic growth present in naive
 # implementations.
-if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null
+then :
   eval 'as_fn_append ()
   {
     eval $1+=\$2
   }'
-else
+else $as_nop
   as_fn_append ()
   {
     eval $1=\$$1\$2
@@ -9279,12 +9869,13 @@ fi # as_fn_append
 # Perform arithmetic evaluation on the ARGs, and store the result in the
 # global $as_val. Take advantage of shells that can avoid forks. The arguments
 # must be portable across $(()) and expr.
-if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null
+then :
   eval 'as_fn_arith ()
   {
     as_val=$(( $* ))
   }'
-else
+else $as_nop
   as_fn_arith ()
   {
     as_val=`expr "$@" || test $? -eq 1`
@@ -9315,7 +9906,7 @@ as_me=`$as_basename -- "$0" ||
 $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
 	 X"$0" : 'X\(//\)$' \| \
 	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X/"$0" |
+printf "%s\n" X/"$0" |
     sed '/^.*\/\([^/][^/]*\)\/*$/{
 	    s//\1/
 	    q
@@ -9337,6 +9928,10 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS
 as_cr_digits='0123456789'
 as_cr_alnum=$as_cr_Letters$as_cr_digits
 
+
+# Determine whether it's possible to make 'echo' print without a newline.
+# These variables are no longer used directly by Autoconf, but are AC_SUBSTed
+# for compatibility with existing Makefiles.
 ECHO_C= ECHO_N= ECHO_T=
 case `echo -n x` in #(((((
 -n*)
@@ -9350,6 +9945,12 @@ case `echo -n x` in #(((((
   ECHO_N='-n';;
 esac
 
+# For backward compatibility with old third-party macros, we provide
+# the shell variables $as_echo and $as_echo_n.  New code should use
+# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively.
+as_echo='printf %s\n'
+as_echo_n='printf %s'
+
 rm -f conf$$ conf$$.exe conf$$.file
 if test -d conf$$.dir; then
   rm -f conf$$.dir/conf$$.file
@@ -9391,7 +9992,7 @@ as_fn_mkdir_p ()
     as_dirs=
     while :; do
       case $as_dir in #(
-      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
       *) as_qdir=$as_dir;;
       esac
       as_dirs="'$as_qdir' $as_dirs"
@@ -9400,7 +10001,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
 	 X"$as_dir" : 'X\(//\)[^/]' \| \
 	 X"$as_dir" : 'X\(//\)$' \| \
 	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X"$as_dir" |
+printf "%s\n" X"$as_dir" |
     sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
 	    s//\1/
 	    q
@@ -9462,8 +10063,8 @@ 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.4.2, which was
-generated by GNU Autoconf 2.69.  Invocation command line was
+This file was extended by unrealircd $as_me 6.1.0, which was
+generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
   CONFIG_HEADERS  = $CONFIG_HEADERS
@@ -9522,14 +10123,16 @@ Report bugs to <https://bugs.unrealircd.org/>.
 unrealircd home page: <https://unrealircd.org/>."
 
 _ACEOF
+ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"`
+ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"`
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
-ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-unrealircd config.status 6.0.4.2
-configured by $0, generated by GNU Autoconf 2.69,
+unrealircd config.status 6.1.0
+configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
-Copyright (C) 2012 Free Software Foundation, Inc.
+Copyright (C) 2021 Free Software Foundation, Inc.
 This config.status script is free software; the Free Software Foundation
 gives unlimited permission to copy, distribute and modify it."
 
@@ -9566,15 +10169,15 @@ do
   -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
     ac_cs_recheck=: ;;
   --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
-    $as_echo "$ac_cs_version"; exit ;;
+    printf "%s\n" "$ac_cs_version"; exit ;;
   --config | --confi | --conf | --con | --co | --c )
-    $as_echo "$ac_cs_config"; exit ;;
+    printf "%s\n" "$ac_cs_config"; exit ;;
   --debug | --debu | --deb | --de | --d | -d )
     debug=: ;;
   --file | --fil | --fi | --f )
     $ac_shift
     case $ac_optarg in
-    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
     '') as_fn_error $? "missing file argument" ;;
     esac
     as_fn_append CONFIG_FILES " '$ac_optarg'"
@@ -9582,7 +10185,7 @@ do
   --header | --heade | --head | --hea )
     $ac_shift
     case $ac_optarg in
-    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
     esac
     as_fn_append CONFIG_HEADERS " '$ac_optarg'"
     ac_need_defaults=false;;
@@ -9591,7 +10194,7 @@ do
     as_fn_error $? "ambiguous option: \`$1'
 Try \`$0 --help' for more information.";;
   --help | --hel | -h )
-    $as_echo "$ac_cs_usage"; exit ;;
+    printf "%s\n" "$ac_cs_usage"; exit ;;
   -q | -quiet | --quiet | --quie | --qui | --qu | --q \
   | -silent | --silent | --silen | --sile | --sil | --si | --s)
     ac_cs_silent=: ;;
@@ -9619,7 +10222,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 if \$ac_cs_recheck; then
   set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
   shift
-  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6
   CONFIG_SHELL='$SHELL'
   export CONFIG_SHELL
   exec "\$@"
@@ -9633,7 +10236,7 @@ exec 5>>config.log
   sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
 ## Running $as_me. ##
 _ASBOX
-  $as_echo "$ac_log"
+  printf "%s\n" "$ac_log"
 } >&5
 
 _ACEOF
@@ -9653,6 +10256,7 @@ do
     "src/modules/chanmodes/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/chanmodes/Makefile" ;;
     "src/modules/usermodes/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/usermodes/Makefile" ;;
     "src/modules/extbans/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/extbans/Makefile" ;;
+    "src/modules/rpc/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/rpc/Makefile" ;;
     "src/modules/third/Makefile") CONFIG_FILES="$CONFIG_FILES src/modules/third/Makefile" ;;
     "extras/unrealircd-upgrade-script") CONFIG_FILES="$CONFIG_FILES extras/unrealircd-upgrade-script" ;;
     "unrealircd") CONFIG_FILES="$CONFIG_FILES unrealircd" ;;
@@ -9667,8 +10271,8 @@ done
 # We use the long form for the default assignment because of an extremely
 # bizarre bug on SunOS 4.1.3.
 if $ac_need_defaults; then
-  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
-  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+  test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files
+  test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers
 fi
 
 # Have a temporary directory for convenience.  Make it in the build tree
@@ -10004,7 +10608,7 @@ do
 	   esac ||
 	   as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
       esac
-      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
       as_fn_append ac_file_inputs " '$ac_f'"
     done
 
@@ -10012,17 +10616,17 @@ do
     # use $as_me), people would be surprised to read:
     #    /* config.h.  Generated by config.status.  */
     configure_input='Generated from '`
-	  $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+	  printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
 	`' by configure.'
     if test x"$ac_file" != x-; then
       configure_input="$ac_file.  $configure_input"
-      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
-$as_echo "$as_me: creating $ac_file" >&6;}
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+printf "%s\n" "$as_me: creating $ac_file" >&6;}
     fi
     # Neutralize special characters interpreted by sed in replacement strings.
     case $configure_input in #(
     *\&* | *\|* | *\\* )
-       ac_sed_conf_input=`$as_echo "$configure_input" |
+       ac_sed_conf_input=`printf "%s\n" "$configure_input" |
        sed 's/[\\\\&|]/\\\\&/g'`;; #(
     *) ac_sed_conf_input=$configure_input;;
     esac
@@ -10039,7 +10643,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
 	 X"$ac_file" : 'X\(//\)[^/]' \| \
 	 X"$ac_file" : 'X\(//\)$' \| \
 	 X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
-$as_echo X"$ac_file" |
+printf "%s\n" X"$ac_file" |
     sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
 	    s//\1/
 	    q
@@ -10063,9 +10667,9 @@ $as_echo X"$ac_file" |
 case "$ac_dir" in
 .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
 *)
-  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'`
   # A ".." for each directory in $ac_dir_suffix.
-  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
   case $ac_top_builddir_sub in
   "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
   *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
@@ -10118,8 +10722,8 @@ ac_sed_dataroot='
 case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
 *datarootdir*) ac_datarootdir_seen=yes;;
 *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
-  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
-$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
   ac_datarootdir_hack='
@@ -10161,9 +10765,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
   { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
   { ac_out=`sed -n '/^[	 ]*datarootdir[	 ]*:*=/p' \
       "$ac_tmp/out"`; test -z "$ac_out"; } &&
-  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&5
-$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
 which seems to be undefined.  Please make sure it is defined" >&2;}
 
   rm -f "$ac_tmp/stdin"
@@ -10179,20 +10783,20 @@ which seems to be undefined.  Please make sure it is defined" >&2;}
   #
   if test x"$ac_file" != x-; then
     {
-      $as_echo "/* $configure_input  */" \
+      printf "%s\n" "/* $configure_input  */" >&1 \
       && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
     } >"$ac_tmp/config.h" \
       || as_fn_error $? "could not create $ac_file" "$LINENO" 5
     if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
-      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
-$as_echo "$as_me: $ac_file is unchanged" >&6;}
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+printf "%s\n" "$as_me: $ac_file is unchanged" >&6;}
     else
       rm -f "$ac_file"
       mv "$ac_tmp/config.h" "$ac_file" \
 	|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
     fi
   else
-    $as_echo "/* $configure_input  */" \
+    printf "%s\n" "/* $configure_input  */" >&1 \
       && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
       || as_fn_error $? "could not create -" "$LINENO" 5
   fi
@@ -10233,8 +10837,9 @@ if test "$no_create" != yes; then
   $ac_cs_success || as_fn_exit 1
 fi
 if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
-$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
 fi
 
 chmod 0700 unrealircd
+
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.4.2], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
+AC_INIT([unrealircd], [6.1.0], [https://bugs.unrealircd.org/], [], [https://unrealircd.org/])
 AC_CONFIG_SRCDIR([src/ircd.c])
 AC_CONFIG_HEADER([include/setup.h])
 AC_CONFIG_AUX_DIR([autoconf])
@@ -30,17 +30,17 @@ UNREAL_VERSION_GENERATION=["6"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_GENERATION], [$UNREAL_VERSION_GENERATION], [Generation version number (e.g.: X for X.Y.Z)])
 
 # Major version number (e.g.: Y in X.Y.Z)
-UNREAL_VERSION_MAJOR=["0"]
+UNREAL_VERSION_MAJOR=["1"]
 AC_DEFINE_UNQUOTED([UNREAL_VERSION_MAJOR], [$UNREAL_VERSION_MAJOR], [Major version number (e.g.: Y for X.Y.Z)])
 
 # Minor version number (e.g.: Z in X.Y.Z)
-UNREAL_VERSION_MINOR=["4"]
+UNREAL_VERSION_MINOR=["0"]
 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=[".2"]
+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)
@@ -257,6 +257,9 @@ check_cc_flag([-Wsign-compare], [CFLAGS="$CFLAGS -Wno-sign-compare"])
 dnl Don't warn about empty body, we use this, eg via Debug(()) or in if's.
 check_cc_flag([-Wempty-body], [CFLAGS="$CFLAGS -Wno-empty-body"])
 
+dnl This warns about all our hook calls - RunHook() and others
+check_cc_flag([-Wdeprecated-non-prototype], [CFLAGS="$CFLAGS -Wno-deprecated-non-prototype"])
+
 dnl Yeah this old clang version is a bit problematic
 dnl (ships in Ubuntu 16.04 for example)
 dnl -Wtautological-compare has false positives
@@ -326,7 +329,7 @@ fi
 ])
 AC_CACHE_CHECK(if your system prepends an underscore on symbols,ac_cv_underscore,[
 cat >uscore.c << __EOF__
-int main() {
+int main(void) {
 	return 0;
 }
 __EOF__
@@ -354,7 +357,8 @@ AC_CACHE_CHECK([if your system has IPv6 support], [ac_cv_ip6], [
 AC_TRY_RUN([
 #include <sys/types.h>
 #include <sys/socket.h>
-int main() {
+#include <stdlib.h>
+int main(void) {
 int s = socket(AF_INET6, SOCK_STREAM, 0);
 exit(0); /* We only check if the code compiles, that's enough. We can deal with missing runtime IPv6 */
 }
@@ -405,6 +409,7 @@ AC_CHECK_FUNCS([setproctitle],
 
 AC_CHECK_FUNCS(explicit_bzero,AC_DEFINE([HAVE_EXPLICIT_BZERO], [], [Define if you have explicit_bzero]))
 AC_CHECK_FUNCS(syslog,AC_DEFINE([HAVE_SYSLOG], [], [Define if you have syslog]))
+AC_CHECK_FUNCS(strnlen,AC_DEFINE([HAVE_STRNLEN], [], [Define if you have strnlen]))
 AC_SUBST(CRYPTOLIB)
 AC_SUBST(MODULEFLAGS)
 AC_SUBST(DYNAMIC_LDFLAGS)
@@ -547,6 +552,7 @@ CHECK_SSL_CTX_SET_MIN_PROTO_VERSION
 CHECK_SSL_CTX_SET_SECURITY_LEVEL
 CHECK_ASN1_TIME_diff
 CHECK_X509_get0_notAfter
+CHECK_X509_check_host
 AC_ARG_ENABLE(dynamic-linking, [AS_HELP_STRING([--disable-dynamic-linking], [Make the IRCd statically link with shared objects rather than dynamically (noone knows if disabling dynamic linking actually does anything or not)])],
 	[enable_dynamic_linking=$enableval], [enable_dynamic_linking="yes"])
 AS_IF([test $enable_dynamic_linking = "yes"],
@@ -580,12 +586,12 @@ export PATH_SEPARATOR
 dnl Use system pcre2 when available, unless --without-system-pcre2.
 has_system_pcre2="no"
 AS_IF([test "x$with_system_pcre2" = "xyes"],[
-PKG_CHECK_MODULES([PCRE2], libpcre2-8 >= 10.00,[has_system_pcre2=yes
+PKG_CHECK_MODULES([PCRE2], libpcre2-8 >= 10.36,[has_system_pcre2=yes
 AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libpcre2*])],[has_system_pcre2=no])])
 
 AS_IF([test "$has_system_pcre2" = "no"], [
 dnl REMEMBER TO CHANGE WITH A NEW PCRE2 RELEASE!
-pcre2_version="10.39"
+pcre2_version="10.42"
 AC_MSG_RESULT(extracting PCRE2 regex library)
 cur_dir=`pwd`
 cd extras
@@ -606,6 +612,7 @@ cd pcre2-$pcre2_version
 AC_MSG_RESULT(compiling PCRE2 regex library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing PCRE2 regex library)
+rm -f "$PRIVATELIBDIR/"libpcre2*
 $ac_cv_prog_MAKER install || exit 1
 PCRE2_CFLAGS="-I$cur_dir/extras/pcre2/include"
 AC_SUBST(PCRE2_CFLAGS)
@@ -631,7 +638,7 @@ AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libargon2*])],[ha
 
 AS_IF([test "$has_system_argon2" = "no"],[
 dnl REMEMBER TO CHANGE WITH A NEW ARGON2 RELEASE!
-argon2_version="20181209"
+argon2_version="20190702"
 AC_MSG_RESULT(extracting Argon2 library)
 cur_dir=`pwd`
 cd extras
@@ -639,12 +646,12 @@ dnl remove old argon2 directory to force a recompile...
 dnl and remove its installation prefix just to clean things up.
 rm -rf argon2-$argon2_version argon2
 if test "x$ac_cv_path_GUNZIP" = "x" ; then
-	tar xfz argon2-$argon2_version.tar.gz
+	tar xfz argon2.tar.gz
 else
-	cp argon2-$argon2_version.tar.gz argon2-$argon2_version.tar.gz.bak
-	gunzip -f argon2-$argon2_version.tar.gz
-	cp argon2-$argon2_version.tar.gz.bak argon2-$argon2_version.tar.gz
-	tar xf argon2-$argon2_version.tar
+	cp argon2.tar.gz argon2.tar.gz.bak
+	gunzip -f argon2.tar.gz
+	cp argon2.tar.gz.bak argon2.tar.gz
+	tar xf argon2.tar
 fi
 AC_MSG_RESULT(compiling Argon2 library)
 cd argon2-$argon2_version
@@ -699,6 +706,7 @@ CFLAGS="$save_cflags"
 AC_MSG_RESULT(compiling sodium resolver library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing sodium resolver library)
+rm -f "$PRIVATELIBDIR/"libsodium*
 $ac_cv_prog_MAKER install || exit 1
 SODIUM_CFLAGS="-I$cur_dir/extras/sodium/include"
 AC_SUBST(SODIUM_CFLAGS)
@@ -725,7 +733,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.18.1"
+cares_version="1.19.0"
 AC_MSG_RESULT(extracting c-ares resolver library)
 cur_dir=`pwd`
 cd extras
@@ -749,6 +757,7 @@ CFLAGS="$save_cflags"
 AC_MSG_RESULT(compiling c-ares resolver library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing c-ares resolver library)
+rm -f "$PRIVATELIBDIR/"libcares*
 $ac_cv_prog_MAKER install || exit 1
 CARES_CFLAGS="-I$cur_dir/extras/c-ares/include"
 AC_SUBST(CARES_CFLAGS)
@@ -799,7 +808,7 @@ AS_IF([test "x$PRIVATELIBDIR" != "x"], [rm -f "$PRIVATELIBDIR/"libjansson*])],[h
 
 AS_IF([test "$has_system_jansson" = "no"],[
 dnl REMEMBER TO CHANGE WITH A NEW JANSSON RELEASE!
-jansson_version="2.13.1"
+jansson_version="2.14"
 AC_MSG_RESULT(extracting jansson library)
 cur_dir=`pwd`
 cd extras
@@ -819,11 +828,12 @@ cd jansson-$jansson_version
 save_cflags="$CFLAGS"
 CFLAGS="$orig_cflags"
 export CFLAGS
-./configure --prefix=$cur_dir/extras/jansson --libdir=$PRIVATELIBDIR --enable-shared --disable-static --enable-opt || exit 1
+./configure --prefix=$cur_dir/extras/jansson --libdir=$PRIVATELIBDIR --enable-shared --disable-static || exit 1
 CFLAGS="$save_cflags"
 AC_MSG_RESULT(compiling jansson resolver library)
 $ac_cv_prog_MAKER || exit 1
 AC_MSG_RESULT(installing jansson resolver library)
+rm -f "$PRIVATELIBDIR/"libjansson*
 $ac_cv_prog_MAKER install || exit 1
 JANSSON_CFLAGS="-I$cur_dir/extras/jansson/include"
 AC_SUBST(JANSSON_CFLAGS)
@@ -873,6 +883,7 @@ AC_CONFIG_FILES([Makefile
 	src/modules/chanmodes/Makefile
 	src/modules/usermodes/Makefile
 	src/modules/extbans/Makefile
+	src/modules/rpc/Makefile
 	src/modules/third/Makefile
 	extras/unrealircd-upgrade-script
 	unrealircd])
diff --git a/doc/Config.header b/doc/Config.header
@@ -7,7 +7,7 @@
  \___/|_| |_|_|  \___|\__,_|_|\___/\_| \_| \____/\__,_|
 
                                Configuration Program
-                                for UnrealIRCd 6.0.4.2
+                                for UnrealIRCd 6.1.0
                                     
 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,19 +1,451 @@
-UnrealIRCd 6.0.4.2
-===================
-Another small update to 6.0.4.x:
+UnrealIRCd 6.1.0
+=================
+This is UnrealIRCd 6.1.0 stable. It is the direct successor to 6.0.7, there
+will be no 6.0.8.
+
+This release contains several channel mode `+f` enhancements and introduces a
+new channel mode `+F` which works with flood profiles like `+F normal` and
+`+F strict`. It is much easier for users than the scary looking mode +f.
+
+UnrealIRCd 6.1.0 also contains lots of JSON-RPC improvements, which is used
+by the [UnrealIRCd admin panel](https://www.unrealircd.org/docs/UnrealIRCd_webpanel).
+Live streaming of logs has been added and the webpanel now communicates to
+UnrealIRCd which web user issued a command (eg: who issued a kill, who
+changed a channel mode, ..).
+
+Other improvements are whowasdb (persistent WHOWAS history) and a new guide
+on running a Tor Onion service. The release also fixes a crash bug related
+to remote includes and fixes multiple memory leaks.
+
+See the full release notes below. As usual on *NIX you can upgrade easily
+with the command: `./unrealircd upgrade`
+
+### Enhancements:
+* Channel flood protection improvements:
+  * New [channel mode `+F`](https://www.unrealircd.org/docs/Channel_anti-flood_settings)
+    (uppercase F). This allows the user to choose a "flood profile",
+    which (behind the scenes) translates to something similar to an `+f` mode.
+    This so end-users can simply choose an `+F` profile without having to learn
+    the complex channel mode `+f`.
+    * For example `+F normal` effectively results in
+      `[7c#C15,30j#R10,10k#K15,40m#M10,8n#N15]:15`
+    * Multiple profiles are available and changing them is possible,
+      see [the documentation](https://www.unrealircd.org/docs/Channel_anti-flood_settings).
+    * Any settings in mode `+f` will override the ones of the `+F` profile.
+      To see the effective flood settings, use `MODE #channel F`.
+  * You can optionally set a default profile via
+    [set::anti-flood::channel::default-profile](https://www.unrealircd.org/docs/Channel_anti-flood_settings#Default_profile).
+    This profile is used if the channel is `-F`. If the user does not
+    want channel flood protection then they have to use an explicit `+F off`.
+  * When channel mode `+f` or `+F` detect that a flood is caused by >75% of
+    ["unknown-users"](https://www.unrealircd.org/docs/Security-group_block),
+    the server will now set a temporary ban on `~security-group:unknown-users`.
+    It will still set `+i` and other modes if the flood keeps on going
+    (eg. is caused by known-users).
+  * Forced nick changes (eg. by NickServ) are no longer counted in nick flood
+    for channel mode `+f`/`+F`.
+  * When a server splits on the network, we now temporarily disable +f/+F
+    join-flood protection for 75 seconds
+    ([set::anti-flood::channel::split-delay](https://www.unrealircd.org/docs/Channel_anti-flood_settings#config)).
+    This because a server splitting could mean that server has network problems
+    or has died (or restarted), in which case the clients would typically
+    reconnect to the remaining other servers, triggering an +f/+F join-flood and
+    channels ending up being `+i` and such. That is not good because we want
+    +f/+F to be as effortless as possible, with as little false positives as
+    possible.
+    * If your network has 5+ servers and the user load is spread evenly among
+      them, then you could disable this feature by setting the amount of seconds
+      to `0`. This because in such a scenario only 1/5th (20%) of the users
+      would reconnect and hopefully don't trigger +f/+F join floods.
+  * All these features only work properly if all servers are on 6.1.0-rc1 or later.
+* New module `whowasdb` (persistent `WHOWAS` history): this saves the WHOWAS
+  history on disk periodically and when we terminate, so next server boot
+  still has the WHOWAS history. This module is currently not loaded by default.
+* New option [listen::spoof-ip](https://www.unrealircd.org/docs/Listen_block#spoof-ip),
+  only valid when using UNIX domain sockets (so listen::file).
+  This way you can override the IP address that users come online with when
+  they use the socket (default was and still is `127.0.0.1`).
+* Add a new guide [Running Tor Onion service with UnrealIRCd](https://www.unrealircd.org/docs/Running_Tor_Onion_service_with_UnrealIRCd)
+  which uses the new listen::spoof-ip and optionally requires a services account.
+* [JSON-RPC](https://www.unrealircd.org/docs/JSON-RPC):
+  * Logging of JSON-RPC requests (eg. via snomask `+R`) has been improved,
+    it now shows:
+    * The issuer, such as the user logged in to the admin panel (if known)
+    * The parameters of the request
+  * The JSON-RPC calls
+    [`channel.list`](https://www.unrealircd.org/docs/JSON-RPC:Channel#channel.list),
+    [`channel.get`](https://www.unrealircd.org/docs/JSON-RPC:Channel#channel.get),
+    [`user.list`](https://www.unrealircd.org/docs/JSON-RPC:User#user.list) and
+    [`user.get`](https://www.unrealircd.org/docs/JSON-RPC:User#user.get)
+    now support an optional argument `object_detail_level` which specifies how detailed
+    the [Channel](https://www.unrealircd.org/docs/JSON-RPC:Channel#Structure_of_a_channel)
+    and [User](https://www.unrealircd.org/docs/JSON-RPC:User#Structure_of_a_client_object)
+    response object will be. Especially useful if you don't need all the
+    details in the list calls.
+  * New JSON-RPC methods
+    [`log.subscribe`](https://www.unrealircd.org/docs/JSON-RPC:Log#log.subscribe) and
+    [`log.unsubscribe`](https://www.unrealircd.org/docs/JSON-RPC:Log#log.unsubscribe)
+    to allow real-time streaming of
+    [JSON log events](https://www.unrealircd.org/docs/JSON_logging).
+  * New JSON-RPC method
+    [`rpc.set_issuer`](https://www.unrealircd.org/docs/JSON-RPC:Rpc#rpc.set_issuer)
+    to indiciate who is actually issuing the requests. The admin panel uses this
+    to communicate who is logged in to the panel so this info can be used in logging.
+  * New JSON-RPC methods
+    [`rpc.add_timer`](https://www.unrealircd.org/docs/JSON-RPC:Rpc#rpc.add_timer) and
+    [`rpc.del_timer`](https://www.unrealircd.org/docs/JSON-RPC:Rpc#rpc.del_timer)
+    so you can schedule JSON-RPC calls, like stats.get, to be executed every xyz msec.
+  * New JSON-RPC method
+    [`whowas.get`](https://www.unrealircd.org/docs/JSON-RPC:Whowas#whowas.get)
+    to fetch WHOWAS history.
+  * Low ASCII is no longer filtered out in strings in JSON-RPC, only in JSON logging.
+* A new message tag `unrealircd.org/issued-by` which is IRCOp-only (and
+  used intra-server) to communicate who actually issued a command.
+  See [docs](https://www.unrealircd.org/issued-by).
+
+### Changes:
+* The RPC modules are enabled by default now. This so remote RPC works
+  from other IRC servers for calls like `modules.list`. The default
+  configuration does NOT enable the webserver nor does it cause
+  listening on any socket for RPC, for that you need to follow the
+  [JSON-RPC](https://www.unrealircd.org/docs/JSON-RPC) instructions.
+* The [blacklist-module](https://www.unrealircd.org/docs/Blacklist-module_directive)
+  directive now accepts wildcards, eg `blacklist-module rpc/*;`
+* The setting set::modef-boot-delay has been moved to
+  [set::anti-flood::channel::boot-delay](https://www.unrealircd.org/docs/Channel_anti-flood_settings#config).
+* We now only exempt `127.0.0.1` and `::1` from banning by default
+  (hardcoded in the source). Previously we exempted whole `127.*` but
+  that gets in the way if you want to allow Tor with a
+  [require authentication](https://www.unrealircd.org/docs/Require_authentication_block)
+  block or soft-ban. Now you can just tell Tor to bind to `127.0.0.2`
+  so its not affected by the default exemption.
+
+### Fixes:
+* Crash if there is a parse error in an included file and there are
+  other remote included files still being downloaded.
+* Memory leak in WHOWAS
+* Memory leak when connecting to a TLS server fails
+* Workaround a bug in some websocket implementations where the WSOP_PONG
+  frame is unmasked (now permitted).
+
+### Developers and protocol:
+* The `cmode.free_param` definition changed. It now has an extra argument
+  `int soft` and for return value you will normally `return 0` here.
+  You can `return 1` if you resist freeing, which is rare and only used by
+  `+F` with set::anti-flood::channel::default-profile.
+* New `cmode.flood_type_action` which can be used to indicate a channel mode
+  can be used from +f/+F as an action. You need to specify for which
+  flood type your mode is, eg `cmode.flood_type_action = 'j';` for joinflood.
+* JSON-RPC supports
+  [UNIX domain sockets](https://www.unrealircd.org/docs/JSON-RPC:Technical_documentation#UNIX_domain_socket)
+  for making RPC calls. If this is used, we now split on `\n` (newline)
+  so multiple parallel requests can be handled properly.
+* Message tag `unrealircd.org/issued-by`, sent to IRCOps only.
+  See [docs](https://www.unrealircd.org/issued-by).
+
+UnrealIRCd 6.0.7
+-----------------
+
+UnrealIRCd 6.0.7 makes WHOWAS show more information to IRCOps and adds an
+experimental spamfilter feature. It also contains other enhancements and
+quite a number of bug fixes. One notable change is that on linking of anope
+or atheme, every server will now check if they have ulines { } for that
+services server, since it's a common mistake to forget this, leading to
+desyncs or other weird problems.
+
+### Enhancements:
+* [Spamfilter](https://www.unrealircd.org/docs/Spamfilter) can now be made UTF8-aware:
+  * This is experimental, to enable: `set { spamfilter { utf8 yes; } }`
+  * Case insensitive matches will then work better. For example, for extended
+    Latin, a spamfilter on `ę` then also matches `Ę`.
+  * Other PCRE2 features such as [\p](https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC5)
+    can then be used. For example the regex `\p{Arabic}` would block all Arabic script.
+    See also this [full list of scripts](https://www.pcre.org/current/doc/html/pcre2syntax.html#SEC7).
+    Please use this new tool with care. Blocking an entire language or script
+    is quite a drastic measure.
+  * As a consequence of this we require PCRE2 10.36 or newer. If your system
+    PCRE2 is older, then the UnrealIRCd-shipped-library version will be compiled
+    and `./Config` may take a little longer than usual.
+* `WHOWAS` now shows IP address and account information to IRCOps
+* Allow services to send a couple of protocol messages in the
+  unregistered / SASL stage. These are: `CHGHOST`, `CHGIDENT`
+  and `SREPLY`
+  * This allows services to set the vhost on a user during SASL,
+    so the user receives the vhost straight from the start, before
+    all the auto-joining/re-rejoining of channels.
+  * Future anope/atheme/etc services will presumably support this.
+* [WebSocket](https://www.unrealircd.org/docs/WebSocket_support) status is
+  now synced over the network and an extra default
+  [security group](https://www.unrealircd.org/docs/Security-group_block)
+  `websocket-users` has been added. Similarly there is now
+  security-group::websocket and security-group::exclude-websocket item.
+  Same for [mask items](https://www.unrealircd.org/docs/Mask_item) such
+  as in [set::restrict-commands::command::except](https://www.unrealircd.org/docs/Restrict_commands).
+* Support for IRCv3 [Standard Replies](https://ircv3.net/specs/extensions/standard-replies).
+  Right now nothing fancy yet, other than us sending `ACCOUNT_REQUIRED_TO_CONNECT`
+  from the authprompt module when a user is
+  [soft-banned](https://www.unrealircd.org/docs/Soft_ban).
+* Add support for sending IRCv3 Standard Replies intra-server, eg
+  from services (`SREPLY` server-to-server command)
+* Support `NO_COLOR` environment variable, as per [no-color.org](https://no-color.org).
+
+### Changes:
+* We now verify that all servers have
+  [ulines { }](https://www.unrealircd.org/docs/Ulines_block) for Anope and
+  Atheme servers and reject the link if this is not the case.
+* The `FLOOD_BLOCKED` log message now shows the target of the flood
+  for `target-flood-user` and `target-flood-channel`.
+* When an IRCOp sets `+H` to hide ircop status, only the swhois items that
+  were added through oper will be hidden (and not the ones added by eg. vhost).
+  Previously all were hidden.
+* Update shipped libraries: c-ares to 1.19.0, Jansson to 2.14, PCRE2 to 10.42,
+  and on Windows LibreSSL to 3.6.2 and cURL to 8.0.1.
+
+### Fixes:
+* Crash if a third party module is loaded which allows very large message tags
+  (e.g. has no length check)
+* Crash if an IRCOp uses
+  [`unrealircd.org/json-log`](https://www.unrealircd.org/docs/JSON_logging#Enabling_on_IRC)
+  on IRC and during `REHASH` some module sends log output during MOD_INIT
+  (eg. with some 3rd party modules)
+* Crash when parsing [deny link block](https://www.unrealircd.org/docs/Deny_link_block)
+* The [Module manager](https://www.unrealircd.org/docs/Module_manager)
+  now works on FreeBSD and similar.
+* In `LUSERS` the "unknown connection(s)" count was wrong. This was just a
+  harmless counting error with no other effects.
+* Silence warnings on Clang 15+ (eg. Ubuntu 23.04)
+* Don't download `GeoIP.dat` if you have 
+  [`blacklist-module geoip_classic;`](https://www.unrealircd.org/docs/Blacklist-module_directive)
+* Channel mode `+S` stripping too much on incorrect color codes.
+* Make [`@if module-loaded()`](https://www.unrealircd.org/docs/Defines_and_conditional_config)
+  work correctly for modules that are about to be unloaded during REHASH.
+* Some missing notices if remotely REHASHing a server, and one duplicate line.
+* Check invalid host setting in oper::vhost, just like we already have in vhost::vhost.
+
+UnrealIRCd 6.0.6
+-----------------
+
+The main objective of this release is to enhance the new JSON-RPC functionality.
+In 6.0.5 we made a start and in 6.0.6 it is expanded a lot, plus some important
+bugs were fixed in it. Thanks everyone who has been testing the functionality!
+
+The new [UnrealIRCd Administration Webpanel](https://github.com/unrealircd/unrealircd-webpanel/)
+(which uses JSON-RPC) is very much usable now. It allows admins to view the
+users/channels/servers lists, view detailed information on users and channels,
+manage server bans and spamfilters, all from the browser.
+
+Both the JSON-RPC API and the webpanel are work in progress. They will improve
+and expand with more features over time.
+
+If you are already using UnrealIRCd 6.0.5 and you are NOT interested in
+JSON-RPC or the webpanel then there is NO reason to upgrade to 6.0.6.
+
+As usual, on *NIX you can easily upgrade with `./unrealircd upgrade`
+
+### Enhancements:
+* The [JSON-RPC](https://www.unrealircd.org/docs/JSON-RPC) API for
+  UnrealIRCd has been expanded a lot. From 12 API methods to 42:
+  `stats.get`, `rpc.info`, `user.part`,
+  `user.join`, `user.quit`, `user.kill`,
+  `user.set_oper`, `user.set_snomask`, `user.set_mode`,
+  `user.set_vhost`, `user.set_realname`,
+  `user.set_username`, `user.set_nick`, `user.get`,
+  `user.list`, `server.module_list`, `server.disconnect`,
+  `server.connect`, `server.rehash`, `server.get`,
+  `server.list`, `channel.kick`, `channel.set_topic`,
+  `channel.set_mode`, `channel.get`, `channel.list`,
+  `server_ban.add`, `server_ban.del`, `server_ban.get`,
+  `server_ban.list`, `server_ban_exception.add`,
+  `server_ban_exception.del`, `server_ban_exception.get`,
+  `server_ban_exception.list`, `name_ban.add`,
+  `name_ban.del`, `name_ban.get`, `name_ban.list`,
+  `spamfilter.add`, `spamfilter.del`, `spamfilter.get`,
+  `spamfilter.list`.
+  * Server admins can read the [JSON-RPC](https://www.unrealircd.org/docs/JSON-RPC)
+    documentation on how to get started. For developers, see the
+    [Technical documentation](https://www.unrealircd.org/docs/JSON-RPC:Technical_documentation)
+    for all info on the different RPC calls and the protocol.
+  * Some functionality requires all servers to be on 6.0.6 or later.
+  * Some functionality requires all servers to include
+    `rpc.modules.default.conf` instead of only the single server that
+    the webpanel interfaces with through JSON-RPC.
+    When all servers have that file included then the API call
+    `server.module_list` can work for remote servers, and the API call
+    `server.rehash` for remote servers can return the actual rehash result
+    and a full log of the rehash process. It is not used for any other
+    API call at the moment, but in the future more API calls may need this
+    functionality because it allows us to do things that are otherwise impossible
+    or very hard.
+  * Known issue: logging of RPC actions needs to be improved. For some API calls,
+    like adding of server bans and spamfilters, this already works, but in
+    other API calls it is not clearly logged yet "who did what".
+
+### Changes:
+* Previously some server protocol commands could only be used by
+  services, commands such as `SVSJOIN` and `SVSPART`. We now allow SVS*
+  command to be used by any servers, so the JSON-RPC API can use them.
+  There's a new option
+  [set::limit-svscmds](https://www.unrealircd.org/docs/Set_block#set::limit-svscmds)
+  so one can revert back to the original situation, if needed.
+* All JSON-RPC calls that don't change anything, such as `user.list`
+  are now logged in the `rpc.debug` facility. Any call that changes
+  anything like `user.join` or `spamfilter.add` is logged via `rpc.info`.
+  This because JSON-RPC calls can be quite noisy and logging the
+  read-only calls is generally not so interesting.
+
+### Fixes:
+* When using JSON-RPC with UnrealIRCd 6.0.5 it would often crash
+* Fix parsing services version (anope) in `EAUTH`.
+
+### Developers and protocol:
+* A new `RRPC` server to server command to handle RPC-over-IRC.
+  This way the JSON-RPC user, like the admin panel, can interface with
+  a remote server. If you are writing an RPC handler, then the remote
+  RPC request does not look much different than a local one, so you
+  can just process it as usual. See the code for `server.rehash` or
+  `server.module_list` for an example (src/modules/rpc/server.c).
+
+UnrealIRCd 6.0.5
+-----------------
+
+This release adds experimental JSON-RPC support, a new TLINE command, the
+`./unrealircd restart` command has been improved to check for config errors,
+logging to files has been improved and there are several other enhancements.
+
+There are also two important changes: 1) servers that use websockets now also
+need to load the "webserver" module (so you may need to edit your config
+file). 2) we now require TLSv1.2 or higher and a modern cipher for IRC clients.
+This should be no problem for clients using any reasonably new SSL/TLS library
+(from 2014 or later).
 
-* Fix crash when linking. This requires a certain sequence of events: first
+I would also like to take this opportunity to say that we are
+[looking for webdevs to create an UnrealIRCd admin panel](https://forums.unrealircd.org/viewtopic.php?t=9257).
+The previous attempt at this failed so we are looking for new people.
+
+See the full release notes below for all changes in more detail.
+
+As usual, on *NIX you can easily upgrade with `./unrealircd upgrade`
+
+### Enhancements:
+* Internally the websocket module has been split up into 3 modules:
+  `websocket_common`, `webserver` and `websocket`. The `websocket_common` one
+  is loaded by default via modules.default.conf, the other two are not.  
+  **Important:** if you use websockets then you need to load two modules now (instead of only one):
+  ```
+  loadmodule "websocket";
+  loadmodule "webserver";
+  ```
+* [JSON-RPC](https://www.unrealircd.org/docs/JSON-RPC) API for UnrealIRCd.
+  This is work in progress.
+* New `TLINE` command to test *LINEs. This can be especially useful for 
+  checking how many people match an [extended server ban](https://www.unrealircd.org/docs/Extended_server_bans)
+  such as `TLINE ~C:NL`
+* The `./unrealircd start` command will now refuse to start if UnrealIRCd
+  is already running.
+* The `./unrealircd restart` command will validate the configuration file
+  (it will call `./unrealircd configtest`). If there is a configuration
+  error then the restart will not go through and the current UnrealIRCd
+  process is kept running.
+* When an IRCOp is outside the channel and does `MODE #channel` they will
+  now get to see the mode parameters too. This depends on the `channel:see:mode:remote`
+  [operclass permission](https://www.unrealircd.org/docs/Operclass_permissions)
+  which all IRCOps have by default if you use the default operclasses.
+* [Logging to a file](https://www.unrealircd.org/docs/Log_block) now creates
+  a directory structure if needed.
+  * You could already use:
+    ```
+    log { source { !debug; all; } destination { file "ircd.%Y-%m-%d.log"; } }
+    ```
+  * But now you can also use:
+    ```
+    log { source { !debug; all; } destination { file "%Y-%m-%d/ircd.log"; } }
+    ```
+    This is especially useful if you output to multiple log files and then
+    want them grouped by date in a directory.
+* Add additional variables in
+  [blacklist::reason](https://www.unrealircd.org/docs/Blacklist_block):
+  * `$blacklist`: name of the blacklist block
+  * `$dnsname`: the blacklist::dns::name
+  * `$dnsreply`: the DNS reply code
+* Resolved technical issue so opers can `REHASH` from
+  [Websocket connections](https://www.unrealircd.org/docs/WebSocket_support).
+* In the [TLD block](https://www.unrealircd.org/docs/Tld_block) the use
+  of `tld::motd` and `tld::rules` is now optional.
+* Log which oper actually initiated a server link request (`CONNECT`)
+
+### Changes:
+* SSL/TLS: By default we now require TLSv1.2 or later and a modern cipher
+  with forward secrecy. Otherwise the connection is refused.
+  * Since UnrealIRCd 4.2.2 (March 2019) users see an on-connect notice with
+    a warning when they use an outdated TLS protocol or cipher that does not
+    meet these requirements.
+  * This move also reflects the phase out of versions below TLSv1.2 which
+    happened in browsers in 2020/2021.
+  * In practice on the client-side this requires at least:
+    * OpenSSL 1.0.1 (released in 2012)
+    * GnuTLS 3.2.6 (2013)
+    * Android 4.4.2 (2013)
+    * Or presumably any other SSL/TLS library that is not 9+ years old
+  * If you want to revert back to the previous less secure settings, then
+    look under ''Previous less secure setting'' in
+    [TLS Ciphers and protocols](https://www.unrealircd.org/docs/TLS_Ciphers_and_protocols).
+* The code for handling
+  [`set::anti-flood::everyone::connect-flood`](https://www.unrealircd.org/docs/Anti-flood_settings#connect-flood)
+  is now in its own module `connect-flood`. This module is loaded by default,
+  no changes needed in your configuration file.
+* Similarly,
+  [`set:max-unknown-connections-per-ip`](https://www.unrealircd.org/docs/Set_block#set::max-unknown-connections-per-ip)
+  is now handled by the new module `max-unknown-connections-per-ip`. This module is loaded
+  by default as well, no changes needed in your configuration file.
+* Upgrade shipped PCRE2 to 10.41, curl-ca-bundle to 2022-10-11,
+  on Windows LibreSSL to 3.6.1 and cURL to 7.86.0.
+* After people do a major upgrade on their Linux distro, UnrealIRCd may
+  no longer start due to an `error while loading shared libraries`.
+  We now print a more helpful message and link to the new
+  [FAQ entry](https://www.unrealircd.org/docs/FAQ#shared-library-error)
+  about it.
+* When timing out on the [authprompt](https://www.unrealircd.org/docs/Set_block#set::authentication-prompt)
+  module, the error (quit message) is now the original (ban) reason for the
+  prompt, instead of the generic `Registration timeout`.
+
+### Fixes:
+* Crash when linking. This requires a certain sequence of events: first
   a server is linked in successfully, then we need to REHASH, and then a new
   link attempt has to come in with the same server name (for example because
   there is a network issue and the old link has not timed out yet).
   If all that happens, then an UnreaIRCd 6 server may crash, but not always.
+* Warning message about moddata creationtime when linking.
+* [Snomask `+j`](https://www.unrealircd.org/docs/Snomasks) was not showing
+  remote joins, even though it did show remote parts and kicks.
+* Leak of 1 file descriptor per /REHASH (the control socket).
+* Ban letters showing up twice in 005 EXTBAN=
+* Setting [set::authentication-prompt::enabled](https://www.unrealircd.org/docs/Set_block#set::authentication-prompt)
+  to `no` was ignored. The default is still `yes`.
+
+### Developers and protocol:
+* Add `CALL_CMD_FUNC(cmd_func_name)` for calling commands in the same
+  module, see [this commit](https://github.com/unrealircd/unrealircd/commit/dc55c3ec9f19e5ed284e5a786f646d0e6bb60ef9).
+  Benefit of this is that it will keep working if we ever change command paramters.
+* Add `CALL_NEXT_COMMAND_OVERRIDE()` which can be used instead of
+  `CallCommandOverride()`, see also [this commit](https://github.com/unrealircd/unrealircd/commit/4e5598b6cf0986095f757f31a2540b03e4d235dc).
+  This too, will keep working if we ever change command parameters.
+* During loading and rehash we now set `loop.config_status` to one of
+  `CONFIG_STATUS_*` so modules (and core) can see at what step we are
+  during configuration file and module processing.
+* New RPC API. See the `src/modules/rpc/` directory for examples.
+* New function `get_nvplist(NameValuePrioList *list, const char *name)`
+
+UnrealIRCd 6.0.4.2
+-------------------
+Another small update to 6.0.4.x:
+
 * Two IRCv3 specifications were ratified which we already supported as drafts:
   * Change CAP `draft/extended-monitor` to `extended-monitor`
   * Add message-tag `bot` next to existing (for now) `draft/bot`
 * Update Turkish translations
 
 UnrealIRCd 6.0.4.1
-===================
+-------------------
 This is a small update to 6.0.4. It fixes the following issues that were
 present in all 6.0.x versions:
 
@@ -27,12 +459,11 @@ present in all 6.0.x versions:
   (limit) and other restrictions and would have to resort back to using
   MODE or SAMODE. Only +b and +i could be bypassed via INVITE OperOverride.
 
+(This cherry picks commit 0e6fc07bd9000ecc463577892cf2195a670de4be and
+ commit 0d139c6e7c268e31ca8a4c9fc5cb7bfeb4f56831 from 6.0.5-git)
+
 UnrealIRCd 6.0.4
 -----------------
-This release comes with lots of features and enhancements. In particular,
-security groups and mask items now allow you to write cleaner and more
-flexible configuration files. There are also JSON logging enhancements and
-several bug fixes. Thanks a lot to everyone who tested the release candidates!
 
 If you are already running UnrealIRCd 6 then read below. Otherwise, jump
 straight to the [summary about UnrealIRCd 6](#Summary) to learn more
@@ -148,8 +579,8 @@ about UnrealIRCd 6.
 ### Changes:
 * Clarified that UnrealIRCd is licensed as "GPLv2 or later"
 * Fix use of variables in
-  [`set::reject-message](https://www.unrealircd.org/docs/Set_block#set::reject-message)
-  and in [`blacklist::reason](https://www.unrealircd.org/docs/Blacklist_block):
+  [`set::reject-message`](https://www.unrealircd.org/docs/Set_block#set::reject-message)
+  and in [`blacklist::reason`](https://www.unrealircd.org/docs/Blacklist_block):
   previously short forms of variables were (unintentionally) expanded
   as well, such as `$serv` for `$server`. This is no longer supported, you need
   to use the correct full variable names.
diff --git a/doc/conf/modules.conf b/doc/conf/modules.conf
@@ -164,6 +164,7 @@ loadmodule "usermodes/servicebot";    /* +S */
 loadmodule "extbans/account";       /* +b ~account        */
 loadmodule "extbans/certfp";        /* +b ~certfp         */
 #loadmodule "extbans/country";      /* +b ~country        */
+loadmodule "extbans/flood";         /* +e ~flood          */
 loadmodule "extbans/inchannel";     /* +b ~channel        */
 loadmodule "extbans/join";          /* +b ~join           */
 loadmodule "extbans/msgbypass";     /* +e ~msgbypass      */
@@ -194,9 +195,23 @@ loadmodule "monitor";
 loadmodule "plaintext-policy";
 loadmodule "reply-tag";
 loadmodule "server-time";
+loadmodule "standard-replies";
 loadmodule "sts";
 loadmodule "typing-indicator";
 
+// RPC
+loadmodule "rpc/rpc";
+loadmodule "rpc/stats";
+loadmodule "rpc/user";
+loadmodule "rpc/server";
+loadmodule "rpc/channel";
+loadmodule "rpc/server_ban";
+loadmodule "rpc/server_ban_exception";
+loadmodule "rpc/name_ban";
+loadmodule "rpc/spamfilter";
+loadmodule "rpc/log";
+loadmodule "rpc/whowas";
+
 // Other
 loadmodule "antimixedutf8";
 #loadmodule "authprompt";
@@ -228,3 +243,4 @@ loadmodule "watch-backend";
 #loadmodule "webirc";
 #loadmodule "webserver";
 #loadmodule "websocket";
+loadmodule "websocket_common";
+\ No newline at end of file
diff --git a/doc/conf/snomasks.conf b/doc/conf/snomasks.conf
@@ -16,6 +16,7 @@ log {
 		nomatch;
 		oper;
 		operoverride;
+		rpc;
 		sacmds;
 		tkl.BAN_REALNAME;
 		tkl.RMTKL_COMMAND;
diff --git a/doc/conf/unrealircd.remote.conf b/doc/conf/unrealircd.remote.conf
@@ -146,6 +146,16 @@ set {
 		oper-message "Network operators must be using an up-to-date TLS protocol & cipher";
 	}
 	anti-flood {
+		channel {
+			profile very-strict  { flood-mode "[7c#C15,10j#R10,10k#K15,30m#M10,10n#N15]:15"; }
+			profile strict       { flood-mode "[7c#C15,15j#R10,10k#K15,40m#M10,10n#N15]:15"; }
+			profile normal       { flood-mode "[7c#C15,30j#R10,10k#K15,40m#M10,10n#N15]:15"; }
+			profile relaxed      { flood-mode "[7c#C15,45j#R10,10k#K15,60m#M10,10n#N15]:15"; }
+			profile very-relaxed { flood-mode "[7c#C15,60j#R10,10k#K15,90m#M10,10n#N15]:15"; }
+			default-profile normal;
+			boot-delay 75;
+			split-delay 75;
+		}
 		everyone {
 			connect-flood 3:300;
 			handshake-data-flood {
diff --git a/doc/technical/005.txt b/doc/technical/005.txt
@@ -18,14 +18,14 @@ by this server"
 Currently UnrealIRCd supports several tokens that are included in numeric 005. A list of
 all tokens, their respective value and a brief description are listed below.
 
-Unreal attempts to follow the proposed ISupport standard as much as possible. Unreal only
-ignores the standard in one regard, the TARGMAX token. This token is believed to be 
+UnrealIRCd attempts to follow the proposed ISupport standard as much as possible. UnrealIRCd
+only ignores the standard in one regard, the TARGMAX token. This token is believed to be 
 impractical and technically impossible to correctly implement due to existing limitations
 in the standard. Therefore, this token is not currently supported. 
 
-Unreal does additionally provide a few tokens which are not specified in the standard, these
+UnrealIRCd does additionally provide a few tokens which are not specified in the standard, these
 include: HCN, AWAYLEN, WATCH, SILENCE, EXTBAN, ELIST, CMDS, NAMESX, UHNAMES, and WATCHOPTS.
-Unreal also maintains a few legacy tokens such as MAXCHANNELS and WALLCHOPS to ensure
+UnrealIRCd also maintains a few legacy tokens such as MAXCHANNELS and WALLCHOPS to ensure
 compatibility until the ISupport standard is more widely accepted by clients.
 
 Token         Value           Default Value                Description
diff --git a/extras/argon2-20181209.tar.gz b/extras/argon2-20181209.tar.gz
Binary files differ.
diff --git a/extras/argon2.tar.gz b/extras/argon2.tar.gz
Binary files differ.
diff --git a/extras/build-tests/nix/build b/extras/build-tests/nix/build
@@ -48,8 +48,8 @@ fi
 $MAKE
 yes ''|$MAKE pem
 $MAKE || exit 1
-./unrealircd module install third/dumpcmds
 $MAKE install || exit 1
+./unrealircd module install third/dumpcmds || exit 1
 
 set +x
 echo ""
diff --git a/extras/build-tests/windows/compilecmd/vs2019.bat b/extras/build-tests/windows/compilecmd/vs2019.bat
@@ -6,7 +6,7 @@ rem But nowadays we use JOM for parallel builds:
 jom /j32 -f makefile.windows ^
 LIBRESSL_INC_DIR="c:\projects\unrealircd-6-libs\libressl\include" ^
 LIBRESSL_LIB_DIR="c:\projects\unrealircd-6-libs\libressl\lib" ^
-SSLLIB="crypto-47.lib ssl-50.lib" ^
+SSLLIB="crypto-50.lib ssl-53.lib" ^
 USE_REMOTEINC=1 ^
 LIBCURL_INC_DIR="c:\projects\unrealircd-6-libs\curl\include" ^
 LIBCURL_LIB_DIR="c:\projects\unrealircd-6-libs\curl\builds\libcurl-vc-x64-release-dll-ssl-dll-cares-dll-ipv6-obj-lib" ^
diff --git a/extras/c-ares.tar.gz b/extras/c-ares.tar.gz
Binary files differ.
diff --git a/extras/curlinstall b/extras/curlinstall
@@ -76,4 +76,6 @@ cd "$OUTD" || exit 1
 
 echo "Building and installing libcurl"
 ./configure --prefix=$UNREALDIR/extras/curl --libdir=$PRIVATELIBDIR --enable-shared --with-openssl
-make && make install
+make || exit 1
+rm -f "$PRIVATELIBDIR/"libcurl*
+make install || exit 1
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.4.2
+PROJECT_NUMBER         = 6.1.0
 
 # 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/jansson.tar.gz b/extras/jansson.tar.gz
Binary files differ.
diff --git a/extras/pcre2.tar.gz b/extras/pcre2.tar.gz
Binary files differ.
diff --git a/extras/tests/tls/cipherscan_profiles/baseline.txt b/extras/tests/tls/cipherscan_profiles/baseline.txt
@@ -1,12 +1,10 @@
 Target: 127.0.0.1:5901
 
-prio  ciphersuite                    protocols              pfs                 curves
-1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-4     ECDHE-ECDSA-AES128-SHA256      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-5     ECDHE-ECDSA-AES256-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
-6     ECDHE-ECDSA-AES128-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
+prio  ciphersuite                    protocols  pfs                 curves
+1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
+2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
+3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
+4     ECDHE-ECDSA-AES128-SHA256      TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
 
 Certificate: untrusted, 384 bits, ecdsa-with-SHA256 signature
 TLS ticket lifetime hint: None
@@ -20,8 +18,8 @@ TLS Tolerance: yes
 
 Intolerance to:
  SSL 3.254           : absent
- TLS 1.0             : absent
- TLS 1.1             : absent
+ TLS 1.0             : PRESENT
+ TLS 1.1             : PRESENT
  TLS 1.2             : absent
  TLS 1.3             : absent
  TLS 1.4             : absent
diff --git a/extras/tests/tls/cipherscan_profiles/openssl-101.txt b/extras/tests/tls/cipherscan_profiles/openssl-101.txt
@@ -1,27 +0,0 @@
-Target: 127.0.0.1:5901
-
-prio  ciphersuite                    protocols              pfs                 curves
-1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2                ECDH,P-256,256bits  prime256v1
-2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2                ECDH,P-256,256bits  prime256v1
-3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2                ECDH,P-256,256bits  prime256v1
-4     ECDHE-ECDSA-AES256-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-256,256bits  prime256v1
-5     ECDHE-ECDSA-AES128-SHA256      TLSv1.2                ECDH,P-256,256bits  prime256v1
-6     ECDHE-ECDSA-AES128-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-256,256bits  prime256v1
-
-Certificate: untrusted, 384 bits, ecdsa-with-SHA256 signature
-TLS ticket lifetime hint: None
-NPN protocols: None
-OCSP stapling: not supported
-Cipher ordering: server
-Curves ordering: server - fallback: no
-Server supports secure renegotiation
-Server supported compression methods: NONE
-TLS Tolerance: yes
-
-Intolerance to:
- SSL 3.254           : absent
- TLS 1.0             : absent
- TLS 1.1             : absent
- TLS 1.2             : absent
- TLS 1.3             : absent
- TLS 1.4             : absent
diff --git a/extras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt b/extras/tests/tls/cipherscan_profiles/openssl-102-ubuntu16.txt
@@ -1,27 +0,0 @@
-Target: 127.0.0.1:5901
-
-prio  ciphersuite                    protocols              pfs                 curves
-1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-4     ECDHE-ECDSA-AES256-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
-5     ECDHE-ECDSA-AES128-SHA256      TLSv1.2                ECDH,P-521,521bits  secp521r1,secp384r1
-6     ECDHE-ECDSA-AES128-SHA         TLSv1,TLSv1.1,TLSv1.2  ECDH,P-521,521bits  secp521r1,secp384r1
-
-Certificate: untrusted, 384 bits, ecdsa-with-SHA256 signature
-TLS ticket lifetime hint: None
-NPN protocols: None
-OCSP stapling: not supported
-Cipher ordering: server
-Curves ordering: server - fallback: no
-Server supports secure renegotiation
-Server supported compression methods: NONE
-TLS Tolerance: yes
-
-Intolerance to:
- SSL 3.254           : absent
- TLS 1.0             : absent
- TLS 1.1             : absent
- TLS 1.2             : absent
- TLS 1.3             : absent
- TLS 1.4             : absent
diff --git a/extras/tests/tls/cipherscan_profiles/openssl-300.txt b/extras/tests/tls/cipherscan_profiles/openssl-300.txt
@@ -1,27 +0,0 @@
-Target: 127.0.0.1:5901
-
-prio  ciphersuite                    protocols  pfs                 curves
-1     ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-2     ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-3     ECDHE-ECDSA-AES256-SHA384      TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-4     ECDHE-ECDSA-AES128-SHA256      TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-5     ECDHE-ECDSA-AES256-SHA         TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-6     ECDHE-ECDSA-AES128-SHA         TLSv1.2    ECDH,P-521,521bits  secp521r1,secp384r1
-
-Certificate: untrusted, 384 bits, ecdsa-with-SHA256 signature
-TLS ticket lifetime hint: None
-NPN protocols: None
-OCSP stapling: not supported
-Cipher ordering: server
-Curves ordering: server - fallback: no
-Server supports secure renegotiation
-Server supported compression methods: NONE
-TLS Tolerance: yes
-
-Intolerance to:
- SSL 3.254           : absent
- TLS 1.0             : PRESENT
- TLS 1.1             : PRESENT
- TLS 1.2             : absent
- TLS 1.3             : absent
- TLS 1.4             : absent
diff --git a/include/common.h b/include/common.h
@@ -96,6 +96,7 @@ extern int myncmp(const char *, const char *, int);
 #endif
 
 extern char *strtoken(char **, char *, char *);
+extern char *strtoken_noskip(char **, char *, char *);
 
 extern MODVAR int  global_count, max_global_count;
 #ifdef _WIN32
@@ -167,7 +168,7 @@ extern MODVAR unsigned char char_atribs[];
  * you are doing.
  */
 
-/* IRCu/Hybrid/Unreal way now :) -Stskeeps */
+/* IRCu/Hybrid/unrealircd way now :) -Stskeeps */
 
 #define EXPAR1	extchmstr[0]
 #define EXPAR2	extchmstr[1]
diff --git a/include/config.h b/include/config.h
@@ -275,12 +275,12 @@
 /* Default TLS cipherlist (except for TLS1.3, see further down).
  * This can be changed via set::ssl::options::ciphers in the config file.
  */
-#define UNREALIRCD_DEFAULT_CIPHERS "TLS13-CHACHA20-POLY1305-SHA256 TLS13-AES-256-GCM-SHA384 TLS13-AES-128-GCM-SHA256 EECDH+CHACHA20 EECDH+AESGCM EECDH+AES AES256-GCM-SHA384 AES128-GCM-SHA256 AES256-SHA256 AES128-SHA256 AES256-SHA AES128-SHA"
+#define UNREALIRCD_DEFAULT_CIPHERS "EECDH+CHACHA20 EECDH+AESGCM EECDH+AES+SHA384 EECDH+AES+SHA256"
 
 /* Default TLS 1.3 ciphersuites.
  * This can be changed via set::ssl::options::ciphersuites in the config file.
  */
-#define UNREALIRCD_DEFAULT_CIPHERSUITES "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256"
+#define UNREALIRCD_DEFAULT_CIPHERSUITES "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"
 
 /* Default TLS curves for ECDH(E)
  * This can be changed via set::ssl::options::ecdh-curve in the config file.
diff --git a/include/dbuf.h b/include/dbuf.h
@@ -95,6 +95,7 @@ void dbuf_delete(dbuf *, size_t);
 #define DBufClear(dyn)	dbuf_delete((dyn),DBufLength(dyn))
 
 extern int dbuf_getmsg(dbuf *, char *);
+extern int dbuf_get(dbuf *dyn, char **buf);
 extern void dbuf_queue_init(dbuf *dyn);
 extern void dbuf_init(void);
 
diff --git a/include/dynconf.h b/include/dynconf.h
@@ -53,6 +53,8 @@ typedef enum BanTarget { BAN_TARGET_IP=1, BAN_TARGET_USERIP=2, BAN_TARGET_HOST=3
 
 typedef enum HideIdleTimePolicy { HIDE_IDLE_TIME_NEVER=1, HIDE_IDLE_TIME_ALWAYS=2, HIDE_IDLE_TIME_USERMODE=3, HIDE_IDLE_TIME_OPER_USERMODE=4 } HideIdleTimePolicy;
 
+typedef enum LimitSVSCMDS { LIMIT_SVSCMDS_SERVERS=0, LIMIT_SVSCMDS_ULINES=1 } LimitSVSCMDS;
+
 /** The set { } block configuration */
 typedef struct Configuration Configuration;
 struct Configuration {
@@ -128,6 +130,7 @@ struct Configuration {
 	long spamfilter_detectslow_warn;
 	long spamfilter_detectslow_fatal;
 	int spamfilter_stop_on_first_match;
+	int spamfilter_utf8;
 	int maxbans;
 	int maxbanlength;
 	int watch_away_notification;
@@ -171,6 +174,7 @@ struct Configuration {
 	char *sasl_server;
 	int server_notice_colors;
 	int server_notice_show_event;
+	LimitSVSCMDS limit_svscmds;
 };
 
 extern MODVAR Configuration iConf;
diff --git a/include/fdlist.h b/include/fdlist.h
@@ -35,7 +35,7 @@ extern int fd_fileopen(const char *path, unsigned int flags);
 #define FD_SELECT_WRITE		0x2
 
 extern void fd_setselect(int fd, int flags, IOCallbackFunc iocb, void *data);
-extern void fd_select(time_t delay);		/* backend-specific */
+extern void fd_select(int delay);		/* backend-specific */
 extern void fd_refresh(int fd);			/* backend-specific */
 extern void fd_fork(); /* backend-specific */
 
diff --git a/include/h.h b/include/h.h
@@ -105,7 +105,6 @@ extern MODVAR ConfigItem_link		*conf_link;
 extern MODVAR ConfigItem_sni		*conf_sni;
 extern MODVAR ConfigItem_ban		*conf_ban;
 extern MODVAR ConfigItem_deny_channel  *conf_deny_channel;
-extern MODVAR ConfigItem_deny_link	*conf_deny_link;
 extern MODVAR ConfigItem_allow_channel *conf_allow_channel;
 extern MODVAR ConfigItem_deny_version	*conf_deny_version;
 extern MODVAR ConfigItem_alias		*conf_alias;
@@ -148,7 +147,7 @@ extern ConfigItem_listen *find_listen(const char *ipmask, int port, SocketType s
 extern ConfigItem_sni *find_sni(const char *name);
 extern ConfigItem_ulines	*find_uline(const char *host);
 extern ConfigItem_tld		*find_tld(Client *cptr);
-extern ConfigItem_link		*find_link(const char *servername, Client *acptr);
+extern ConfigItem_link		*find_link(const char *servername);
 extern ConfigItem_ban 		*find_ban(Client *, const char *host, short type);
 extern ConfigItem_ban 		*find_banEx(Client *,const char *host, short type, short type2);
 extern ConfigItem_vhost	*find_vhost(const char *name);
@@ -183,6 +182,7 @@ 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 MODVAR struct list_head rpc_remote_list;
 extern RealCommand *find_command(const char *cmd, int flags);
 extern RealCommand *find_command_simple(const char *cmd);
 extern Membership *find_membership_link(Membership *lp, Channel *ptr);
@@ -226,12 +226,15 @@ extern const char *extban_conv_param_nuh_or_extban(BanContext *b, Extban *extban
 extern const char *extban_conv_param_nuh(BanContext *b, Extban *extban);
 extern Ban *is_banned(Client *, Channel *, int, const char **, const char **);
 extern Ban *is_banned_with_nick(Client *, Channel *, int, const char *, const char **, const char **);
+extern int ban_exists(Ban *lst, const char *str);
+extern int ban_exists_ignore_time(Ban *lst, const char *str);
 
 extern Client *find_client(const char *, Client *);
 extern Client *find_name(const char *, Client *);
 extern Client *find_nickserv(const char *, Client *);
 extern Client *find_user(const char *, Client *);
 extern Client *find_server(const char *, Client *);
+extern Client *find_server_by_uid(const char *uid);
 extern Client *find_service(const char *, Client *);
 #define find_server_quick(x) find_server(x, NULL)
 extern char *find_or_add(char *);
@@ -283,6 +286,7 @@ extern void sendto_channel(Channel *channel, Client *from, Client *skip,
 extern void sendto_local_common_channels(Client *user, Client *skip,
                                          long clicap, MessageTag *mtags,
                                          FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,5,6)));
+extern void quit_sendto_local_common_channels(Client *user, MessageTag *mtags, const char *reason);
 extern void sendto_match_servs(Channel *, Client *, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
 extern void sendto_match_butone(Client *, Client *, const char *, int, MessageTag *,
     FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,6,7)));
@@ -312,8 +316,33 @@ extern void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...) __at
  * @endcode
  * @ingroup SendFunctions
  */
-#define sendnumeric(to, numeric, ...) sendnumericfmt(to, numeric, STR_ ## numeric, ##__VA_ARGS__)
-extern void sendnumericfmt(Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,3,4)));
+#define sendnumeric(to, numeric, ...) sendtaggednumericfmt(to, NULL, numeric, STR_ ## numeric, ##__VA_ARGS__)
+
+/** Send numeric message to a client - format to user specific needs.
+ * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
+ * followed by the pattern and format string you choose.
+ * @param to		The recipient
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
+#define sendnumericfmt(to, numeric, ...) sendtaggednumericfmt(to, NULL, numeric, __VA_ARGS__)
+
+/** Send numeric message to a client - format to user specific needs.
+ * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
+ * followed by the pattern and format string you choose.
+ * @param to		The recipient
+ * @param mtags     NULL, or NULL-terminated array of message tags
+ * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
+ * @param pattern	The format string / pattern to use.
+ * @param ...		Format string parameters.
+ * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
+ */
+#define sendtaggednumeric(to, mtags, numeric, ...) sendtaggednumericfmt(to, mtags, numeric, STR_ ## numeric, ##__VA_ARGS__)
+
+extern void sendtaggednumericfmt(Client *to, MessageTag *mtags, int numeric, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,4,5)));
+
 extern void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
 /** Build numeric message so it is ready to be sent to a client - rarely used, normally you use sendnumeric() instead.
  * This function is normally only used in eg CAN_KICK and CAN_SET_TOPIC, where
@@ -408,7 +437,13 @@ extern uint64_t siphash_raw(const char *in, size_t len, const char *k);
 extern uint64_t siphash_nocase(const char *in, const char *k);
 extern void siphash_generate_key(char *k);
 extern void init_hash(void);
-uint64_t hash_whowas_name(const char *name);
+extern void add_whowas_to_clist(WhoWas **, WhoWas *);
+extern void del_whowas_from_clist(WhoWas **, WhoWas *);
+extern void add_whowas_to_list(WhoWas **, WhoWas *);
+extern void del_whowas_from_list(WhoWas **, WhoWas *);
+extern uint64_t hash_whowas_name(const char *name);
+extern void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event);
+extern void free_whowas_fields(WhoWas *e);
 extern int add_to_client_hash_table(const char *, Client *);
 extern int del_from_client_hash_table(const char *, Client *);
 extern int add_to_id_hash_table(const char *, Client *);
@@ -454,6 +489,9 @@ extern MODVAR long SNO_SNOTICE;
 extern MODVAR long SNO_SPAMF;
 extern MODVAR long SNO_OPER;
 
+#ifndef HAVE_STRNLEN
+extern size_t strnlen(const char *s, size_t maxlen);
+#endif
 #ifndef HAVE_STRLCPY
 extern size_t strlcpy(char *dst, const char *src, size_t size);
 #endif
@@ -489,11 +527,11 @@ extern MODVAR RealCommand *CommandHash[256];
 extern void init_CommandHash(void);
 
 /* CRULE */
-char *crule_parse(char *);
-int crule_test(char *);
-char *crule_errstring(int);
-int crule_eval(char *);
-void crule_free(char **);
+extern struct CRuleNode* crule_parse(const char*);
+extern void crule_free(struct CRuleNode**);
+extern int crule_eval(struct CRuleNode* rule);
+extern int crule_test(const char *rule);
+extern const char *crule_errstring(int errcode);
 
 /*
  * Close all local socket connections, invalidate client fd's
@@ -664,11 +702,16 @@ extern void IRCToRTF(unsigned char *buffer, unsigned char *string);
 #endif
 extern void verify_opercount(Client *, const char *);
 extern int valid_host(const char *host, int strict);
+extern int valid_username(const char *username);
+extern int valid_vhost(const char *userhost);
 extern int count_oper_sessions(const char *);
 extern char *unreal_mktemp(const char *dir, const char *suffix);
 extern char *unreal_getpathname(const char *filepath, char *path);
 extern const char *unreal_getfilename(const char *path);
 extern const char *unreal_getmodfilename(const char *path);
+extern int unreal_create_directory_structure_for_file(const char *fname, mode_t mode);
+extern int unreal_create_directory_structure(const char *dname, mode_t mode);
+extern int unreal_mkdir(const char *pathname, mode_t mode);
 extern int unreal_copyfile(const char *src, const char *dest);
 extern int unreal_copyfileex(const char *src, const char *dest, int tryhardlink);
 extern time_t unreal_getfilemodtime(const char *filename);
@@ -704,8 +747,8 @@ extern int add_banid(Client *, Channel *, const char *);
 extern int add_exbanid(Client *cptr, Channel *channel, const char *banid);
 extern int sub1_from_channel(Channel *);
 extern MODVAR CoreChannelModeTable corechannelmodetable[];
-extern char *unreal_encodespace(char *s);
-extern char *unreal_decodespace(char *s);
+extern char *unreal_encodespace(const char *s);
+extern char *unreal_decodespace(const char *s);
 extern MODVAR Link *helpign;
 extern void reread_motdsandrules();
 extern MODVAR int SVSNOOP;
@@ -739,12 +782,14 @@ extern MODVAR int (*can_join)(Client *client, Channel *channel, const char *key,
 extern MODVAR void (*do_mode)(Channel *channel, Client *client, MessageTag *mtags, int parc, const char *parv[], time_t sendts, int samode);
 extern MODVAR MultiLineMode *(*set_mode)(Channel *channel, Client *cptr, int parc, const char *parv[], u_int *pcount,
                             char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
-extern MODVAR void (*set_channel_mode)(Channel *channel, char *modes, char *parameters);
+extern MODVAR void (*set_channel_mode)(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters);
+extern MODVAR void (*set_channel_topic)(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at);
 extern MODVAR void (*cmd_umode)(Client *, MessageTag *, int, const char **);
 extern MODVAR int (*register_user)(Client *client);
 extern MODVAR int (*tkl_hash)(unsigned int c);
 extern MODVAR char (*tkl_typetochar)(int type);
 extern MODVAR int (*tkl_chartotype)(char c);
+extern MODVAR char (*tkl_configtypetochar)(const char *name);
 extern MODVAR const char *(*tkl_type_string)(TKL *tk);
 extern MODVAR const char *(*tkl_type_config_string)(TKL *tk);
 extern MODVAR TKL *(*tkl_add_serverban)(int type, const char *usermask, const char *hostmask, const char *reason, const char *setby,
@@ -798,7 +843,6 @@ extern MODVAR void (*send_moddata_client)(Client *srv, Client *acptr);
 extern MODVAR void (*send_moddata_channel)(Client *srv, Channel *channel);
 extern MODVAR void (*send_moddata_members)(Client *srv);
 extern MODVAR void (*broadcast_moddata_client)(Client *acptr);
-extern MODVAR int (*check_banned)(Client *cptr, int exitflags);
 extern MODVAR void (*introduce_user)(Client *to, Client *acptr);
 extern MODVAR int (*check_deny_version)(Client *cptr, const char *software, int protocol, const char *flags);
 extern MODVAR int (*match_user)(const char *rmask, Client *acptr, int options);
@@ -810,6 +854,7 @@ extern MODVAR int (*do_remote_nick_name)(char *nick);
 extern MODVAR const char *(*charsys_get_current_languages)(void);
 extern MODVAR void (*broadcast_sinfo)(Client *acptr, Client *to, Client *except);
 extern MODVAR void (*connect_server)(ConfigItem_link *aconf, Client *by, struct hostent *hp);
+extern MODVAR int (*is_services_but_not_ulined)(Client *client);
 extern MODVAR void (*parse_message_tags)(Client *cptr, char **str, MessageTag **mtag_list);
 extern MODVAR const char *(*mtags_to_string)(MessageTag *m, Client *acptr);
 extern MODVAR int (*can_send_to_channel)(Client *cptr, Channel *channel, const char **msgtext, const char **errmsg, int notice);
@@ -818,6 +863,9 @@ extern MODVAR void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender,
 extern MODVAR int (*tkl_ip_hash)(const char *ip);
 extern MODVAR int (*tkl_ip_hash_type)(int type);
 extern MODVAR int (*find_tkl_exception)(int ban_type, Client *cptr);
+extern MODVAR int (*server_ban_parse_mask)(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+extern MODVAR int (*server_ban_exception_parse_mask)(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+extern MODVAR void (*tkl_added)(Client *client, TKL *tkl);
 extern MODVAR int (*del_silence)(Client *client, const char *mask);
 extern MODVAR int (*add_silence)(Client *client, const char *mask, int senderr);
 extern MODVAR int (*is_silenced)(Client *client, Client *acptr);
@@ -836,6 +884,22 @@ extern MODVAR char *(*get_chmodes_for_user)(Client *client, const char *flags);
 extern MODVAR WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name);
 extern MODVAR int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
 extern MODVAR int (*unreal_match_iplist)(Client *client, NameList *l);
+extern MODVAR void (*webserver_send_response)(Client *client, int status, char *msg);
+extern MODVAR void (*webserver_close_client)(Client *client);
+extern MODVAR int (*webserver_handle_body)(Client *client, WebRequest *web, const char *readbuf, int length);
+extern MODVAR void (*rpc_response)(Client *client, json_t *request, json_t *result);
+extern MODVAR void (*rpc_error)(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
+extern MODVAR void (*rpc_error_fmt)(Client *client, json_t *request, JsonRpcError error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5)));
+extern MODVAR void (*rpc_send_request_to_remote)(Client *source, Client *target, json_t *request);
+extern MODVAR void (*rpc_send_response_to_remote)(Client *source, Client *target, json_t *request);
+extern MODVAR int (*rrpc_supported_simple)(Client *target, char **problem_server);
+extern MODVAR int (*rrpc_supported)(Client *target, const char *module, const char *minimum_version, char **problem_server);
+extern MODVAR int (*websocket_handle_websocket)(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
+extern MODVAR int (*websocket_create_packet)(int opcode, char **buf, int *len);
+extern MODVAR int (*websocket_create_packet_ex)(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
+extern MODVAR int (*websocket_create_packet_simple)(int opcode, const char **buf, int *len);
+extern MODVAR const char *(*check_deny_link)(ConfigItem_link *link, int auto_connect);
+extern MODVAR void (*mtag_add_issued_by)(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
 /* /Efuncs */
 
 /* TLS functions */
@@ -872,6 +936,21 @@ extern int del_silence_default_handler(Client *client, const char *mask);
 extern int is_silenced_default_handler(Client *client, Client *acptr);
 extern void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized);
 extern int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
+extern void webserver_send_response_default_handler(Client *client, int status, char *msg);
+extern void webserver_close_client_default_handler(Client *client);
+extern int webserver_handle_body_default_handler(Client *client, WebRequest *web, const char *readbuf, int length);
+extern void rpc_response_default_handler(Client *client, json_t *request, json_t *result);
+extern void rpc_error_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
+extern void rpc_error_fmt_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...);
+extern void rpc_send_request_to_remote_default_handler(Client *source, Client *target, json_t *request);
+extern void rpc_send_response_to_remote_default_handler(Client *source, Client *target, json_t *response);
+extern int rrpc_supported_simple_default_handler(Client *target, char **problem_server);
+extern int rrpc_supported_default_handler(Client *target, const char *module, const char *minimum_version, char **problem_server);
+extern int websocket_handle_websocket_default_handler(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
+extern int websocket_create_packet_default_handler(int opcode, char **buf, int *len);
+extern int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
+extern int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len);
+extern void mtag_add_issued_by_default_handler(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
 /* End of default handlers for efunctions */
 
 extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules;
@@ -880,6 +959,7 @@ extern int add_listmode(Ban **list, Client *cptr, Channel *channel, const char *
 extern int add_listmode_ex(Ban **list, Client *cptr, Channel *channel, const char *banid, const char *setby, time_t seton);
 extern int del_listmode(Ban **list, Channel *channel, const char *banid);
 extern int Halfop_mode(long mode);
+extern const char *convert_regular_ban(char *mask, char *buf, size_t buflen);
 extern const char *clean_ban_mask(const char *, int, Client *, int);
 extern int find_invex(Channel *channel, Client *client);
 extern void DoMD5(char *mdout, const char *src, unsigned long n);
@@ -955,6 +1035,7 @@ extern int unix_sockets_capable(void);
 extern void init_winsock(void);
 #endif
 extern MODVAR Client *remote_rehash_client;
+extern MODVAR json_t *json_rehash_log;
 extern MODVAR int debugfd;
 extern void convert_to_absolute_path(char **path, const char *reldir);
 extern int has_user_mode(Client *acptr, char mode);
@@ -994,7 +1075,7 @@ extern int verify_certificate(SSL *ssl, const char *hostname, char **errstr);
 extern const char *certificate_name(SSL *ssl);
 extern void start_of_normal_client_handshake(Client *acptr);
 extern void clicap_pre_rehash(void);
-extern void clicap_post_rehash(void);
+extern void clicap_check_for_changes(void);
 extern void unload_all_unused_mtag_handlers(void);
 extern void send_cap_notify(int add, const char *token);
 extern void sendbufto_one(Client *to, char *msg, unsigned int quick);
@@ -1002,6 +1083,7 @@ extern MODVAR int current_serial;
 extern const char *spki_fingerprint(Client *acptr);
 extern const char *spki_fingerprint_ex(X509 *x509_cert);
 extern int is_module_loaded(const char *name);
+extern int is_blacklisted_module(const char *name);
 extern void close_std_descriptors(void);
 extern void banned_client(Client *acptr, const char *bantype, const char *reason, int global, int noexit);
 extern char *mystpcpy(char *dst, const char *src);
@@ -1093,6 +1175,7 @@ extern int hide_idle_time(Client *client, Client *target);
 extern void lost_server_link(Client *serv, const char *tls_error_string);
 extern const char *sendtype_to_cmd(SendType sendtype);
 extern MODVAR MessageTagHandler *mtaghandlers;
+extern MODVAR RPCHandler *rpchandlers;
 #define nv_find_by_name(stru, name)       do_nv_find_by_name(stru, name, ARRAY_SIZEOF((stru)))
 extern long do_nv_find_by_name(NameValue *table, const char *cmd, int numelements);
 #define nv_find_by_value(stru, value)       do_nv_find_by_value(stru, value, ARRAY_SIZEOF((stru)))
@@ -1111,6 +1194,7 @@ extern void add_fmt_nvplist(NameValuePrioList **lst, int priority, const char *n
 #define add_nvplist_numeric(lst, priority, name, to, numeric, ...) add_nvplist_numeric_fmt(lst, priority, name, to, numeric, STR_ ## numeric, ##__VA_ARGS__)
 extern void add_nvplist_numeric_fmt(NameValuePrioList **lst, int priority, const char *name, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,6,7)));
 extern NameValuePrioList *find_nvplist(NameValuePrioList *list, const char *name);
+extern const char *get_nvplist(NameValuePrioList *list, const char *name);
 extern void free_nvplist(NameValuePrioList *lst);
 extern void unreal_add_name_values(NameValuePrioList **n, const char *name, ConfigEntry *ce);
 extern const char *namevalue(NameValuePrioList *n);
@@ -1127,7 +1211,21 @@ extern void skip_whitespace(char **p);
 extern void read_until(char **p, char *stopchars);
 extern int is_ip_valid(const char *ip);
 extern int is_file_readable(const char *file, const char *dir);
-json_t *json_string_unreal(const char *s);
+/* json.c */
+extern int log_json_filter;
+extern json_t *json_string_unreal(const char *s);
+extern const char *json_object_get_string(json_t *j, const char *name);
+extern int json_object_get_integer(json_t *j, const char *name, int default_value);
+extern int json_object_get_boolean(json_t *j, const char *name, int default_value);
+extern json_t *json_timestamp(time_t v);
+extern const char *timestamp_iso8601_now(void);
+extern const char *timestamp_iso8601(time_t v);
+extern const char *json_get_value(json_t *t);
+extern void json_expand_client(json_t *j, const char *key, Client *client, int detail);
+extern void json_expand_client_security_groups(json_t *parent, Client *client);
+extern void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail);
+extern void json_expand_tkl(json_t *j, const char *key, TKL *tkl, int detail);
+/* end of json.c */
 /* securitygroup.c start */
 extern MODVAR SecurityGroup *securitygroups;
 extern void unreal_delete_masks(ConfigItem_mask *m);
@@ -1202,7 +1300,7 @@ extern const char *log_type_valtostring(LogType v);
 #endif
 extern void do_unreal_log(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, const char *msg, ...) __attribute__((format(printf,5,0)));
 extern void do_unreal_log_raw(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, const char *msg, ...);
-extern void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, Client *from_server);
+extern void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server);
 extern LogData *log_data_string(const char *key, const char *str);
 extern LogData *log_data_char(const char *key, const char c);
 extern LogData *log_data_integer(const char *key, int64_t integer);
@@ -1219,11 +1317,15 @@ extern int log_tests(void);
 extern void config_pre_run_log(void);
 extern void log_blocks_switchover(void);
 extern void postconf_defaults_log_block(void);
+extern int valid_loglevel(int v);
 extern LogLevel log_level_stringtoval(const char *str);
 extern const char *log_level_valtostring(LogLevel loglevel);
 extern LogLevel log_level_stringtoval(const char *str);
 extern int valid_event_id(const char *s);
 extern int valid_subsystem(const char *s);
+extern LogSource *add_log_source(const char *str);
+extern void free_log_sources(LogSource *l);
+extern int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsystem, const char *event_id, int matched_already);
 extern const char *timestamp_iso8601_now(void);
 extern const char *timestamp_iso8601(time_t v);
 extern int is_valid_snomask(char c);
@@ -1244,11 +1346,13 @@ extern const char *displayurl(const char *url);
 extern char *url_getfilename(const char *url);
 extern void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects);
 extern void url_init(void);
+extern void url_cancel_handle_by_callback_data(void *ptr);
 extern EVENT(url_socket_timeout);
 /* end of url stuff */
 extern char *collapse(char *pattern);
 extern void clear_scache_hash_table(void);
 extern void sendto_one(Client *, MessageTag *mtags, FORMAT_STRING(const char *), ...) __attribute__((format(printf,3,4)));
+extern void mark_data_to_send(Client *to);
 extern EVENT(garbage_collect);
 extern EVENT(loop_event);
 extern EVENT(check_pings);
@@ -1270,5 +1374,8 @@ 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);
 extern long get_connected_time(Client *client);
+extern time_t get_creationtime(Client *client);
 extern const char *StripControlCodes(const char *text);
 extern const char *StripControlCodesEx(const char *text, char *output, size_t outputlen, int strip_flags);
+extern MODVAR Module *Modules;
+extern const char *command_issued_by_rpc(MessageTag *mtags);
diff --git a/include/modules.h b/include/modules.h
@@ -22,7 +22,7 @@
 #define MODULES_H
 #include "types.h"
 #define MAXCUSTOMHOOKS  30
-#define MAXHOOKTYPES	150
+#define MAXHOOKTYPES	200
 #define MAXCALLBACKS	30
 #define MAXEFUNCTIONS	128
 #if defined(_WIN32)
@@ -107,6 +107,7 @@ typedef enum ModuleObjectType {
 	MOBJ_CLICAP = 16,
 	MOBJ_MTAG = 17,
 	MOBJ_HISTORY_BACKEND = 18,
+	MOBJ_RPC = 19,
 } ModuleObjectType;
 
 typedef struct Umode Umode;
@@ -284,10 +285,13 @@ struct Cmode {
 
 	/** Free and remove parameter from list.
 	 * This function pointer is NULL (unused) for modes without parameters.
-	 * @param parastruct		The parameter struct
+	 * @param parastruct	The parameter struct
+	 * @param soft		This is set to 1 if you may 'resist freeing'
+	 *			(used by floodprot module to have active F profile even if -F).
+	 * @returns Normally return 0, must return 1 if it 'resisted' freeing.
 	 * @note In most cases you will just call safe_free() on 'list'
 	 */
-	void (*free_param)(void *parastruct);
+	int (*free_param)(void *parastruct, int soft);
 
 	/** duplicate a struct and return a pointer to duplicate.
 	 * This function pointer is NULL (unused) for modes without parameters.
@@ -317,6 +321,11 @@ struct Cmode {
 	/** Unsetting also eats/requires a parameter. Unusual, but possible. */
 	char unset_with_param;
 
+	/** Is this mode available for chanmode +f, and if so for which flood type?
+	 * eg 'j' for join flood.
+	 */
+	char flood_type_action;
+
 	/** Is this mode being unloaded?
 	 * This is set to 1 if the chanmode module providing this mode is unloaded
 	 * and we are waiting to see if in our new round of loads a "new" chanmode
@@ -324,7 +333,7 @@ struct Cmode {
 	 * should never be 0 outside an internal rehash.
 	 */
 	char unloaded;
-	
+
 	/** Slot number - Can be used instead of GETPARAMSLOT() */
 	int param_slot;
 	
@@ -346,11 +355,12 @@ typedef struct {
 	void *		(*put_param)(void *, const char *);
 	const char *	(*get_param)(void *);
 	const char *	(*conv_param)(const char *, Client *, Channel *);
-	void		(*free_param)(void *);
+	int		(*free_param)(void *, int);
 	void *		(*dup_struct)(void *);
 	int		(*sjoin_check)(Channel *, void *, void *);
 	char		local;
 	char		unset_with_param;
+	char		flood_type_action;
 } CmodeInfo;
 
 /** Get a slot number for a param - eg GETPARAMSLOT('k') */
@@ -433,7 +443,7 @@ struct Extban {
 
 	int (*is_ok)(BanContext *b);
 
-	/** Convert input parameter to output [optional].
+	/** Convert input parameter to output.
 	 * like with normal bans '+b blah' gets '+b blah!*@*', and it allows
 	 * you to limit the length of the ban too.
 	 * return value: pointer to output string (temp. storage)
@@ -604,6 +614,49 @@ typedef struct {
 	int (*history_destroy)(const char *object);
 } HistoryBackendInfo;
 
+/** @defgroup RPCAPI RPC API
+ * @{
+ */
+
+/** No special flags set */
+#define RPC_HANDLER_FLAGS_NONE			0x0
+#define RPC_HANDLER_FLAGS_UNFILTERED		0x1	/**< Don't filter input (don't reject strings bigger than 510 in length or containing \r or \n) */
+
+/** RPC Tag Handler */
+typedef struct RPCHandler RPCHandler;
+struct RPCHandler {
+	RPCHandler *prev, *next;
+	char *method;                                             /**< Name of the method handler, eg "client.get" */
+	int flags;                                                /**< A flag of RPC_HANDLER_FLAG_* */
+	LogLevel loglevel;                                        /**< Log level to use for this call: for example ULOG_DEBUG for .list calls, leave 0 for default */
+	void (*call)(Client *, json_t *request, json_t *params);  /**< RPC call: use RPC_CALL_FUNC() ! */
+	Module *owner;                                            /**< Module introducing this. */
+	char unloaded;                                            /**< Internal flag to indicate module is being unloaded */
+};
+
+/** The struct used to register a RPC handler.
+ * For documentation, see the RPCHandler struct.
+ */
+typedef struct {
+	char *method;
+	int flags;
+	LogLevel loglevel;
+	void (*call)(Client *, json_t *request, json_t *params);
+} RPCHandlerInfo;
+
+/** RPC function - used by all RPC call functions.
+ * This is used in the code like <pre>RPC_CALL_FUNC(rpc_call_xyz)</pre> as a function definition.
+ * It allows the UnrealIRCd devs to add or change parameters to the function without
+ * (necessarily) breaking your code.
+ * @param client      The client issueing the request
+ * @param request     The full JSON-RPC request
+ * @param params      Parameters of the JSON-RPC call
+ * @note You are expected to call rpc_response() or rpc_error() on the request.
+ */
+#define RPC_CALL_FUNC(x) void (x) (Client *client, json_t *request, json_t *params)
+
+/** @} */
+
 struct Hook {
 	Hook *prev, *next;
 	int priority;
@@ -688,6 +741,7 @@ typedef struct ModuleObject {
 		ClientCapability *clicap;
 		MessageTagHandler *mtag;
 		HistoryBackend *history_backend;
+		RPCHandler *rpc;
 	} object;
 } ModuleObject;
 
@@ -735,9 +789,15 @@ struct Module
 #define MOD_OPT_OFFICIAL	0x0002 /* Official module, do not set "tainted" */
 #define MOD_OPT_PERM_RELOADABLE	0x0004 /* Module is semi-permanent: it can be re-loaded but not un-loaded */
 #define MOD_OPT_GLOBAL		0x0008 /* Module is required to be loaded globally (i.e. across the entire network) */
-#define MOD_OPT_UNLOAD_PRIORITY	0x1000 /* Module wants a higher or lower unload priority */
+#define MOD_OPT_PRIORITY	0x1000 /* Module wants a higher or lower priority for unloading, init, load, etc */
+#define MOD_OPT_UNLOAD_PRIORITY	0x1000 /* Alias for MOD_OPT_PRIORITY */
 #define MOD_Dep(name, container,module) {#name, (vFP *) &container, module}
 
+/** Websocket module should init 'first' because it handles sockets */
+#define WEBSOCKET_MODULE_PRIORITY_INIT		-1000000000
+/** Websocket module should unload 'last' because it handles sockets */
+#define WEBSOCKET_MODULE_PRIORITY_UNLOAD	1000000000
+
 /** Event structs */
 struct Event {
 	Event		*prev;		/**< Previous event (linked list) */
@@ -825,6 +885,10 @@ extern HistoryBackend *HistoryBackendFind(const char *name);
 extern HistoryBackend *HistoryBackendAdd(Module *module, HistoryBackendInfo *mreq);
 extern void HistoryBackendDel(HistoryBackend *m);
 
+extern RPCHandler *RPCHandlerFind(const char *method);
+extern RPCHandler *RPCHandlerAdd(Module *module, RPCHandlerInfo *mreq);
+extern void RPCHandlerDel(RPCHandler *m);
+
 #ifndef GCC_TYPECHECKING
 #define HookAdd(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, func, NULL, NULL, NULL)
 #define HookAddVoid(module, hooktype, priority, func) HookAddMain(module, hooktype, priority, NULL, func, NULL, NULL)
@@ -909,6 +973,11 @@ extern int CommandExists(const char *name);
 extern CommandOverride *CommandOverrideAdd(Module *module, const char *name, int priority, OverrideCmdFunc func);
 extern void CommandOverrideDel(CommandOverride *ovr);
 extern void CallCommandOverride(CommandOverride *ovr, Client *client, MessageTag *mtags, int parc, const char *parv[]);
+/** Call next command override function - easy way to do it.
+ * This way you don't have to call CallCommandOverride() with the right arguments.
+ * Which is nice because command (override) arguments may change in future UnrealIRCd versions.
+ */
+#define CALL_NEXT_COMMAND_OVERRIDE()	CallCommandOverride(ovr, client, recv_mtags, parc, parv)
 
 extern void moddata_free_client(Client *acptr);
 extern void moddata_free_local_client(Client *acptr);
@@ -937,6 +1006,11 @@ extern int LoadPersistentLongX(ModuleInfo *modinfo, const char *varshortname, lo
 extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, long var);
 #define SavePersistentLong(modinfo, var) SavePersistentLongX(modinfo, #var, var)
 
+extern int LoadPersistentLongLongX(ModuleInfo *modinfo, const char *varshortname, long long *var);
+#define LoadPersistentLongLong(modinfo, var) LoadPersistentLongLongX(modinfo, #var, &var)
+extern void SavePersistentLongLongX(ModuleInfo *modinfo, const char *varshortname, long long var);
+#define SavePersistentLongLong(modinfo, var) SavePersistentLongLongX(modinfo, #var, var)
+
 /** Hooks trigger on "events", such as a new user connecting or joining a channel,
  * see https://www.unrealircd.org/docs/Dev:Hook_API for background info.
  * You are suggested to use CTRL+F on this page to search for any useful hook,
@@ -1170,6 +1244,12 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, l
 #define HOOKTYPE_JSON_EXPAND_CLIENT_SERVER	114
 /** See hooktype_json_expand_channel() */
 #define HOOKTYPE_JSON_EXPAND_CHANNEL	115
+/** See hooktype_accept() */
+#define HOOKTYPE_ACCEPT		116
+/** See hooktype_pre_local_handshake_timeout */
+#define HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT	117
+/** See hooktype_rehash_log */
+#define HOOKTYPE_REHASH_LOG	118
 
 /* Adding a new hook here?
  * 1) Add the #define HOOKTYPE_.... with a new number
@@ -1759,11 +1839,12 @@ int hooktype_tkl_del(Client *client, TKL *tkl);
  * @param subsystem		Subsystem (eg "operoverride")
  * @param event_id		Event ID (eg "SAJOIN_COMMAND")
  * @param msg			Message(s) in text form
- * @param json_serialized	The associated JSON text
+ * @param json			The JSON log entry
+ * @param json_serialized	The serialized JSON log entry (as a string)
  * @param timebuf		The [xxxx] time buffer, for convenience
  * @return The return value is ignored (use return 0)
  */
-int hooktype_log(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized, const char *timebuf);
+int hooktype_log(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
 
 /** Called when a local user matches a spamfilter (function prototype for HOOKTYPE_LOCAL_SPAMFILTER).
  * @param client		The client
@@ -1821,6 +1902,18 @@ int hooktype_packet(Client *from, Client *to, Client *intended_to, char **msg, i
  */
 int hooktype_handshake(Client *client);
 
+/** Called very early when a client connects (function prototype for HOOKTYPE_ACCEPT).
+ * Module coders: have a look at hooktype_handshake() instead of this one!
+ * HOOKTYPE_ACCEPT is called even before HOOKTYPE_HANDSHAKE, as soon as the socket
+ * is connected and during the client is being set up, before the SSL/TLS handshake.
+ * It is only used for connection flood detection and checking (G)Z-lines.
+ * Note that this connection is also called for *NIX domain socket connections,
+ * HTTP(S) requests, and so on.
+ * @param client		The client
+ * @return One of HOOK_*. Use HOOK_DENY to reject the client.
+ */
+int hooktype_accept(Client *client);
+
 /** Called when a client structure is freed (function prototype for HOOKTYPE_FREE_CLIENT).
  * @param client		The client
  * @note Normally you use hooktype_local_quit(), hooktype_remote_quit() and hooktype_unkuser_quit() for this.
@@ -2200,6 +2293,24 @@ int hooktype_json_expand_client_server(Client *client, int detail, json_t *j, js
  */
 int hooktype_json_expand_channel(Channel *channel, int detail, json_t *j);
 
+/** Called when a local user is about to be disconnected due to a registration timeout,
+ * allows changing the disconnect reason (function prototype for HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT).
+ * This is used by the authprompt module.
+ * @param client		The client
+ * @param comment		The quit/disconnect reason (can be changed by you)
+ * @retval HOOK_CONTINUE	Continue as normal
+ * @retval HOOK_ALLOW		Do not exit the user due to a handshake timeout
+ */
+int hooktype_pre_local_handshake_timeout(Client *client, const char **comment);
+
+/** Called when a REHASH completed (either succesfully or with a failure).
+ * This gives the full rehash log. Used by the JSON-RPC interface.
+ * @param failure		Set to 1 if the rehash failed, otherwise 0.
+ * @param t			The JSON object containing the rehash log and other information.
+ * @return The return value is ignored (use return 0)
+ */
+int hooktype_rehash_log(int failure, json_t *rehash_log);
+
 /** @} */
 
 #ifdef GCC_TYPECHECKING
@@ -2318,7 +2429,9 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum
         ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT) && !ValidateHook(hooktype_json_expand_client, func)) || \
         ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT_USER) && !ValidateHook(hooktype_json_expand_client_user, func)) || \
         ((hooktype == HOOKTYPE_JSON_EXPAND_CLIENT_SERVER) && !ValidateHook(hooktype_json_expand_client_server, func)) || \
-        ((hooktype == HOOKTYPE_JSON_EXPAND_CHANNEL) && !ValidateHook(hooktype_json_expand_channel, func)) ) \
+        ((hooktype == HOOKTYPE_JSON_EXPAND_CHANNEL) && !ValidateHook(hooktype_json_expand_channel, func)) || \
+        ((hooktype == HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT) && !ValidateHook(hooktype_pre_local_handshake_timeout, func)) || \
+        ((hooktype == HOOKTYPE_REHASH_LOG) && !ValidateHook(hooktype_rehash_log, func)) ) \
         _hook_error_incompatible();
 #endif /* GCC_TYPECHECKING */
 
@@ -2350,6 +2463,7 @@ enum EfunctionType {
 	EFUNC_DO_MODE,
 	EFUNC_SET_MODE,
 	EFUNC_SET_CHANNEL_MODE,
+	EFUNC_SET_CHANNEL_TOPIC,
 	EFUNC_CMD_UMODE,
 	EFUNC_REGISTER_USER,
 	EFUNC_TKL_HASH,
@@ -2382,7 +2496,6 @@ enum EfunctionType {
 	EFUNC_BROADCAST_MD_CHANNEL,
 	EFUNC_BROADCAST_MD_MEMBER,
 	EFUNC_BROADCAST_MD_MEMBERSHIP,
-	EFUNC_CHECK_BANNED,
 	EFUNC_INTRODUCE_USER,
 	EFUNC_CHECK_DENY_VERSION,
 	EFUNC_BROADCAST_MD_CLIENT_CMD,
@@ -2404,9 +2517,11 @@ enum EfunctionType {
 	EFUNC_CHARSYS_GET_CURRENT_LANGUAGES,
 	EFUNC_BROADCAST_SINFO,
 	EFUNC_CONNECT_SERVER,
+	EFUNC_IS_SERVICES_BUT_NOT_ULINED,
 	EFUNC_PARSE_MESSAGE_TAGS,
 	EFUNC_MTAGS_TO_STRING,
 	EFUNC_TKL_CHARTOTYPE,
+	EFUNC_TKL_CONFIGTYPETOCHAR,
 	EFUNC_TKL_TYPE_STRING,
 	EFUNC_TKL_TYPE_CONFIG_STRING,
 	EFUNC_CAN_SEND_TO_CHANNEL,
@@ -2426,6 +2541,9 @@ enum EfunctionType {
 	EFUNC_FIND_TKL_NAMEBAN,
 	EFUNC_FIND_TKL_SPAMFILTER,
 	EFUNC_FIND_TKL_EXCEPTION,
+	EFUNC_SERVER_BAN_PARSE_MASK,
+	EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK,
+	EFUNC_TKL_ADDED,
 	EFUNC_ADD_SILENCE,
 	EFUNC_DEL_SILENCE,
 	EFUNC_IS_SILENCED,
@@ -2444,6 +2562,22 @@ enum EfunctionType {
 	EFUNC_WHOIS_GET_POLICY,
 	EFUNC_MAKE_OPER,
 	EFUNC_UNREAL_MATCH_IPLIST,
+	EFUNC_WEBSERVER_SEND_RESPONSE,
+	EFUNC_WEBSERVER_CLOSE_CLIENT,
+	EFUNC_WEBSERVER_HANDLE_BODY,
+	EFUNC_RPC_RESPONSE,
+	EFUNC_RPC_ERROR,
+	EFUNC_RPC_ERROR_FMT,
+	EFUNC_RPC_SEND_REQUEST_TO_REMOTE,
+	EFUNC_RPC_SEND_RESPONSE_TO_REMOTE,
+	EFUNC_RRPC_SUPPORTED,
+	EFUNC_RRPC_SUPPORTED_SIMPLE,
+	EFUNC_WEBSOCKET_HANDLE_WEBSOCKET,
+	EFUNC_WEBSOCKET_CREATE_PACKET,
+	EFUNC_WEBSOCKET_CREATE_PACKET_EX,
+	EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE,
+	EFUNC_CHECK_DENY_LINK,
+	EFUNC_MTAG_GENERATE_ISSUED_BY_IRC,
 };
 
 /* Module flags */
diff --git a/include/numeric.h b/include/numeric.h
@@ -59,6 +59,7 @@
 #define ERR_NORECIPIENT      411
 #define ERR_NOTEXTTOSEND     412
 #define ERR_TOOMANYMATCHES   416
+#define ERR_INPUTTOOLONG     417
 
 #define ERR_UNKNOWNCOMMAND   421
 #define	ERR_NOMOTD           422
@@ -482,6 +483,7 @@
 #define STR_ERR_NORECIPIENT		/* 411 */	":No recipient given (%s)"
 #define STR_ERR_NOTEXTTOSEND		/* 412 */	":No text to send"
 #define STR_ERR_TOOMANYMATCHES		/* 416 */	"%s :%s"
+#define STR_ERR_INPUTTOOLONG		/* 417 */	":Input line was too long"
 #define STR_ERR_UNKNOWNCOMMAND		/* 421 */	"%s :Unknown command"
 #define STR_ERR_NOMOTD			/* 422 */	":MOTD File is missing"
 #define STR_ERR_NOADMININFO		/* 423 */	"%s :No administrative info available"
diff --git a/include/setup.h.in b/include/setup.h.in
@@ -43,6 +43,9 @@
 /* Define if ssl library has SSL_CTX_set_security_level */
 #undef HAS_SSL_CTX_SET_SECURITY_LEVEL
 
+/* Define if ssl library has X509_check_host */
+#undef HAS_X509_check_host
+
 /* Define if ssl library has X509_get0_notAfter */
 #undef HAS_X509_get0_notAfter
 
@@ -76,9 +79,6 @@
 /* Define to 1 if you have the `kqueue' function. */
 #undef HAVE_KQUEUE
 
-/* Define to 1 if you have the <memory.h> header file. */
-#undef HAVE_MEMORY_H
-
 /* Define to 1 if you have the `poll' function. */
 #undef HAVE_POLL
 
@@ -100,6 +100,9 @@
 /* Define to 1 if you have the <stdint.h> header file. */
 #undef HAVE_STDINT_H
 
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
 /* Define to 1 if you have the <stdlib.h> header file. */
 #undef HAVE_STDLIB_H
 
@@ -121,6 +124,9 @@
 /* Define to 1 if you have the `strlncpy' function. */
 #undef HAVE_STRLNCPY
 
+/* Define to 1 if you have the `strnlen' function. */
+#undef HAVE_STRNLEN
+
 /* Define to 1 if you have the `syslog' function. */
 #undef HAVE_SYSLOG
 
@@ -201,7 +207,9 @@
    tofail) */
 #undef STATIC_LINKING
 
-/* Define to 1 if you have the ANSI C header files. */
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+   required in a freestanding environment). This macro is provided for
+   backward compatibility; new code need not use it. */
 #undef STDC_HEADERS
 
 /* Define if you have the <sys/syslog.h> header file. */
diff --git a/include/struct.h b/include/struct.h
@@ -39,6 +39,9 @@
 #include <openssl/rand.h>
 #include <openssl/md5.h>
 #include <openssl/ripemd.h>
+#ifdef HAS_X509_check_host
+#include <openssl/x509v3.h>
+#endif
 #include <jansson.h>
 #include "common.h"
 #include "sys.h"
@@ -104,7 +107,6 @@ typedef struct ConfigItem_vhost ConfigItem_vhost;
 typedef struct ConfigItem_link	ConfigItem_link;
 typedef struct ConfigItem_ban ConfigItem_ban;
 typedef struct ConfigItem_deny_dcc ConfigItem_deny_dcc;
-typedef struct ConfigItem_deny_link ConfigItem_deny_link;
 typedef struct ConfigItem_deny_channel ConfigItem_deny_channel;
 typedef struct ConfigItem_deny_version ConfigItem_deny_version;
 typedef struct ConfigItem_alias ConfigItem_alias;
@@ -128,6 +130,7 @@ typedef struct LocalClient LocalClient;
 typedef struct Channel Channel;
 typedef struct User User;
 typedef struct Server Server;
+typedef struct RPCClient RPCClient;
 typedef struct Link Link;
 typedef struct Ban Ban;
 typedef struct Mode Mode;
@@ -179,7 +182,9 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
 #define	KEYLEN		23
 #define LINKLEN		32
 #define	BUFSIZE		512	/* WARNING: *DONT* CHANGE THIS!!!! */
-#define READBUFSIZE	8192	/* for the read buffer */
+#define MAXTAGSIZE	8192	/**< Maximum length of message tags (4K user + 4K server) */
+#define MAXLINELENGTH	(MAXTAGSIZE+BUFSIZE)	/**< Maximum length of a line on IRC: 4k client tags + 4k server tags + 512 bytes (IRCv3) */
+#define READBUFSIZE	MAXLINELENGTH	/* for the read buffer */
 #define	MAXRECIPIENTS 	20
 #define	MAXSILELENGTH	NICKLEN+USERLEN+HOSTLEN+10
 #define IDLEN		12
@@ -323,9 +328,10 @@ 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_RPC			= -10,	/**< RPC Client (either local or remote) */
+	CLIENT_STATUS_CONTROL			= -9,	/**< Client is on the control channel */
+	CLIENT_STATUS_LOG			= -8,	/**< Client is a log file */
+	CLIENT_STATUS_TLS_STARTTLS_HANDSHAKE	= -7,	/**< Client is doing a STARTTLS handshake */
 	CLIENT_STATUS_CONNECTING		= -6,	/**< Client is an outgoing connect */
 	CLIENT_STATUS_TLS_CONNECT_HANDSHAKE	= -5,	/**< Client is doing an TLS handshake - outgoing connection */
 	CLIENT_STATUS_TLS_ACCEPT_HANDSHAKE	= -4,	/**< Client is doing an TLS handshake - incoming connection */
@@ -346,7 +352,8 @@ 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	IsControl(x)		((x)->status == CLIENT_STATUS_CONTROL)	/**< Is on the control channel (not an IRC client) */
+#define	IsRPC(x)		((x)->status == CLIENT_STATUS_RPC)	/**< Is doing RPC (not an IRC client) */
 #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 */
@@ -364,6 +371,7 @@ typedef enum ClientStatus {
 #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	SetRPC(x)		((x)->status = CLIENT_STATUS_RPC)
 #define	SetUser(x)		((x)->status = CLIENT_STATUS_USER)
 
 /** @} */
@@ -403,6 +411,7 @@ typedef enum ClientStatus {
 #define CLIENT_FLAG_PINGWARN		0x10000000	/**< Server ping warning (remote server slow with responding to PINGs) */
 #define CLIENT_FLAG_NOHANDSHAKEDELAY	0x20000000	/**< No handshake delay */
 #define CLIENT_FLAG_SERVER_DISCONNECT_LOGGED	0x40000000	/**< Server disconnect message is (already) logged */
+#define CLIENT_FLAG_ASYNC_RPC			0x80000000	/**< Asynchronous remote RPC request - special case for rehash etc. */
 
 /** @} */
 
@@ -493,8 +502,10 @@ typedef enum ClientStatus {
 #define IsTLS(x)			((x)->flags & CLIENT_FLAG_TLS)
 #define IsSecure(x)			((x)->flags & CLIENT_FLAG_TLS)
 #define IsULine(x)			((x)->flags & CLIENT_FLAG_ULINE)
+#define IsSvsCmdOk(x)			(((x)->flags & CLIENT_FLAG_ULINE) || ((iConf.limit_svscmds == LIMIT_SVSCMDS_SERVERS) && (IsServer((x)) || IsMe((x)))))
 #define IsVirus(x)			((x)->flags & CLIENT_FLAG_VIRUS)
 #define IsIdentLookupSent(x)		((x)->flags & CLIENT_FLAG_IDENTLOOKUPSENT)
+#define IsAsyncRPC(x)			((x)->flags & CLIENT_FLAG_ASYNC_RPC)
 #define SetIdentLookup(x)		do { (x)->flags |= CLIENT_FLAG_IDENTLOOKUP; } while(0)
 #define SetClosing(x)			do { (x)->flags |= CLIENT_FLAG_CLOSING; } while(0)
 #define SetDCCBlock(x)			do { (x)->flags |= CLIENT_FLAG_DCCBLOCK; } while(0)
@@ -526,6 +537,7 @@ typedef enum ClientStatus {
 #define SetULine(x)			do { (x)->flags |= CLIENT_FLAG_ULINE; } while(0)
 #define SetVirus(x)			do { (x)->flags |= CLIENT_FLAG_VIRUS; } while(0)
 #define SetIdentLookupSent(x)		do { (x)->flags |= CLIENT_FLAG_IDENTLOOKUPSENT; } while(0)
+#define SetAsyncRPC(x)			do { (x)->flags |= CLIENT_FLAG_ASYNC_RPC; } while(0)
 #define ClearIdentLookup(x)		do { (x)->flags &= ~CLIENT_FLAG_IDENTLOOKUP; } while(0)
 #define ClearClosing(x)			do { (x)->flags &= ~CLIENT_FLAG_CLOSING; } while(0)
 #define ClearDCCBlock(x)		do { (x)->flags &= ~CLIENT_FLAG_DCCBLOCK; } while(0)
@@ -556,12 +568,13 @@ 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 ClearAsyncRPC(x)		do { (x)->flags &= ~CLIENT_FLAG_ASYNC_RPC; } 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)
 #define SetUnixSocket(x)			do { (x)->local->socket_type = SOCKET_TYPE_UNIX; } while(0)
-/** @} */
-
 
 /* Others that access client structs: */
 #define	IsNotSpoof(x)	((x)->local->nospoof == 0)
@@ -788,6 +801,20 @@ struct MOTDLine {
 	struct MOTDLine *next;
 };
 
+/** Current status of configuration in memory (what stage are we in..) */
+typedef enum ConfigStatus {
+	CONFIG_STATUS_NONE = 0,		/**< Config files have not been parsed yet */
+	CONFIG_STATUS_TEST = 1,		/**< Currently running MOD_TEST() */
+	CONFIG_STATUS_POSTTEST = 2,	/**< Currently running post_config_test hooks */
+	CONFIG_STATUS_PRE_INIT = 3,	/**< In-between */
+	CONFIG_STATUS_INIT = 4,		/**< Currently running MOD_INIT() */
+	CONFIG_STATUS_RUN_CONFIG = 5,	/**< Currently running CONFIG_RUN hooks */
+	CONFIG_STATUS_LOAD = 6,		/**< Currently running MOD_LOAD() */
+	CONFIG_STATUS_POSTLOAD = 7,	/**< Doing post-load stuff like activating listeners */
+	CONFIG_STATUS_COMPLETE = 8,	/**< Load or rehash complete */
+	CONFIG_STATUS_ROLLBACK = 99,	/**< Configuration failed, rolling back changes */
+} ConfigStatus;
+
 struct LoopStruct {
 	unsigned do_garbage_collect : 1;
 	unsigned config_test : 1;
@@ -801,6 +828,7 @@ struct LoopStruct {
 	unsigned rehash_download_busy : 1; /* don't return "all downloads complete", needed for race condition */
 	unsigned tainted : 1;
 	int rehashing;
+	ConfigStatus config_status;
 	Client *rehash_save_client;
 	void (*boot_function)();
 };
@@ -826,10 +854,15 @@ typedef struct Whowas {
 	char *username;
 	char *hostname;
 	char *virthost;
+	char *ip;
 	char *servername;
 	char *realname;
+	char *account;
 	long umodes;
-	time_t   logoff;
+	time_t logon;
+	time_t logoff;
+	time_t connected_since;
+	WhoWasEvent event;
 	struct Client *online;	/* Pointer to new nickname for chasing or NULL */
 	struct Whowas *next;	/* for hash table... */
 	struct Whowas *prev;	/* for hash table... */
@@ -873,8 +906,9 @@ struct SWhois {
 
 /** Command function - used by all command handlers.
  * This is used in the code like <pre>CMD_FUNC(cmd_yourcmd)</pre> as a function definition.
- * @param cptr        The client direction pointer.
- * @param client        The source client pointer (you usually need this one).
+ * It allows UnrealIRCd devs to change the parameters in the function without
+ * (necessarily) breaking your code.
+ * @param client      The client
  * @param recv_mtags  Received message tags for this command.
  * @param parc        Parameter count *plus* 1.
  * @param parv        Parameter values.
@@ -885,6 +919,15 @@ struct SWhois {
  *        E.g. parv[3] in the above example is out of bounds.
  */
 #define CMD_FUNC(x) void (x) (Client *client, MessageTag *recv_mtags, int parc, const char *parv[])
+
+/** Call a command function - can be useful if you are calling another command function in your own module.
+ * For example in cmd_nick() we call cmd_nick_local() for local functions,
+ * and then we can just use CALL_CMD_FUNC(cmd_nick_local); and don't have
+ * to bother with passing the right command arguments. Which is nice because
+ * command arguments may change in future UnrealIRCd versions.
+ */
+#define CALL_CMD_FUNC(x)	(x)(client, recv_mtags, parc, parv)
+
 /** @} */
 
 /** Command override function - used by all command override handlers.
@@ -1226,13 +1269,15 @@ extern ModDataInfo *ModDataAdd(Module *module, ModDataInfo req);
 extern void ModDataDel(ModDataInfo *md);
 extern void unload_all_unused_moddata(void);
 
-#define LISTENER_NORMAL		0x000001
-#define LISTENER_CLIENTSONLY	0x000002
-#define LISTENER_SERVERSONLY	0x000004
-#define LISTENER_TLS		0x000010
-#define LISTENER_BOUND		0x000020
-#define LISTENER_DEFER_ACCEPT	0x000040
-#define LISTENER_CONTROL	0x000080	/**< Control channel */
+#define LISTENER_NORMAL			0x000001
+#define LISTENER_CLIENTSONLY		0x000002
+#define LISTENER_SERVERSONLY		0x000004
+#define LISTENER_TLS			0x000010
+#define LISTENER_BOUND			0x000020
+#define LISTENER_DEFER_ACCEPT		0x000040
+#define LISTENER_CONTROL		0x000080	/**< Control channel */
+#define LISTENER_NO_CHECK_CONNECT_FLOOD	0x000100	/**< Don't check for connect-flood and max-unknown-connections-per-ip (eg for RPC) */
+#define LISTENER_NO_CHECK_ZLINED	0x000200	/**< Don't check for zlines */
 
 #define IsServersOnlyListener(x)	((x) && ((x)->options & LISTENER_SERVERSONLY))
 
@@ -1292,11 +1337,12 @@ struct Client {
 	LocalClient *local;			/**< Additional information regarding locally connected clients */
 	User *user;				/**< Additional information, if this client is a user */
 	Server *server;				/**< Additional information, if this is a server */
+	RPCClient *rpc;				/**< RPC Client, or NULL */
 	ClientStatus status;			/**< Client status, one of CLIENT_STATUS_* */
 	struct list_head client_hash;		/**< For name hash table (clientTable) */
 	char name[HOSTLEN + 1];			/**< Unique name of the client: nickname for users, hostname for servers */
 	time_t lastnick;			/**< Timestamp on nick */
-	long flags;				/**< Client flags (one or more of CLIENT_FLAG_*) */
+	uint64_t flags;				/**< Client flags (one or more of CLIENT_FLAG_*) */
 	long umodes;				/**< Client usermodes (if user) */
 	Client *direction;			/**< Direction from which this client originated.
 	                                             This always points to a directly connected server or &me.
@@ -1349,6 +1395,7 @@ struct LocalClient {
 	char sockhost[HOSTLEN + 1];	/**< Hostname from the socket */
 	u_short port;			/**< Remote TCP port of client */
 	FloodCounter flood[MAXFLOODOPTIONS];
+	RPCClient *rpc;			/**< RPC Client, or NULL */
 };
 
 /** User information (persons, not servers), you use client->user to access these (see also @link Client @endlink).
@@ -1393,6 +1440,15 @@ struct Server {
 
 /** @} */
 
+typedef struct RPCClient RPCClient;
+/** RPC Client information */
+struct RPCClient {
+	char *rpc_user; /**< Name of the rpc-user block after authentication, NULL during pre-auth */
+	char *issuer; /**< Optional name of the issuer, set by rpc.set_issuer(), eg logged in user on admin panel, can be NULL */
+	json_t *rehash_request; /**< If a REHASH (request) is currently running, otherwise NULL */
+	LogSource *log_sources; /**< Subscribed to which log sources */
+};
+
 struct MessageTag {
 	MessageTag *prev, *next;
 	char *name;
@@ -1409,7 +1465,8 @@ typedef enum PreprocessorItem {
 
 typedef enum PreprocessorPhase {
 	PREPROCESSOR_PHASE_INITIAL	= 1,
-	PREPROCESSOR_PHASE_MODULE	= 2
+	PREPROCESSOR_PHASE_SECONDARY	= 2,
+	PREPROCESSOR_PHASE_MODULE	= 3
 } PreprocessorPhase;
 
 typedef enum AuthenticationType {
@@ -1437,6 +1494,30 @@ struct AuthConfig {
 #define crypt DES_crypt
 #endif
 
+/* CRULE stuff */
+
+#define CRULE_ALL		0
+#define CRULE_AUTO		1
+
+/* some constants and shared data types */
+#define CR_MAXARGLEN 80         /**< Maximum arg length (must be > HOSTLEN) */
+#define CR_MAXARGS 3            /**< Maximum number of args for a rule */
+
+/** Evaluation function for a connection rule. */
+typedef int (*crule_funcptr) (int, void **);
+
+/** CRULE - Node in a connection rule tree. */
+struct CRuleNode {
+  crule_funcptr funcptr; /**< Evaluation function for this node. */
+  int numargs;           /**< Number of arguments. */
+  void *arg[CR_MAXARGS]; /**< Array of arguments.  For operators, each arg
+                            is a tree element; for functions, each arg is
+                            a string. */
+};
+typedef struct CRuleNode CRuleNode;
+typedef struct CRuleNode* CRuleNodePtr;
+
+
 /*
  * conf2 stuff -stskeeps
 */
@@ -1514,9 +1595,6 @@ struct ConfigFlag_tld
 #define CONF_BAN_TYPE_AKILL	1
 #define CONF_BAN_TYPE_TEMPORARY 2
 
-#define CRULE_ALL		0
-#define CRULE_AUTO		1
-
 struct ConfigItem {
 	ConfigItem *prev, *next;
 	ConfigFlag flag;
@@ -1693,19 +1771,93 @@ struct ConfigItem_tld {
 	u_short		options;
 };
 
+#define WEB_OPT_ENABLE	0x1
+
+typedef enum HttpMethod {
+	HTTP_METHOD_NONE = 0,	/**< No valid HTTP request (yet) */
+	HTTP_METHOD_HEAD = 1,	/**< HEAD request */
+	HTTP_METHOD_GET = 2,	/**< GET request */
+	HTTP_METHOD_PUT = 3,	/**< PUT request */
+	HTTP_METHOD_POST = 4,	/**< POST request */
+} HttpMethod;
+
+typedef enum TransferEncoding {
+	TRANSFER_ENCODING_NONE=0,
+	TRANSFER_ENCODING_CHUNKED=1
+} TransferEncoding;
+
+typedef struct WebRequest WebRequest;
+struct WebRequest {
+	HttpMethod method; /**< GET/PUT/POST */
+	char *uri; /**< Requested resource, eg "/api" */
+	NameValuePrioList *headers; /**< HTTP request headers */
+	int num_headers; /**< Number of HTTP request headers (also used for sorting the list) */
+	char request_header_parsed; /**< Done parsing? */
+	char *lefttoparse; /**< Leftover buffer to parse */
+	int lefttoparselen; /**< Length of lefttoparse buffer */
+	int pending_close; /**< Set to 1 when connection should be closed as soon as all data is sent (sendq==0) */
+	char *request_buffer; /**< Buffer for POST data */
+	int request_buffer_size; /**< Size of buffer for POST data */
+	int request_body_complete; /**< POST data has all been read */
+	long long content_length; /**< "Content-Length" as sent by the client */
+	long long chunk_remaining;
+	TransferEncoding transfer_encoding;
+	long long config_max_request_buffer_size; /**< CONFIG: Maximum request length allowed */
+};
+
+typedef struct WebServer WebServer;
+struct WebServer {
+	int (*handle_request)(Client *client, WebRequest *web);
+	int (*handle_body)(Client *client, WebRequest *web, const char *buf, int length);
+};
+
+typedef enum WebSocketType {
+	WEBSOCKET_TYPE_BINARY = 1,
+	WEBSOCKET_TYPE_TEXT   = 2
+} WebSocketType;
+
+typedef struct WebSocketUser WebSocketUser;
+struct WebSocketUser {
+	char get; /**< GET initiated */
+	char handshake_completed; /**< Handshake completed, use websocket frames */
+	char *handshake_key; /**< Handshake key (used during handshake) */
+	char *lefttoparse; /**< Leftover buffer to parse */
+	int lefttoparselen; /**< Length of lefttoparse buffer */
+	WebSocketType type; /**< WEBSOCKET_TYPE_BINARY or WEBSOCKET_TYPE_TEXT */
+	char *sec_websocket_protocol; /**< Only valid during parsing of the request, after that it is NULL again */
+	char *forwarded; /**< Unparsed `Forwarded:` header, RFC 7239 */
+	int secure; /**< If there is a Forwarded header, this indicates if the remote connection is secure */
+};
+
+#define WEBSOCKET_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" /* see RFC6455 */
+
+/* Websocket operations: */
+#define WSOP_CONTINUATION 0x00
+#define WSOP_TEXT         0x01
+#define WSOP_BINARY       0x02
+#define WSOP_CLOSE        0x08
+#define WSOP_PING         0x09
+#define WSOP_PONG         0x0a
+
 struct ConfigItem_listen {
 	ConfigItem_listen *prev, *next;
 	ConfigFlag flag;
-	SocketType socket_type;
-	char *file;
-	char *ip;
-	int port;
-	int options, clients;
-	int fd;
-	SSL_CTX *ssl_ctx;
-	TLSOptions *tls_options;
-	int websocket_options; /* should be in module, but lazy */
-	char *websocket_forward;
+	SocketType socket_type;		/**< Socket type, eg. SOCKET_TYPE_IPV4 or SOCKET_TYPE_UNIX */
+	char *file;			/**< If the listener is a file, the full pathname */
+	char *ip;			/**< IP bind address (if IP listener) */
+	int port;			/**< Port to listen on (if IP listener) */
+	int mode;			/**< Mode permissions (if file aka unix socket listener) */
+	int options;			/**< e.g. LISTENER_BOUND if active */
+	int clients;			/**< Clients connected to this socket / listener */
+	int fd;				/**< File descriptor (if open), or -1 (if not open yet) */
+	char *spoof_ip;			/**< listen::spoof-ip (only for listen::file, if you want to override 127.0.0.1) */
+	SSL_CTX *ssl_ctx;		/**< SSL/TLS context */
+	TLSOptions *tls_options;	/**< SSL/TLS options */
+	WebServer *webserver;		/**< For the webserver module */
+	void (*start_handshake)(Client *client); /**< Function to call on accept() */
+	int websocket_options;		/**< Websocket options (for the websocket module) */
+	int rpc_options;		/**< For the RPC module */
+	char *websocket_forward;	/**< For websocket module too */
 };
 
 struct ConfigItem_sni {
@@ -1768,13 +1920,6 @@ struct ConfigItem_deny_dcc {
 	char			*filename, *reason;
 };
 
-struct ConfigItem_deny_link {
-	ConfigItem_deny_link *prev, *next;
-	ConfigFlag_except flag;
-	ConfigItem_mask  *mask;
-	char *rule, *prettyrule;
-};
-
 struct ConfigItem_deny_version {
 	ConfigItem_deny_version	*prev, *next;
 	ConfigFlag		flag;
@@ -1873,6 +2018,7 @@ struct SecurityGroup {
 	int reputation_score;
 	long connect_time;
 	int webirc;
+	int websocket;
 	int tls;
 	NameList *ip;
 	ConfigItem_mask *mask;
@@ -1883,6 +2029,7 @@ struct SecurityGroup {
 	int exclude_reputation_score;
 	long exclude_connect_time;
 	int exclude_webirc;
+	int exclude_websocket;
 	int exclude_tls;
 	NameList *exclude_ip;
 	ConfigItem_mask *exclude_mask;
@@ -2270,6 +2417,62 @@ typedef enum WhoisConfigDetails {
 #define UNRL_STRIP_LOW_ASCII    0x1     /**< Strip all ASCII < 32 (control codes) */
 #define UNRL_STRIP_KEEP_LF      0x2     /**< Do not strip LF (line feed, \n) */
 
+/** JSON-RPC API Errors, according to jsonrpc.org spec */
+typedef enum JsonRpcError {
+	// Official JSON-RPC error codes:
+	JSON_RPC_ERROR_PARSE_ERROR	= -32700, /**< JSON parse error (fatal) */
+	JSON_RPC_ERROR_INVALID_REQUEST	= -32600, /**< Invalid JSON-RPC Request */
+	JSON_RPC_ERROR_METHOD_NOT_FOUND	= -32601, /**< Method not found */
+	JSON_RPC_ERROR_INVALID_PARAMS	= -32602, /**< Method parameters invalid */
+	JSON_RPC_ERROR_INTERNAL_ERROR	= -32603, /**< Internal server error */
+	// UnrealIRCd JSON-RPC server specific error codes:
+	JSON_RPC_ERROR_API_CALL_DENIED	= -32000, /**< The api user does not have enough permissions to do this call */
+	JSON_RPC_ERROR_SERVER_GONE	= -32001, /**< The request was forwarded to a remote server, but this server went gone while processing the request */
+	JSON_RPC_ERROR_TIMEOUT		= -32002, /**< The request was forwarded to a remote server, but the request/response timed out (15 seconds) */
+	JSON_RPC_ERROR_REMOTE_SERVER_NO_RPC	= -32003, /**< The request was going to be forwarded to a remote server, but the remote server does not support JSON-RPC */
+	// UnrealIRCd specific application error codes:
+	JSON_RPC_ERROR_NOT_FOUND	=  -1000, /**< Target not found (no such nick / channel / ..) */
+	JSON_RPC_ERROR_ALREADY_EXISTS	=  -1001, /**< Resource already exists by that name (eg on nickchange request, a gline, etc) */
+	JSON_RPC_ERROR_INVALID_NAME	=  -1002, /**< Name is not permitted (eg: nick, channel, ..) */
+	JSON_RPC_ERROR_USERNOTINCHANNEL	=  -1003, /**< The user is not in the channel */
+	JSON_RPC_ERROR_TOO_MANY_ENTRIES	=  -1004, /**< Too many entries (eg: banlist, ..) */
+	JSON_RPC_ERROR_DENIED		=  -1005, /**< Permission denied for user (unrelated to api user permissions) */
+} JsonRpcError;
+
+/** Require a parameter in an RPC command */
+#define REQUIRE_PARAM_STRING(name, varname)          do { \
+                                                         varname = json_object_get_string(params, name); \
+                                                         if (!varname) \
+                                                         { \
+                                                             rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", name); \
+                                                             return; \
+                                                         } \
+                                                     } while(0)
+
+#define REQUIRE_PARAM_INTEGER(name, varname)         do { \
+                                                         json_t *t = json_object_get(params, name); \
+                                                         if (!t || !json_is_integer(t)) \
+                                                         { \
+                                                             rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", name); \
+                                                             return; \
+                                                         } \
+                                                         varname = json_integer_value(t); \
+                                                     } while(0)
+
+#define REQUIRE_PARAM_BOOLEAN(name, varname)         do { \
+                                                         json_t *vvv = json_object_get(params, name); \
+                                                         if (!v || !json_is_boolean(v)) \
+                                                         { \
+                                                             rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", name); \
+                                                             return; \
+                                                         } \
+                                                         varname = json_is_true(v) ? 1 : 0; \
+                                                     } while(0)
+
+#define OPTIONAL_PARAM_STRING(name, varname)         varname = json_object_get_string(params, name)
+#define OPTIONAL_PARAM_INTEGER(name, varname, def)   varname = json_object_get_integer(params, name, def)
+#define OPTIONAL_PARAM_BOOLEAN(name, varname, def)   varname = json_object_get_boolean(params, name, def)
+
 #endif /* __struct_include__ */
 
 #include "dynconf.h"
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	202204
+#define UNREAL_VERSION_TIME	202313
 
 #define UNREAL_VERSION		((UNREAL_VERSION_GENERATION << 24) + (UNREAL_VERSION_MAJOR << 16) + (UNREAL_VERSION_MINOR << 8))
 #define UnrealProtocol 		6000
diff --git a/include/whowas.h b/include/whowas.h
@@ -35,6 +35,15 @@
 #ifndef	__whowas_include__
 #define __whowas_include__
 
+/* NOTE: Don't reorder values of these, as they are used in whowasdb */
+typedef enum WhoWasEvent {
+    WHOWAS_EVENT_QUIT=0,
+    WHOWAS_EVENT_NICK_CHANGE=1,
+    WHOWAS_EVENT_SERVER_TERMINATING=2
+} WhoWasEvent;
+#define WHOWAS_LOWEST_EVENT 0
+#define WHOWAS_HIGHEST_EVENT 2
+
 /*
 ** add_history
 **	Add the currently defined name of the client to history.
@@ -42,7 +51,7 @@
 **	Client must be a fully registered user (specifically,
 **	the user structure must have been allocated).
 */
-void add_history(Client *, int);
+void add_history(Client *, int, WhoWasEvent);
 
 /*
 ** off_history
diff --git a/include/windows/setup.h b/include/windows/setup.h
@@ -39,6 +39,7 @@
 #define strcasecmp _stricmp
 #define strncasecmp _strnicmp
 #define HAVE_EXPLICIT_BZERO
+#define HAVE_STRNLEN
 #define explicit_bzero(a,b) SecureZeroMemory(a,b)
 
 /* mode_t: Needed in s_conf.c for the third argument of open(3p).
@@ -59,13 +60,13 @@
 #define UNREAL_VERSION_GENERATION 6
 
 /* Major version number (e.g.: 2 for Unreal3.2*) */
-#define UNREAL_VERSION_MAJOR 0
+#define UNREAL_VERSION_MAJOR 1
 
 /* Minor version number (e.g.: 1 for Unreal3.2.1) */
-#define UNREAL_VERSION_MINOR 4
+#define UNREAL_VERSION_MINOR 0
 
 /* Version suffix such as a beta marker or release candidate marker. (e.g.:
    -rcX for unrealircd-3.2.9-rcX) */
-#define UNREAL_VERSION_SUFFIX ".2"
+#define UNREAL_VERSION_SUFFIX ""
 
 #endif
diff --git a/src/Makefile.in b/src/Makefile.in
@@ -30,9 +30,9 @@ OBJS=ircd_vars.o dns.o auth.o channel.o crule.o dbuf.o \
 	version.o whowas.o random.o api-usermode.o api-channelmode.o \
 	api-moddata.o api-extban.o api-isupport.o api-command.o \
 	api-clicap.o api-messagetag.o api-history-backend.o api-efunctions.o \
-	api-event.o \
+	api-event.o api-rpc.o \
 	crypt_blowfish.o unrealdb.o crashreport.o modulemanager.o \
-	utf8.o log.o \
+	utf8.o json.o log.o \
 	openssl_hostname_validation.o $(URL)
 
 SRC=$(OBJS:%.o=%.c)
diff --git a/src/api-channelmode.c b/src/api-channelmode.c
@@ -423,6 +423,7 @@ Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
 	cm->sjoin_check = req.sjoin_check;
 	cm->local = req.local;
 	cm->unset_with_param = req.unset_with_param;
+	cm->flood_type_action = req.flood_type_action;
 	cm->owner = module;
 	cm->unloaded = 0;
 
@@ -534,7 +535,7 @@ static void unload_extcmode_commit(Cmode *cmode)
 					}
 					free_message_tags(mtags);
 
-					cmode->free_param(GETPARASTRUCT(channel, cmode->letter));
+					cmode->free_param(GETPARASTRUCT(channel, cmode->letter), 0);
 					channel->mode.mode &= ~cmode->mode;
 				}
 			}
@@ -627,8 +628,9 @@ void cm_putparameter(Channel *channel, char mode, const char *str)
  */
 void cm_freeparameter(Channel *channel, char mode)
 {
-	GETPARAMHANDLERBYLETTER(mode)->free_param(GETPARASTRUCT(channel, mode));
-	GETPARASTRUCT(channel, mode) = NULL;
+	int n = GETPARAMHANDLERBYLETTER(mode)->free_param(GETPARASTRUCT(channel, mode), 1);
+	if (n == 0)
+		GETPARASTRUCT(channel, mode) = NULL;
 }
 
 
@@ -713,7 +715,7 @@ void extcmode_free_paramlist(void **ar)
 		handler = GETPARAMHANDLERBYSLOT(i);
 		if (!handler)
 			continue; /* nothing here... */
-		handler->free_param(ar[handler->param_slot]);
+		handler->free_param(ar[handler->param_slot], 0);
 		ar[handler->param_slot] = NULL;
 	}
 }
diff --git a/src/api-clicap.c b/src/api-clicap.c
@@ -22,10 +22,20 @@
 
 #include "unrealircd.h"
 
+#define ADVERTISEONLYCAPS 16
+/* Advertise only caps are not counted anywhere, this only provides space in rehash temporary storage arrays.
+ * If exceeded, the caps just won't be stored and will be re-added safely. --k4be
+ */
+
+#define MAXCLICAPS ((int)(sizeof(long)*8 - 1 + ADVERTISEONLYCAPS)) /* how many cap bits will fit in `long`? */
+static char *old_caps[MAXCLICAPS]; /**< List of old CAP names - used for /rehash */
+int old_caps_proto[MAXCLICAPS]; /**< List of old CAP protocol values - used for /rehash */
+
 MODVAR ClientCapability *clicaps = NULL; /* List of client capabilities */
 
 void clicap_init(void)
 {
+	memset(&old_caps, 0, sizeof(old_caps));
 }
 
 /**
@@ -211,7 +221,7 @@ void unload_clicap_commit(ClientCapability *clicap)
 	           log_data_string("token", clicap->name));
 
 	/* NOTE: Stripping the CAP from local clients is done
-	 * in clicap_post_rehash(), so not here.
+	 * in clicap_check_for_changes(), so not here.
 	 */
 
 	/* A message tag handler may depend on us, remove it */
@@ -266,24 +276,19 @@ void unload_all_unused_caps(void)
 	}
 }
 
-#define ADVERTISEONLYCAPS 16
-/* Advertise only caps are not counted anywhere, this only provides space in rehash temporary storage arrays.
- * If exceeded, the caps just won't be stored and will be re-added safely. --k4be
- */
-
-#define MAXCLICAPS ((int)(sizeof(long)*8 - 1 + ADVERTISEONLYCAPS)) /* how many cap bits will fit in `long`? */
-static char *old_caps[MAXCLICAPS]; /**< List of old CAP names - used for /rehash */
-int old_caps_proto[MAXCLICAPS]; /**< List of old CAP protocol values - used for /rehash */
-
 /** Called before REHASH. This saves the list of cap names and protocol values */
 void clicap_pre_rehash(void)
 {
 	ClientCapability *clicap;
 	int i = 0;
 
-	memset(&old_caps, 0, sizeof(old_caps));
+	for (i=0; i < MAXCLICAPS; i++)
+	{
+		safe_free(old_caps[i]);
+		old_caps_proto[i] = 0;
+	}
 
-	for (clicap = clicaps; clicap; clicap = clicap->next)
+	for (i=0, clicap = clicaps; clicap; clicap = clicap->next)
 	{
 		if (i == MAXCLICAPS)
 		{
@@ -321,7 +326,7 @@ void clear_cap_for_users(long cap)
  * 2. Sending any CAP DEL
  * 3. Sending any CAP NEW
  */
-void clicap_post_rehash(void)
+void clicap_check_for_changes(void)
 {
 	ClientCapability *clicap;
 	char *name;
diff --git a/src/api-efunctions.c b/src/api-efunctions.c
@@ -40,12 +40,14 @@ int (*can_join)(Client *client, Channel *channel, const char *key, char **errmsg
 void (*do_mode)(Channel *channel, Client *client, MessageTag *mtags, int parc, const char *parv[], time_t sendts, int samode);
 MultiLineMode *(*set_mode)(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
                            char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
-void (*set_channel_mode)(Channel *channel, char *modes, char *parameters);
+void (*set_channel_mode)(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters);
+void (*set_channel_topic)(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at);
 void (*cmd_umode)(Client *client, MessageTag *mtags, int parc, const char *parv[]);
 int (*register_user)(Client *client);
 int (*tkl_hash)(unsigned int c);
 char (*tkl_typetochar)(int type);
 int (*tkl_chartotype)(char c);
+char (*tkl_configtypetochar)(const char *name);
 const char *(*tkl_type_string)(TKL *tk);
 const char *(*tkl_type_config_string)(TKL *tk);
 char *(*tkl_uhost)(TKL *tkl, char *buf, size_t buflen, int options);
@@ -84,7 +86,6 @@ void (*broadcast_md_client)(ModDataInfo *mdi, Client *client, ModData *md);
 void (*broadcast_md_channel)(ModDataInfo *mdi, Channel *channel, ModData *md);
 void (*broadcast_md_member)(ModDataInfo *mdi, Channel *channel, Member *m, ModData *md);
 void (*broadcast_md_membership)(ModDataInfo *mdi, Client *client, Membership *m, ModData *md);
-int (*check_banned)(Client *client, int exitflags);
 int (*check_deny_version)(Client *client, const char *software, int protocol, const char *flags);
 void (*broadcast_md_client_cmd)(Client *except, Client *sender, Client *acptr, const char *varname, const char *value);
 void (*broadcast_md_channel_cmd)(Client *except, Client *sender, Channel *channel, const char *varname, const char *value);
@@ -105,6 +106,7 @@ int (*do_remote_nick_name)(char *nick);
 const char *(*charsys_get_current_languages)(void);
 void (*broadcast_sinfo)(Client *client, Client *to, Client *except);
 void (*connect_server)(ConfigItem_link *aconf, Client *by, struct hostent *hp);
+int (*is_services_but_not_ulined)(Client *client);
 void (*parse_message_tags)(Client *client, char **str, MessageTag **mtag_list);
 const char *(*mtags_to_string)(MessageTag *m, Client *client);
 int (*can_send_to_channel)(Client *client, Channel *channel, const char **msgtext, const char **errmsg, int notice);
@@ -120,6 +122,9 @@ TKL *(*find_tkl_banexception)(int type, const char *usermask, const char *hostma
 TKL *(*find_tkl_nameban)(int type, const char *name, int hold);
 TKL *(*find_tkl_spamfilter)(int type, const char *match_string, unsigned short action, unsigned short target);
 int (*find_tkl_exception)(int ban_type, Client *client);
+int (*server_ban_parse_mask)(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+int (*server_ban_exception_parse_mask)(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+void (*tkl_added)(Client *client, TKL *tkl);
 int (*is_silenced)(Client *client, Client *acptr);
 int (*del_silence)(Client *client, const char *mask);
 int (*add_silence)(Client *client, const char *mask, int senderr);
@@ -137,6 +142,22 @@ char *(*get_chmodes_for_user)(Client *client, const char *flags);
 WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name);
 int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
 int (*unreal_match_iplist)(Client *client, NameList *l);
+void (*webserver_send_response)(Client *client, int status, char *msg);
+void (*webserver_close_client)(Client *client);
+int (*webserver_handle_body)(Client *client, WebRequest *web, const char *readbuf, int length);
+void (*rpc_response)(Client *client, json_t *request, json_t *result);
+void (*rpc_error)(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
+void (*rpc_error_fmt)(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...);
+void (*rpc_send_request_to_remote)(Client *source, Client *target, json_t *request);
+void (*rpc_send_response_to_remote)(Client *source, Client *target, json_t *response);
+int (*rrpc_supported_simple)(Client *target, char **problem_server);
+int (*rrpc_supported)(Client *target, const char *module, const char *minimum_version, char **problem_server);
+int (*websocket_handle_websocket)(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
+int (*websocket_create_packet)(int opcode, char **buf, int *len);
+int (*websocket_create_packet_ex)(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
+int (*websocket_create_packet_simple)(int opcode, const char **buf, int *len);
+const char *(*check_deny_link)(ConfigItem_link *link, int auto_connect);
+void (*mtag_add_issued_by)(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
 
 Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
 {
@@ -320,6 +341,7 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_DO_MODE, do_mode, NULL);
 	efunc_init_function(EFUNC_SET_MODE, set_mode, NULL);
 	efunc_init_function(EFUNC_SET_CHANNEL_MODE, set_channel_mode, NULL);
+	efunc_init_function(EFUNC_SET_CHANNEL_TOPIC, set_channel_topic, NULL);
 	efunc_init_function(EFUNC_CMD_UMODE, cmd_umode, NULL);
 	efunc_init_function(EFUNC_REGISTER_USER, register_user, NULL);
 	efunc_init_function(EFUNC_TKL_HASH, tkl_hash, NULL);
@@ -349,7 +371,6 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_BROADCAST_MD_CHANNEL, broadcast_md_channel, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_MEMBER, broadcast_md_member, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_MEMBERSHIP, broadcast_md_membership, NULL);
-	efunc_init_function(EFUNC_CHECK_BANNED, check_banned, NULL);
 	efunc_init_function(EFUNC_INTRODUCE_USER, introduce_user, NULL);
 	efunc_init_function(EFUNC_CHECK_DENY_VERSION, check_deny_version, NULL);
 	efunc_init_function(EFUNC_BROADCAST_MD_CLIENT_CMD, broadcast_md_client_cmd, NULL);
@@ -371,9 +392,11 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_CHARSYS_GET_CURRENT_LANGUAGES, charsys_get_current_languages, NULL);
 	efunc_init_function(EFUNC_BROADCAST_SINFO, broadcast_sinfo, NULL);
 	efunc_init_function(EFUNC_CONNECT_SERVER, connect_server, NULL);
+	efunc_init_function(EFUNC_IS_SERVICES_BUT_NOT_ULINED, is_services_but_not_ulined, NULL);
 	efunc_init_function(EFUNC_PARSE_MESSAGE_TAGS, parse_message_tags, &parse_message_tags_default_handler);
 	efunc_init_function(EFUNC_MTAGS_TO_STRING, mtags_to_string, &mtags_to_string_default_handler);
 	efunc_init_function(EFUNC_TKL_CHARTOTYPE, tkl_chartotype, NULL);
+	efunc_init_function(EFUNC_TKL_CONFIGTYPETOCHAR, tkl_configtypetochar, NULL);
 	efunc_init_function(EFUNC_TKL_TYPE_STRING, tkl_type_string, NULL);
 	efunc_init_function(EFUNC_TKL_TYPE_CONFIG_STRING, tkl_type_config_string, NULL);
 	efunc_init_function(EFUNC_CAN_SEND_TO_CHANNEL, can_send_to_channel, NULL);
@@ -391,6 +414,9 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_FIND_TKL_NAMEBAN, find_tkl_nameban, NULL);
 	efunc_init_function(EFUNC_FIND_TKL_SPAMFILTER, find_tkl_spamfilter, NULL);
 	efunc_init_function(EFUNC_FIND_TKL_EXCEPTION, find_tkl_exception, NULL);
+	efunc_init_function(EFUNC_SERVER_BAN_PARSE_MASK, server_ban_parse_mask, NULL);
+	efunc_init_function(EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK, server_ban_exception_parse_mask, NULL);
+	efunc_init_function(EFUNC_TKL_ADDED, tkl_added, NULL);
 	efunc_init_function(EFUNC_ADD_SILENCE, add_silence, add_silence_default_handler);
 	efunc_init_function(EFUNC_DEL_SILENCE, del_silence, del_silence_default_handler);
 	efunc_init_function(EFUNC_IS_SILENCED, is_silenced, is_silenced_default_handler);
@@ -409,4 +435,20 @@ void efunctions_init(void)
 	efunc_init_function(EFUNC_WHOIS_GET_POLICY, whois_get_policy, NULL);
 	efunc_init_function(EFUNC_MAKE_OPER, make_oper, make_oper_default_handler);
 	efunc_init_function(EFUNC_UNREAL_MATCH_IPLIST, unreal_match_iplist, NULL);
+	efunc_init_function(EFUNC_WEBSERVER_SEND_RESPONSE, webserver_send_response, webserver_send_response_default_handler);
+	efunc_init_function(EFUNC_WEBSERVER_CLOSE_CLIENT, webserver_close_client, webserver_close_client_default_handler);
+	efunc_init_function(EFUNC_WEBSERVER_HANDLE_BODY, webserver_handle_body, webserver_handle_body_default_handler);
+	efunc_init_function(EFUNC_RPC_RESPONSE, rpc_response, rpc_response_default_handler);
+	efunc_init_function(EFUNC_RPC_ERROR, rpc_error, rpc_error_default_handler);
+	efunc_init_function(EFUNC_RPC_ERROR_FMT, rpc_error_fmt, rpc_error_fmt_default_handler);
+	efunc_init_function(EFUNC_RPC_SEND_REQUEST_TO_REMOTE, rpc_send_request_to_remote, rpc_send_request_to_remote_default_handler);
+	efunc_init_function(EFUNC_RPC_SEND_RESPONSE_TO_REMOTE, rpc_send_response_to_remote, rpc_send_response_to_remote_default_handler);
+	efunc_init_function(EFUNC_RRPC_SUPPORTED, rrpc_supported, rrpc_supported_default_handler);
+	efunc_init_function(EFUNC_RRPC_SUPPORTED_SIMPLE, rrpc_supported_simple, rrpc_supported_simple_default_handler);
+	efunc_init_function(EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, websocket_handle_websocket, websocket_handle_websocket_default_handler);
+	efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET, websocket_create_packet, websocket_create_packet_default_handler);
+	efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET_EX, websocket_create_packet_ex, websocket_create_packet_ex_default_handler);
+	efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, websocket_create_packet_simple, websocket_create_packet_simple_default_handler);
+	efunc_init_function(EFUNC_CHECK_DENY_LINK, check_deny_link, NULL);
+	efunc_init_function(EFUNC_MTAG_GENERATE_ISSUED_BY_IRC, mtag_add_issued_by, mtag_add_issued_by_default_handler);
 }
diff --git a/src/api-extban.c b/src/api-extban.c
@@ -80,8 +80,10 @@ int is_valid_extban_name(const char *p)
 {
 	if (!*p)
 		return 0; /* empty name */
+	if (strlen(p) > 32)
+		return 0; /* too long */
 	for (; *p; p++)
-		if (!isalnum(*p) && !strchr("_-", *p))
+		if (!islower(*p) && !isdigit(*p) && !strchr("_-", *p))
 			return 0;
 	return 1;
 }
@@ -169,6 +171,15 @@ Extban *ExtbanAdd(Module *module, ExtbanInfo req)
 		return NULL;
 	}
 
+	if (!req.conv_param)
+	{
+		module->errorcode = MODERR_INVALID;
+		unreal_log(ULOG_ERROR, "module", "EXTBANADD_API_ERROR", NULL,
+			   "ExtbanAdd(): conv_param event missing. Module: $module_name",
+			   log_data_string("module_name", module->header->name));
+		return NULL;
+	}
+
 	for (e=extbans; e; e = e->next)
 	{
 		if (e->letter == req.letter)
@@ -188,6 +199,7 @@ Extban *ExtbanAdd(Module *module, ExtbanInfo req)
 				 */
 				e->preregistered = 0;
 				existing = 1;
+				break;
 			} else
 			if (module->flags == MODFLAG_NONE)
 			{
@@ -360,33 +372,12 @@ int extban_is_ok_nuh_extban(BanContext *b)
  */
 const char *extban_conv_param_nuh(BanContext *b, Extban *extban)
 {
-	char *cp, *user, *host, *mask, *ret = NULL;
-	static char retbuf[USERLEN + NICKLEN + HOSTLEN + 32];
 	char tmpbuf[USERLEN + NICKLEN + HOSTLEN + 32];
+	static char retbuf[USERLEN + NICKLEN + HOSTLEN + 32];
 
 	/* Work on a copy */
 	strlcpy(tmpbuf, b->banstr, sizeof(retbuf));
-	mask = tmpbuf;
-
-	if (!*mask)
-		return NULL; /* empty extban */
-	if ((*mask == '~') && !strchr(mask, '@'))
-		return NULL; /* not a user@host ban, too confusing. */
-	if ((user = strchr((cp = mask), '!')))
-		*user++ = '\0';
-	if ((host = strrchr(user ? user : cp, '@')))
-	{
-		*host++ = '\0';
-		if (!user)
-			ret = make_nick_user_host(NULL, trim_str(cp,USERLEN), trim_str(host,HOSTLEN));
-	}
-	else if (!user && strchr(cp, '.'))
-		ret = make_nick_user_host(NULL, NULL, trim_str(cp,HOSTLEN));
-	if (!ret)
-		ret = make_nick_user_host(trim_str(cp,NICKLEN), trim_str(user,USERLEN), trim_str(host,HOSTLEN));
-
-	strlcpy(retbuf, ret, sizeof(retbuf));
-	return retbuf;
+	return convert_regular_ban(tmpbuf, retbuf, sizeof(retbuf));
 }
 
 /** conv_param to deal with stacked extbans.
@@ -440,25 +431,11 @@ const char *extban_conv_param_nuh_or_extban(BanContext *b, Extban *self_extban)
 		return NULL;
 	}
 
-	if (extban->conv_param)
-	{
-		//BanContext *b = safe_alloc(sizeof(BanContext));
-		//b->banstr = mask; <-- this is redundant right? we can use existing 'b' context??
-		extban_recursion++;
-		ret = extban->conv_param(b, extban);
-		extban_recursion--;
-		ret = prefix_with_extban(ret, b, extban, retbuf, sizeof(retbuf));
-		//safe_free(b);
-		return ret;
-	}
-	/* I honestly don't know what the deal is with the 80 char cap in clean_ban_mask is about. So I'm leaving it out here. -- aquanight */
-	/* I don't know why it's 80, but I like a limit anyway. A ban of 500 characters can never be good... -- Syzop */
-	if (strlen(b->banstr) > 80)
-	{
-		strlcpy(retbuf, b->banstr, 128);
-		return retbuf;
-	}
-	return b->banstr;
+	extban_recursion++;
+	ret = extban->conv_param(b, extban);
+	extban_recursion--;
+	ret = prefix_with_extban(ret, b, extban, retbuf, sizeof(retbuf));
+	return ret;
 }
 
 char *prefix_with_extban(const char *remainder, BanContext *b, Extban *extban, char *buf, size_t buflen)
diff --git a/src/api-rpc.c b/src/api-rpc.c
@@ -0,0 +1,150 @@
+/************************************************************************
+ * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-rpc.c
+ * (c) 2022- Bram Matthys and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+/** @file
+ * @brief RPC API
+ */
+#include "unrealircd.h"
+
+/** This is the RPC API used for web requests.
+ * For an overview of available RPC's (not the API)
+ * see https://www.unrealircd.org/docs/RPC
+ * @defgroup RPCAPI RPC API
+ * @{
+ */
+
+/** List of RPC handlers */
+MODVAR RPCHandler *rpchandlers = NULL;
+
+/* Forward declarations */
+static void unload_rpc_handler_commit(RPCHandler *m);
+
+/** Adds a new RPC handler.
+ * @param module The module which owns this RPC handler.
+ * @param mreq   The details of the request such as the method name, callback, etc.
+ * @return Returns the handle to the RPC handler if successful, otherwise NULL.
+ *         The module's error code contains specific information about the
+ *         error.
+ */
+RPCHandler *RPCHandlerAdd(Module *module, RPCHandlerInfo *mreq)
+{
+	RPCHandler *m;
+	ModuleObject *mobj;
+
+	/* Some consistency checks to avoid a headache for module devs later on: */
+	if (!mreq->method || !mreq->call)
+	{
+		unreal_log(ULOG_ERROR, "module", "RPCHANDLERADD_API_ERROR", NULL,
+			   "RPCHandlerAdd() from module $module_name: "
+			   "Missing required fields.",
+			   log_data_string("module_name", module->header->name));
+		abort();
+	}
+
+	m = RPCHandlerFind(mreq->method);
+	if (m)
+	{
+		if (m->unloaded)
+		{
+			m->unloaded = 0;
+		} else {
+			if (module)
+				module->errorcode = MODERR_EXISTS;
+			return NULL;
+		}
+	} else {
+		/* New RPC handler */
+		m = safe_alloc(sizeof(RPCHandler));
+		safe_strdup(m->method, mreq->method);
+		AddListItem(m, rpchandlers);
+	}
+	/* Add or update the following fields: */
+	m->owner = module;
+	m->flags = mreq->flags;
+	m->loglevel = mreq->loglevel;
+	if (!valid_loglevel(m->loglevel))
+		m->loglevel = ULOG_INFO;
+	m->call = mreq->call;
+
+	/* Add module object */
+	mobj = safe_alloc(sizeof(ModuleObject));
+	mobj->type = MOBJ_RPC;
+	mobj->object.rpc = m;
+	AddListItem(mobj, module->objects);
+	module->errorcode = MODERR_NOERROR;
+
+	return m;
+}
+
+/** Returns the RPC handler for the given method name.
+ * @param method The method to search for.
+ * @return Returns the handle to the RPC handler,
+ *         or NULL if not found.
+ */
+RPCHandler *RPCHandlerFind(const char *method)
+{
+	RPCHandler *m;
+
+	for (m = rpchandlers; m; m = m->next)
+	{
+		if (!strcasecmp(method, m->method))
+			return m;
+	}
+	return NULL;
+}
+
+/** Remove the specified RPC handler - modules should not call this.
+ * This is done automatically for modules on unload, so is only called internally.
+ * @param m The PRC handler to remove.
+ */
+void RPCHandlerDel(RPCHandler *m)
+{
+	if (m->owner)
+	{
+		ModuleObject *mobj;
+		for (mobj = m->owner->objects; mobj; mobj = mobj->next) {
+			if (mobj->type == MOBJ_RPC && mobj->object.rpc == m)
+			{
+				DelListItem(mobj, m->owner->objects);
+				safe_free(mobj);
+				break;
+			}
+		}
+		m->owner = NULL;
+	}
+
+	if (loop.rehashing)
+		m->unloaded = 1;
+	else
+		unload_rpc_handler_commit(m);
+}
+
+/** @} */
+
+static void unload_rpc_handler_commit(RPCHandler *m)
+{
+	/* This is an unusual operation, I think we should log it. */
+	unreal_log(ULOG_INFO, "module", "UNLOAD_RPC_HANDLER", NULL,
+	           "Unloading RPC handler for '$method'",
+	           log_data_string("method", m->method));
+
+	/* Destroy the object */
+	DelListItem(m, rpchandlers);
+	safe_free(m->method);
+	safe_free(m);
+}
+
+void unload_all_unused_rpc_handlers(void)
+{
+	RPCHandler *m, *m_next;
+
+	for (m = rpchandlers; m; m = m_next)
+	{
+		m_next = m->next;
+		if (m->unloaded)
+			unload_rpc_handler_commit(m);
+	}
+}
diff --git a/src/channel.c b/src/channel.c
@@ -473,6 +473,46 @@ Ban *is_banned_with_nick(Client *client, Channel *channel, int type, const char 
 	return ban;
 }
 
+/** Checks if a ban already exists */
+int ban_exists(Ban *lst, const char *str)
+{
+	for (; lst; lst = lst->next)
+		if (!mycmp(lst->banstr, str))
+			return 1;
+	return 0;
+}
+
+/** Checks if a ban already exists - special version.
+ * This ignores the "~time:xx:" suffixes in the banlist.
+ * So it will return 1 if a ban is there for ~time:5:blah!*@*
+ * and you call ban_exists_ignore_time(channel->banlist, "blah!*@*")
+ */
+int ban_exists_ignore_time(Ban *lst, const char *str)
+{
+	const char *p;
+
+	for (; lst; lst = lst->next)
+	{
+		if (!strncmp(lst->banstr, "~time:", 6))
+		{
+			/* Special treatment for ~time:xx: */
+			p = strchr(lst->banstr+6, ':');
+			if (p)
+			{
+				p++;
+				if (!mycmp(p, str))
+					return 1;
+			}
+		} else
+		{
+			/* The simple version */
+			if (!mycmp(lst->banstr, str))
+				return 1;
+		}
+	}
+	return 0;
+}
+
 /** Add user to the channel.
  * This adds both the Member struct to the channel->members linked list
  * and also the Membership struct to the client->user->channel linked list.
@@ -614,34 +654,24 @@ Cmode_t get_extmode_bitbychar(char m)
  */
 void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, Channel *channel, int hide_local_modes)
 {
-	int ismember = 0;
+	int show_mode_parameters = 0;
 	Cmode *cm;
 
 	if (!mbuf_size || !pbuf_size)
 		return;
 
-	if (!client || IsMember(client, channel) || IsServer(client) || IsMe(client) || IsULine(client))
-		ismember = 1;
+	if (!client || IsMember(client, channel) || IsServer(client) || IsMe(client) || IsULine(client) ||
+	    ValidatePermissionsForPath("channel:see:mode:remote",client,NULL,channel,NULL))
+	{
+		show_mode_parameters = 1;
+	}
 
 	*pbuf = '\0';
 	strlcpy(mbuf, "+", mbuf_size);
 
-	/* Paramless first */
-	for (cm=channelmodes; cm; cm = cm->next)
-	{
-		if (cm->letter &&
-		    !cm->paracount &&
-		    !(hide_local_modes && cm->local) &&
-		    (channel->mode.mode & cm->mode))
-		{
-			strlcat_letter(mbuf, cm->letter, mbuf_size);
-		}
-	}
-
 	for (cm=channelmodes; cm; cm = cm->next)
 	{
 		if (cm->letter &&
-		    cm->paracount &&
 		    !(hide_local_modes && cm->local) &&
 		    (channel->mode.mode & cm->mode))
 		{
@@ -650,7 +680,7 @@ void channel_modes(Client *client, char *mbuf, char *pbuf, size_t mbuf_size, siz
 			if (mbuf_size)
 				strlcat_letter(mbuf, flag, mbuf_size);
 
-			if (ismember)
+			if (cm->paracount && show_mode_parameters)
 			{
 				strlcat(pbuf, cm_getparameter(channel, flag), pbuf_size);
 				strlcat(pbuf, " ", pbuf_size);
@@ -704,6 +734,59 @@ char *trim_str(char *str, int len)
 	return str;
 }
 
+/* Convert regular ban (non-extban) if needed.
+ * This does things like:
+ * nick!user@host -> nick!user@host (usually no change)
+ * nickkkkkkkkkkkkkkkkkkkkkkkkkk!user@host -> nickkkkkkk*!user@host (dealing with NICKLEN restrictions and such).
+ * user@host -> *!user@host
+ * 1.2.3.4 -> *!*@1.2.3.4 (converting IP to a proper mask)
+ * @param mask		Incoming mask (this will be touched/fragged!)
+ * @param buf		Output buffer
+ * @param buflen	Length of the output buffer, eg sizeof(buf)
+ * @retval The sanitized mask, or NULL if it should be rejected fully.
+ * @note Since 'mask' will be fragged, you most likely wish to pass a copy of it rather than the original.
+ */
+const char *convert_regular_ban(char *mask, char *buf, size_t buflen)
+{
+	static char namebuf[USERLEN + HOSTLEN + 6];
+	char *user, *host;
+
+	if (!*mask)
+		return NULL; /* empty extban */
+
+	if (!buf)
+	{
+		buf = namebuf;
+		buflen = sizeof(namebuf);
+	}
+
+	if ((*mask == '~') && !strchr(mask, '@'))
+	{
+		/* has a '~', which makes it look like an extban,
+		 * but is not a user@host ban, too confusing.
+		 */
+		return NULL;
+	}
+
+	if ((user = strchr(mask, '!')))
+		*user++ = '\0';
+
+	if ((host = strrchr(user ? user : mask, '@')))
+	{
+		*host++ = '\0';
+		if (!user)
+			return make_nick_user_host_r(buf, buflen, NULL, trim_str(mask,USERLEN), trim_str(host,HOSTLEN));
+	}
+	else if (!user && (strchr(mask, '.') || strchr(mask, ':')))
+	{
+		/* 1.2.3.4 -> *!*@1.2.3.4 (and the same for IPv6) */
+		return make_nick_user_host_r(buf, buflen, NULL, NULL, trim_str(mask,HOSTLEN));
+	}
+
+	/* regular nick!user@host with the auto-trimming feature */
+	return make_nick_user_host_r(buf, buflen, trim_str(mask,NICKLEN), trim_str(user,USERLEN), trim_str(host,HOSTLEN));
+}
+
 /** Make a proper ban mask.
  * This takes user input (eg: "nick") and converts it to a mask suitable
  * in the +beI lists (eg: "nick!*@*"). It also deals with extended bans,
@@ -719,8 +802,6 @@ char *trim_str(char *str, int len)
 const char *clean_ban_mask(const char *mask_in, int what, Client *client, int conv_options)
 {
 	char *cp, *x;
-	char *user;
-	char *host;
 	static char mask[512];
 
 	/* Strip any ':' at beginning since that would cause a desync */
@@ -798,21 +879,7 @@ const char *clean_ban_mask(const char *mask_in, int what, Client *client, int co
 		return mask;
 	}
 
-	if ((*mask == '~') && !strchr(mask, '@'))
-		return NULL; /* not an extended ban and not a ~user@host ban either. */
-
-	if ((user = strchr((cp = mask), '!')))
-		*user++ = '\0';
-	if ((host = strrchr(user ? user : cp, '@')))
-	{
-		*host++ = '\0';
-
-		if (!user)
-			return make_nick_user_host(NULL, trim_str(cp,USERLEN), trim_str(host,HOSTLEN));
-	}
-	else if (!user && strchr(cp, '.'))
-		return make_nick_user_host(NULL, NULL, trim_str(cp,HOSTLEN));
-	return make_nick_user_host(trim_str(cp,NICKLEN), trim_str(user,USERLEN), trim_str(host,HOSTLEN));
+	return convert_regular_ban(mask, NULL, 0);
 }
 
 /** Check if 'client' matches an invite exception (+I) on 'channel' */
diff --git a/src/conf.c b/src/conf.c
@@ -52,7 +52,6 @@ static int	_conf_link		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_ban		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_set		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_deny		(ConfigFile *conf, ConfigEntry *ce);
-static int	_conf_deny_link		(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_deny_channel	(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_deny_version	(ConfigFile *conf, ConfigEntry *ce);
 static int	_conf_require		(ConfigFile *conf, ConfigEntry *ce);
@@ -188,6 +187,8 @@ extern void unload_all_unused_extcmodes(void);
 extern void unload_all_unused_extbans(void);
 extern void unload_all_unused_caps(void);
 extern void unload_all_unused_history_backends(void);
+extern void unload_all_unused_rpc_handlers(void);
+
 int reloadable_perm_module_unloaded(void);
 int tls_tests(void);
 
@@ -226,7 +227,6 @@ ConfigItem_link		*conf_link = NULL;
 ConfigItem_ban		*conf_ban = NULL;
 ConfigItem_deny_channel *conf_deny_channel = NULL;
 ConfigItem_allow_channel *conf_allow_channel = NULL;
-ConfigItem_deny_link	*conf_deny_link = NULL;
 ConfigItem_deny_version *conf_deny_version = NULL;
 ConfigItem_alias	*conf_alias = NULL;
 ConfigResource	*config_resources = NULL;
@@ -241,6 +241,7 @@ MODVAR ConfigFile		*conf = NULL;
 extern NameValueList *config_defines;
 MODVAR int ipv6_disabled = 0;
 MODVAR Client *remote_rehash_client = NULL;
+MODVAR json_t *json_rehash_log = NULL;
 
 MODVAR int			config_error_flag = 0;
 int			config_verbose = 0;
@@ -1413,8 +1414,6 @@ void config_error(FORMAT_STRING(const char *format), ...)
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
 	unreal_log_raw(ULOG_ERROR, "config", "CONFIG_ERROR_GENERIC", NULL, buffer);
-	if (remote_rehash_client)
-		sendnotice(remote_rehash_client, "error: %s", buffer);
 	/* We cannot live with this */
 	config_error_flag = 1;
 }
@@ -1471,8 +1470,6 @@ void config_status(FORMAT_STRING(const char *format), ...)
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
 	unreal_log_raw(ULOG_INFO, "config", "CONFIG_INFO_GENERIC", NULL, buffer);
-	if (remote_rehash_client)
-		sendnotice(remote_rehash_client, "%s", buffer);
 }
 
 void config_warn(FORMAT_STRING(const char *format), ...)
@@ -1487,8 +1484,6 @@ void config_warn(FORMAT_STRING(const char *format), ...)
 	if ((ptr = strchr(buffer, '\n')) != NULL)
 		*ptr = '\0';
 	unreal_log_raw(ULOG_WARNING, "config", "CONFIG_WARNING_GENERIC", NULL, buffer);
-	if (remote_rehash_client)
-		sendnotice(remote_rehash_client, "[warning] %s", buffer);
 }
 
 void config_warn_duplicate(const char *filename, int line, const char *entry)
@@ -1731,7 +1726,7 @@ void config_setdefaultsettings(Configuration *i)
 	safe_strdup(i->tls_options->trusted_ca_file, tmp);
 	safe_strdup(i->tls_options->ciphers, UNREALIRCD_DEFAULT_CIPHERS);
 	safe_strdup(i->tls_options->ciphersuites, UNREALIRCD_DEFAULT_CIPHERSUITES);
-	i->tls_options->protocols = TLS_PROTOCOL_ALL;
+	i->tls_options->protocols = TLS_PROTOCOL_TLSV1_2|TLS_PROTOCOL_TLSV1_3; /* TLSv1.2 & TLSv1.3 */
 #ifdef HAS_SSL_CTX_SET1_CURVES_LIST
 	safe_strdup(i->tls_options->ecdh_curves, UNREALIRCD_DEFAULT_ECDH_CURVES);
 #endif
@@ -1979,6 +1974,7 @@ void config_load_failed(void)
 {
 	if (conf)
 		unreal_log(ULOG_ERROR, "config", "CONFIG_NOT_LOADED", NULL, "IRCd configuration failed to load");
+	loop.config_status = CONFIG_STATUS_ROLLBACK;
 	Unload_all_testing_modules();
 	free_all_config_resources();
 	config_free(conf);
@@ -2051,6 +2047,7 @@ int config_test(void)
 	}
 
 	config_status("Testing IRCd configuration..");
+	loop.config_status = CONFIG_STATUS_TEST;
 
 	memset(&tempiConf, 0, sizeof(iConf));
 	memset(&settings, 0, sizeof(settings));
@@ -2067,6 +2064,8 @@ int config_test(void)
 		return -1;
 	}
 
+	loop.config_status = CONFIG_STATUS_POSTTEST;
+
 	preprocessor_resolve_conditionals_all(PREPROCESSOR_PHASE_MODULE);
 
 	if (!config_test_all())
@@ -2075,6 +2074,7 @@ int config_test(void)
 		config_load_failed();
 		return -1;
 	}
+	loop.config_status = CONFIG_STATUS_PRE_INIT;
 	callbacks_switchover();
 	efunctions_switchover();
 	set_targmax_defaults();
@@ -2085,8 +2085,6 @@ int config_test(void)
 		safe_strdup(old_pid_file, conf_files->pid_file);
 		unrealdns_delasyncconnects();
 		config_rehash();
-		Unload_all_loaded_modules();
-
 		/* Notify permanent modules of the rehash */
 		for (h = Hooks[HOOKTYPE_REHASH]; h; h = h->next)
 		{
@@ -2096,11 +2094,15 @@ int config_test(void)
 				continue;
 			(*(h->func.intfunc))();
 		}
+		/* Last step: */
+		Unload_all_loaded_modules();
 	}
 	config_pre_run_log();
 
+	loop.config_status = CONFIG_STATUS_INIT;
 	Init_all_testing_modules();
 
+	loop.config_status = CONFIG_STATUS_RUN_CONFIG;
 	if (config_run_blocks() < 0)
 	{
 		config_error("Bad case of config errors. Server will now die. This really shouldn't happen");
@@ -2124,12 +2126,13 @@ int config_test(void)
 	conf = NULL;
 	if (loop.rehashing)
 	{
+		/* loop.config_status = CONFIG_STATUS_LOAD is done by module_loadall() */
 		module_loadall();
 		RunHook(HOOKTYPE_REHASH_COMPLETE);
 	}
+	loop.config_status = CONFIG_STATUS_POSTLOAD;
 	postconf();
 	unreal_log(ULOG_INFO, "config", "CONFIG_LOADED", NULL, "Configuration loaded");
-	clicap_post_rehash();
 	unload_all_unused_mtag_handlers();
 	return 0;
 }
@@ -2230,6 +2233,8 @@ int config_read_file(const char *filename, const char *display_name)
 			if (!strcmp(ce->name, "blacklist-module"))
 				 _test_blacklist_module(cfptr, ce);
 
+		preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_SECONDARY);
+
 		/* Load urls */
 		config_parse_and_queue_urls(cfptr->items);
 
@@ -2326,7 +2331,6 @@ void config_rehash()
 	ConfigItem_listen	 	*listen_ptr;
 	ConfigItem_tld			*tld_ptr;
 	ConfigItem_vhost		*vhost_ptr;
-	ConfigItem_deny_link		*deny_link_ptr;
 	ConfigItem_deny_channel		*deny_channel_ptr;
 	ConfigItem_allow_channel	*allow_channel_ptr;
 	ConfigItem_admin		*admin_ptr;
@@ -2471,14 +2475,6 @@ void config_rehash()
 
 	remove_config_tkls();
 
-	for (deny_link_ptr = conf_deny_link; deny_link_ptr; deny_link_ptr = (ConfigItem_deny_link *) next) {
-		next = (ListStruct *)deny_link_ptr->next;
-		safe_free(deny_link_ptr->prettyrule);
-		unreal_delete_masks(deny_link_ptr->mask);
-		crule_free(&deny_link_ptr->rule);
-		DelListItem(deny_link_ptr, conf_deny_link);
-		safe_free(deny_link_ptr);
-	}
 	for (deny_version_ptr = conf_deny_version; deny_version_ptr; deny_version_ptr = (ConfigItem_deny_version *) next) {
 		next = (ListStruct *)deny_version_ptr->next;
 		safe_free(deny_version_ptr->mask);
@@ -3056,15 +3052,14 @@ ConfigItem_tld *find_tld(Client *client)
 	return NULL;
 }
 
-
-ConfigItem_link *find_link(const char *servername, Client *client)
+/** Find a link block by server name (but don't check any restrictions like IP or auth) */
+ConfigItem_link *find_link(const char *servername)
 {
 	ConfigItem_link	*link;
 
 	for (link = conf_link; link; link = link->next)
 	{
-		if (match_simple(link->servername, servername) &&
-		    user_allowed_by_security_group(client, link->incoming.match))
+		if (!link->flag.temporary && match_simple(link->servername, servername))
 		{
 		    return link;
 		}
@@ -4111,6 +4106,12 @@ int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
 						cep->line_number, "oper::vhost");
 					continue;
 				}
+				if (!valid_vhost(cep->value))
+				{
+					config_error("%s:%i: oper::vhost contains illegal characters or is too long: '%s'",
+					             cep->file->filename, cep->line_number, cep->value);
+					errors++;
+				}
 				has_vhost = 1;
 			}
 			/* oper::snomask */
@@ -4900,43 +4901,141 @@ int     _test_tld(ConfigFile *conf, ConfigEntry *ce)
 		             ce->file->filename, ce->line_number, ce->name);
 		errors++;
 	}
-	if (!has_motd)
+	return errors;
+}
+
+/* Helper for _conf_listen() */
+void conf_listen_configure(const char *ip, int port, SocketType socket_type, int options, ConfigEntry *ce, ConfigEntry *tlsconfig)
+{
+	ConfigItem_listen *listen;
+	ConfigEntry *cep, *cepp;
+	Hook *h;
+	char isnew = 0;
+
+	if (!(listen = find_listen(ip, port, socket_type)))
 	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"tld::motd");
-		errors++;
+		listen = safe_alloc(sizeof(ConfigItem_listen));
+		if (socket_type == SOCKET_TYPE_UNIX)
+		{
+			safe_strdup(listen->file, ip);
+		} else {
+			safe_strdup(listen->ip, ip);
+			listen->port = port;
+		}
+		listen->fd = -1;
+		listen->socket_type = socket_type;
+		isnew = 1;
 	}
-	if (!has_rules)
+
+	if (listen->options & LISTENER_BOUND)
+		options |= LISTENER_BOUND;
+	listen->options = options;
+
+	if (isnew)
+		AddListItem(listen, conf_listen);
+
+	/* Reset all settings of the current listener (free and set defaults): */
+	listen->flag.temporary = 0;
+	listen->start_handshake = start_of_normal_client_handshake;
+	if (listen->ssl_ctx)
 	{
-		config_error_missing(ce->file->filename, ce->line_number,
-			"tld::rules");
-		errors++;
+		SSL_CTX_free(listen->ssl_ctx);
+		listen->ssl_ctx = NULL;
+	}
+	if (listen->tls_options)
+	{
+		free_tls_options(listen->tls_options);
+		listen->tls_options = NULL;
+	}
+	safe_free(listen->websocket_forward);
+	safe_free(listen->webserver);
+
+	/* Now set the new settings: */
+	if (tlsconfig)
+	{
+		listen->tls_options = safe_alloc(sizeof(TLSOptions));
+		conf_tlsblock(conf, tlsconfig, listen->tls_options);
+		listen->ssl_ctx = init_ctx(listen->tls_options, 1);
+	}
+
+	/* For modules that hook CONFIG_LISTEN and CONFIG_LISTEN_OPTIONS.
+	 * Yeah, ugly we have this here..
+	 * and again about 100 lines down too.
+	 */
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mode"))
+		{
+			/* Yeah, we actually do something with this one.. */
+			if (cep->value)
+				listen->mode = strtol(cep->value, NULL, 8); /* octal */
+		}
+		else if (!strcmp(cep->name, "spoof-ip"))
+			safe_strdup(listen->spoof_ip, cep->value);
+		else if (!strcmp(cep->name, "ip"))
+			;
+		else if (!strcmp(cep->name, "port"))
+			;
+		else if (!strcmp(cep->name, "options"))
+		{
+			for (cepp = cep->items; cepp; cepp = cepp->next)
+			{
+				NameValue *ofp;
+				if (!nv_find_by_name(_ListenerFlags, cepp->name))
+				{
+					for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
+					{
+						int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS, listen);
+						if (value == 1)
+							break;
+					}
+				}
+			}
+		} else
+		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
+			;
+		else
+		{
+			for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
+			{
+				int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN, listen);
+				if (value == 1)
+					break;
+			}
+		}
 	}
-	return errors;
 }
 
 int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 {
-	ConfigEntry *cep;
-	ConfigEntry *cepp;
+	ConfigEntry *cep, *cepp;
 	ConfigEntry *tlsconfig = NULL;
-	ConfigItem_listen *listen = NULL;
 	char *file = NULL;
 	char *ip = NULL;
-	int start=0, end=0, port, isnew;
-	int tmpflags =0;
+	char *spoof_ip = NULL;
+	int start=0, end=0, port;
+	int listener_flags =0;
 	Hook *h;
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (!strcmp(cep->name, "file"))
 		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
 			file = cep->value;
 		} else
+		if (!strcmp(cep->name, "mode"))
+		{
+			// Handled elsewhere, but need to be caught here as noop
+		} else
 		if (!strcmp(cep->name, "ip"))
 		{
 			ip = cep->value;
 		} else
+		if (!strcmp(cep->name, "spoof-ip"))
+		{
+			spoof_ip = cep->value;
+		} else
 		if (!strcmp(cep->name, "port"))
 		{
 			port_range(cep->value, &start, &end);
@@ -4950,7 +5049,7 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 				long v;
 				if ((v = nv_find_by_name(_ListenerFlags, cepp->name)))
 				{
-					tmpflags |= v;
+					listener_flags |= v;
 				} else {
 					for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
 					{
@@ -4975,28 +5074,10 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 		}
 	}
 
-	/* UNIX domain socket code */
+	/* UNIX domain socket */
 	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;
-
+		conf_listen_configure(file, 0, SOCKET_TYPE_UNIX, listener_flags, ce, tlsconfig);
 		return 1;
 	}
 
@@ -5004,171 +5085,13 @@ int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
 	{
 		/* First deal with IPv4 */
 		if (!strchr(ip, ':'))
-		{
-			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->socket_type = SOCKET_TYPE_IPV4;
-				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;
-
-			if (listen->ssl_ctx)
-			{
-				SSL_CTX_free(listen->ssl_ctx);
-				listen->ssl_ctx = NULL;
-			}
-
-			if (listen->tls_options)
-			{
-				free_tls_options(listen->tls_options);
-				listen->tls_options = NULL;
-			}
-
-			if (tlsconfig)
-			{
-				listen->tls_options = safe_alloc(sizeof(TLSOptions));
-				conf_tlsblock(conf, tlsconfig, listen->tls_options);
-				listen->ssl_ctx = init_ctx(listen->tls_options, 1);
-			}
-			
-			safe_free(listen->websocket_forward);
-
-			/* For modules that hook CONFIG_LISTEN and CONFIG_LISTEN_OPTIONS.
-			 * Yeah, ugly we have this here..
-			 * and again about 100 lines down too.
-			 */
-			for (cep = ce->items; cep; cep = cep->next)
-			{
-				if (!strcmp(cep->name, "ip"))
-					;
-				else if (!strcmp(cep->name, "port"))
-					;
-				else if (!strcmp(cep->name, "options"))
-				{
-					for (cepp = cep->items; cepp; cepp = cepp->next)
-					{
-						NameValue *ofp;
-						if (!nv_find_by_name(_ListenerFlags, cepp->name))
-						{
-							for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
-							{
-								int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS, listen);
-								if (value == 1)
-									break;
-							}
-						}
-					}
-				} else
-				if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
-					;
-				else
-				{
-					for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
-					{
-						int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN, listen);
-						if (value == 1)
-							break;
-					}
-				}
-			}
-		}
+			conf_listen_configure(ip, port, SOCKET_TYPE_IPV4, listener_flags, ce, tlsconfig);
 
 		/* Then deal with IPv6 (if available/enabled) */
-		if (!DISABLE_IPV6)
-		{
-			if (strchr(ip, ':') || (*ip == '*'))
-			{
-				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->socket_type = SOCKET_TYPE_IPV6;
-					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;
-
-				if (listen->ssl_ctx)
-				{
-					SSL_CTX_free(listen->ssl_ctx);
-					listen->ssl_ctx = NULL;
-				}
-
-				if (listen->tls_options)
-				{
-					free_tls_options(listen->tls_options);
-					listen->tls_options = NULL;
-				}
-
-				if (tlsconfig)
-				{
-					listen->tls_options = safe_alloc(sizeof(TLSOptions));
-					conf_tlsblock(conf, tlsconfig, listen->tls_options);
-					listen->ssl_ctx = init_ctx(listen->tls_options, 1);
-				}
-				
-				safe_free(listen->websocket_forward);
-				
-				/* For modules that hook CONFIG_LISTEN and CONFIG_LISTEN_OPTIONS.
-				 * Yeah, ugly we have this here..
-				 */
-				for (cep = ce->items; cep; cep = cep->next)
-				{
-					if (!strcmp(cep->name, "ip"))
-						;
-					else if (!strcmp(cep->name, "port"))
-						;
-					else if (!strcmp(cep->name, "options"))
-					{
-						for (cepp = cep->items; cepp; cepp = cepp->next)
-						{
-							if (!nv_find_by_name(_ListenerFlags, cepp->name))
-							{
-								for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
-								{
-									int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS, listen);
-									if (value == 1)
-										break;
-								}
-							}
-						}
-					} else
-					if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
-						;
-					else
-					{
-						for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
-						{
-							int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN, listen);
-							if (value == 1)
-								break;
-						}
-					}
-				}
-			}
-		}
+		if (!DISABLE_IPV6 && (strchr(ip, ':') || (*ip == '*')))
+			conf_listen_configure(ip, port, SOCKET_TYPE_IPV6, listener_flags, ce, tlsconfig);
 	}
+
 	return 1;
 }
 
@@ -5177,7 +5100,7 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 	ConfigEntry *cep;
 	ConfigEntry *cepp;
 	int errors = 0;
-	char has_file = 0, 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, has_spoof_ip = 0;
 	char *file = NULL;
 	char *ip = NULL;
 	Hook *h;
@@ -5298,6 +5221,28 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 			has_file = 1;
 			file = cep->value;
 		} else
+		if (!strcmp(cep->name, "spoof-ip"))
+		{
+			has_spoof_ip = 1;
+			if (!is_valid_ip(cep->value))
+			{
+				config_error("%s:%i: listen::spoof-ip is not a valid IP address (%s)",
+				             cep->file->filename, cep->line_number, cep->value);
+				errors++;
+			}
+		} else
+		if (!strcmp(cep->name, "mode"))
+		{
+			int mode = strtol(cep->value, NULL, 8);
+			if ((mode != 0700) && (mode != 0770) && (mode != 0777))
+			{
+				config_error("%s:%i: listen::mode must be one of: 0700 (user only, the default), "
+				             "0770 (user and group readable/writable), or "
+				             "0777 (world readable and writable, not recommended).",
+				             cep->file->filename, cep->line_number);
+				errors++;
+			}
+		} else
 		if (!strcmp(cep->name, "ip"))
 		{
 			has_ip = 1;
@@ -5398,6 +5343,13 @@ int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
 		}
 	}
 
+	if (has_spoof_ip && !has_file)
+	{
+		config_error("%s:%d: listen::spoof-ip is only valid when listen::file is used (UNIX domain sockets)",
+		             ce->file->filename, ce->line_number);
+		errors++;
+	}
+
 	if (port_6667)
 		safe_strdup(port_6667_ip, ip);
 
@@ -6053,40 +6005,12 @@ int	_test_vhost(ConfigFile *conf, ConfigEntry *ce)
 				errors++;
 				continue;
 			}
-			if ((at = strchr(cep->value, '@')))
-			{
-				for (tmp = cep->value; tmp != at; tmp++)
-				{
-					if (*tmp == '~' && tmp == cep->value)
-						continue;
-					if (!isallowed(*tmp))
-						break;
-				}
-				if (tmp != at)
-				{
-					config_error("%s:%i: vhost::vhost contains an invalid ident",
-						cep->file->filename, cep->line_number);
-					errors++;
-				}
-				host = at+1;
-			}
-			else
-				host = cep->value;
-			if (!*host)
+			if (!valid_vhost(cep->value))
 			{
-				config_error("%s:%i: vhost::vhost does not have a host set",
-					cep->file->filename, cep->line_number);
+				config_error("%s:%i: oper::vhost contains illegal characters or is too long: '%s'",
+					     cep->file->filename, cep->line_number, cep->value);
 				errors++;
 			}
-			else
-			{
-				if (!valid_host(host, 0))
-				{
-					config_error("%s:%i: vhost::vhost contains an invalid host",
-						cep->file->filename, cep->line_number);
-					errors++;
-				}
-			}
 		}
 		else if (!strcmp(cep->name, "login"))
 		{
@@ -7644,6 +7568,18 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 				int lag_penalty_bytes = -1;
 				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 				{
+					/* Check hooks first */
+					int used = 0;
+					for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
+					{
+						used = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD);
+						if (used == 1)
+							break;
+					}
+					if (used == 1)
+						continue; /* module handled it */
+					if (used == 2)
+						break; /* module handled it and we must stop entire block processing */
 					if (!strcmp(ceppp->name, "handshake-data-flood"))
 					{
 						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
@@ -7697,7 +7633,7 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 						tempiConf.throttle_count = cnt;
 						tempiConf.throttle_period = period;
 					}
-					if (!strcmp(ceppp->name, "max-concurrent-conversations"))
+					else if (!strcmp(ceppp->name, "max-concurrent-conversations"))
 					{
 						/* We use a hack here to make it fit our storage format */
 						char buf[64];
@@ -7717,15 +7653,6 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 						snprintf(buf, sizeof(buf), "%d:%ld", users, every);
 						config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_CONVERSATIONS);
 					}
-					else
-					{
-						for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
-						{
-							int value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD);
-							if (value == 1)
-								break;
-						}
-					}
 				}
 				if ((lag_penalty != -1) && (lag_penalty_bytes != -1))
 				{
@@ -7840,6 +7767,10 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 				{
 					tempiConf.spamfilter_stop_on_first_match = config_checkval(cepp->value, CFG_YESNO);
 				}
+				else if (!strcmp(cepp->name, "utf8"))
+				{
+					tempiConf.spamfilter_utf8 = config_checkval(cepp->value, CFG_YESNO);
+				}
 			}
 		}
 		else if (!strcmp(cep->name, "default-bantime"))
@@ -8006,6 +7937,12 @@ int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
 				if (!strcmp(cepp->name, "policy"))
 					tempiConf.hide_idle_time = hideidletime_strtoval(cepp->value);
 			}
+		} else if (!strcmp(cep->name, "limit-svscmds"))
+		{
+			if (!strcmp(cep->value, "ulines"))
+				tempiConf.limit_svscmds = LIMIT_SVSCMDS_ULINES;
+			else
+				tempiConf.limit_svscmds = LIMIT_SVSCMDS_SERVERS;
 		} else
 		{
 			int value;
@@ -8477,9 +8414,52 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 
 				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
 				{
-					int everyone = !strcmp(cepp->name, "everyone") ? 1 : 0;
-					int for_everyone = flood_option_is_for_everyone(ceppp->name);
+					int everyone;
+					int for_everyone;
+					int used = 0;
+					Hook *h;
+
+					/* First, check hooks... */
+					for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
+					{
+						int value, errs = 0;
+						if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
+							&& !(h->owner->options & MOD_OPT_PERM))
+							continue;
+						value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD,&errs);
+						if (value == 2)
+						{
+							used = 2;
+							break;
+						} else
+						if (value == 1)
+						{
+							used = 1;
+							break;
+						} else
+						if (value == -1)
+						{
+							used = 1;
+							errors += errs;
+							break;
+						} else
+						if (value == -2)
+						{
+							used = 2;
+							errors += errs;
+							break;
+						}
+					}
+					if (used == 1)
+						continue; /* module handled it */
+					if (used == 2)
+						break; /* module handled it and we must stop entire block processing */
 
+					/* Prevent users from using options that belong in "everyone"
+					 * at other places, and vice-versa.
+					 */
+					everyone = !strcmp(cepp->name, "everyone") ? 1 : 0;
+					for_everyone = flood_option_is_for_everyone(ceppp->name);
 					if (everyone && !for_everyone)
 					{
 						config_error("%s:%i: %s cannot be in the set::anti-flood::everyone block. "
@@ -8724,43 +8704,10 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					}
 					else
 					{
-						/* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */
-						int used = 0;
-						Hook *h;
-						for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
-						{
-							int value, errs = 0;
-							if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
-								&& !(h->owner->options & MOD_OPT_PERM))
-								continue;
-							value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD,&errs);
-							if (value == 2)
-								used = 1;
-							if (value == 1)
-							{
-								used = 1;
-								break;
-							}
-							if (value == -1)
-							{
-								used = 1;
-								errors += errs;
-								break;
-							}
-							if (value == -2)
-							{
-								used = 1;
-								errors += errs;
-							}
-						}
-						if (!used)
-						{
-							config_error_unknownopt(ceppp->file->filename,
-								ceppp->line_number, "set::anti-flood",
-								ceppp->name);
-							errors++;
-						}
-						continue;
+						config_error_unknownopt(ceppp->file->filename,
+							ceppp->line_number, "set::anti-flood",
+							ceppp->name);
+						errors++;
 					}
 				}
 				if (has_lag_penalty+has_lag_penalty_bytes == 1)
@@ -8966,6 +8913,9 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 				if (!strcmp(cepp->name, "stop-on-first-match"))
 				{
 				} else
+				if (!strcmp(cepp->name, "utf8"))
+				{
+				} else
 				{
 					config_error_unknown(cepp->file->filename,
 						cepp->line_number, "set::spamfilter",
@@ -9351,6 +9301,15 @@ int	_test_set(ConfigFile *conf, ConfigEntry *ce)
 					continue;
 				}
 			}
+		} else if (!strcmp(cep->name, "limit-svscmds"))
+		{
+			CheckNull(cep);
+			if (strcmp(cep->value, "servers") && strcmp(cep->value, "ulines"))
+			{
+				config_error("%s:%i: set::limit-svscmds: value must be one of: 'servers' or 'ulines'",
+				             cep->file->filename, cep->line_number);
+				errors++;
+			}
 		} else
 		{
 			int used = 0;
@@ -9436,20 +9395,6 @@ int	_test_blacklist_module(ConfigFile *conf, ConfigEntry *ce)
 
 	path = Module_TransformPath(ce->value);
 
-	/* Is it a good idea to warn about this?
-	 * Yes, the user may have made a typo, thinking (s)he blacklisted something
-	 *      but due to the typo the blacklist-module is not effective.
-	 *  No, the user may have blacklisted a bunch of modules of which not all may
-	 *      be installed at the time.
-	 * Hmmmmmm.
-	 */
-	if (!file_exists(path))
-	{
-		config_warn("%s:%i: blacklist-module for '%s' but module does not exist anyway",
-			ce->file->filename, ce->line_number, ce->value);
-		/* fallthrough */
-	}
-
 	m = safe_alloc(sizeof(ConfigItem_blacklist_module));
 	safe_strdup(m->name, ce->value);
 	AddListItem(m, conf_blacklist_module);
@@ -9463,7 +9408,7 @@ int is_blacklisted_module(const char *name)
 	ConfigItem_blacklist_module *m;
 
 	for (m = conf_blacklist_module; m; m = m->next)
-		if (!strcasecmp(m->name, name) || !strcasecmp(m->name, path))
+		if (match_simple(m->name, name) || match_simple(m->name, path))
 			return 1;
 
 	return 0;
@@ -9579,9 +9524,11 @@ void start_listeners(void)
 /* Actually use configuration */
 void config_run(void)
 {
+	loop.config_status = CONFIG_STATUS_POSTLOAD;
 	extcmodes_check_for_changes();
 	start_listeners();
-	add_proc_io_server();
+	if (!loop.booted)
+		add_proc_io_server();
 	free_all_config_resources();
 }
 
@@ -9947,8 +9894,6 @@ Hook *h;
 
 	if (!strcmp(ce->value, "channel"))
 		_conf_deny_channel(conf, ce);
-	else if (!strcmp(ce->value, "link"))
-		_conf_deny_link(conf, ce);
 	else if (!strcmp(ce->value, "version"))
 		_conf_deny_version(conf, ce);
 	else
@@ -10001,34 +9946,6 @@ int	_conf_deny_channel(ConfigFile *conf, ConfigEntry *ce)
 	AddListItem(deny, conf_deny_channel);
 	return 0;
 }
-int	_conf_deny_link(ConfigFile *conf, ConfigEntry *ce)
-{
-	ConfigItem_deny_link 	*deny = NULL;
-	ConfigEntry 	    	*cep;
-
-	deny = safe_alloc(sizeof(ConfigItem_deny_link));
-	for (cep = ce->items; cep; cep = cep->next)
-	{
-		if (!strcmp(cep->name, "mask"))
-		{
-			unreal_add_masks(&deny->mask, cep);
-		}
-		else if (!strcmp(cep->name, "rule"))
-		{
-			deny->rule = (char *)crule_parse(cep->value);
-			safe_strdup(deny->prettyrule, cep->value);
-		}
-		else if (!strcmp(cep->name, "type")) {
-			if (!strcmp(cep->value, "all"))
-				deny->flag.type = CRULE_ALL;
-			else if (!strcmp(cep->value, "auto"))
-				deny->flag.type = CRULE_AUTO;
-		}
-	}
-	AddListItem(deny, conf_deny_link);
-	return 0;
-}
-
 int	_conf_deny_version(ConfigFile *conf, ConfigEntry *ce)
 {
 	ConfigItem_deny_version *deny = NULL;
@@ -10164,102 +10081,6 @@ int     _test_deny(ConfigFile *conf, ConfigEntry *ce)
 			errors++;
 		}
 	}
-	else if (!strcmp(ce->value, "link"))
-	{
-		char has_mask = 0, has_rule = 0, has_type = 0;
-		for (cep = ce->items; cep; cep = cep->next)
-		{
-			if (!cep->items)
-			{
-				if (config_is_blankorempty(cep, "deny link"))
-				{
-					errors++;
-					continue;
-				}
-				else if (!strcmp(cep->name, "mask"))
-				{
-					has_mask = 1;
-				} else if (!strcmp(cep->name, "rule"))
-				{
-					int val = 0;
-					if (has_rule)
-					{
-						config_warn_duplicate(cep->file->filename,
-							cep->line_number, "deny link::rule");
-						continue;
-					}
-					has_rule = 1;
-					if ((val = crule_test(cep->value)))
-					{
-						config_error("%s:%i: deny link::rule contains an invalid expression: %s",
-							cep->file->filename,
-							cep->line_number,
-							crule_errstring(val));
-						errors++;
-					}
-				}
-				else if (!strcmp(cep->name, "type"))
-				{
-					if (has_type)
-					{
-						config_warn_duplicate(cep->file->filename,
-							cep->line_number, "deny link::type");
-						continue;
-					}
-					has_type = 1;
-					if (!strcmp(cep->value, "auto"))
-					;
-					else if (!strcmp(cep->value, "all"))
-					;
-					else {
-						config_status("%s:%i: unknown deny link type",
-						cep->file->filename, cep->line_number);
-						errors++;
-					}
-				}
-				else
-				{
-					config_error_unknown(cep->file->filename,
-						cep->line_number, "deny link", cep->name);
-					errors++;
-				}
-			}
-			else
-			{
-				// Sections
-				if (!strcmp(cep->name, "mask"))
-				{
-					if (cep->value || cep->items)
-						has_mask = 1;
-				}
-				else
-				{
-					config_error_unknown(cep->file->filename,
-						cep->line_number, "deny link", cep->name);
-					errors++;
-					continue;
-				}
-			}
-		}
-		if (!has_mask)
-		{
-			config_error_missing(ce->file->filename, ce->line_number,
-				"deny link::mask");
-			errors++;
-		}
-		if (!has_rule)
-		{
-			config_error_missing(ce->file->filename, ce->line_number,
-				"deny link::rule");
-			errors++;
-		}
-		if (!has_type)
-		{
-			config_error_missing(ce->file->filename, ce->line_number,
-				"deny link::type");
-			errors++;
-		}
-	}
 	else if (!strcmp(ce->value, "version"))
 	{
 		char has_mask = 0, has_version = 0, has_flags = 0;
@@ -10733,14 +10554,17 @@ void resource_download_complete(const char *url, const char *file, const char *e
 }
 
 /** Request to REHASH the configuration file.
- * There is no guarantee that the request will be done immediately
- * (eg: it won't in case of remote includes).
+ * The rehash will not be done immediately, just scheduled.
+ * This means this function can safely be called from modules or
+ * other areas.
  * @param client	The client requesting the /REHASH.
  *                      If this is NULL then the rehash was requested
  *                      via a signal to the process or GUI.
  */
 void request_rehash(Client *client)
 {
+	json_t *j;
+
 	if (loop.rehashing)
 	{
 		if (client)
@@ -10748,17 +10572,26 @@ void request_rehash(Client *client)
 		return;
 	}
 
+	/* Free any old json_rehash_log */
+	if (json_rehash_log)
+	{
+		json_decref(json_rehash_log);
+		json_rehash_log = NULL;
+	}
+
+	/* Start a new json_rehash_log */
+	json_rehash_log = json_object();
+	if (client)
+		json_expand_client(json_rehash_log, "rehash_client", client, 99);
+	j = json_array();
+	json_object_set_new(json_rehash_log, "log", j);
+
+	/* Now actually process the rehash request... */
 	loop.rehashing = 1;
 	loop.rehash_save_client = client;
 	config_read_start();
-	/* If we already have everything, then can we proceed with the rehash */
-	if (is_config_read_finished())
-	{
-		rehash_internal(client);
-		return;
-	}
-	/* Otherwise, I/O events will take care of it later
-	 * after all remote includes have been downloaded.
+	/* More config reading (or network I/O), and the actual rehash will
+	 * happen in "the main loop". See end of SocketLoop() in src/ircd.c.
 	 */
 }
 
@@ -10768,7 +10601,7 @@ int rehash_internal(Client *client)
 
 	/* 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]");
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", NULL, "Rehashing server configuration file [./unrealircd rehash]");
 
 	loop.rehashing = 2; /* now doing the actual rehash */
 
@@ -10782,7 +10615,9 @@ int rehash_internal(Client *client)
 	unload_all_unused_extbans();
 	unload_all_unused_caps();
 	unload_all_unused_history_backends();
+	unload_all_unused_rpc_handlers();
 	// unload_all_unused_moddata(); -- this will crash
+	clicap_check_for_changes();
 	umodes_check_for_changes();
 	charsys_check_for_changes();
 
@@ -10790,6 +10625,10 @@ int rehash_internal(Client *client)
 	loop.rehashing = 0;
 	remote_rehash_client = NULL;
 	procio_post_rehash(failure);
+	json_object_set_new(json_rehash_log, "success", json_boolean(failure ? 0 : 1));
+	RunHook(HOOKTYPE_REHASH_LOG, failure, json_rehash_log);
+
+	loop.config_status = CONFIG_STATUS_COMPLETE;
 	return 1;
 }
 
@@ -10853,6 +10692,7 @@ void	listen_cleanup()
 			safe_free(listen_ptr->ip);
 			free_tls_options(listen_ptr->tls_options);
 			DelListItem(listen_ptr, conf_listen);
+			safe_free(listen_ptr->webserver);
 			safe_free(listen_ptr->websocket_forward);
 			safe_free(listen_ptr);
 			i++;
@@ -10988,6 +10828,7 @@ void free_all_config_resources(void)
 		rs->wce = NULL;
 		if (rs->type & RESOURCE_REMOTE)
 		{
+			url_cancel_handle_by_callback_data(rs);
 			/* Delete the file, but only if it's not a cached version */
 			if (rs->file && strncmp(rs->file, CACHEDIR, strlen(CACHEDIR)))
 			{
diff --git a/src/conf_preprocessor.c b/src/conf_preprocessor.c
@@ -348,7 +348,17 @@ int preprocessor_resolve_if(ConditionalConfig *cc, PreprocessorPhase phase)
 	if (cc->condition == IF_MODULE)
 	{
 		if (phase == PREPROCESSOR_PHASE_INITIAL)
-			return 1; /* we cannot handle @if module-loaded() yet.. */
+		{
+			/* We cannot handle @if module-loaded() yet.. */
+			return 1;
+		}
+		if (phase == PREPROCESSOR_PHASE_SECONDARY)
+		{
+			/* We can only handle blacklisted modules at this point, so: */
+			if (is_blacklisted_module(cc->name))
+				return 0;
+			return 1;
+		}
 		if (is_module_loaded(cc->name))
 		{
 			result = 1;
diff --git a/src/crule.c b/src/crule.c
@@ -1,9 +1,11 @@
-/*
- * SmartRoute phase 1
- * connection rule patch
+/**
+ * @file
+ * @brief Connection rule parser and checker
+ * @version $Id$
+ *
  * by Tony Vencill (Tonto on IRC) <vencill@bga.com>
  *
- * The majority of this file is a recusive descent parser used to convert
+ * The majority of this file is a recursive descent parser used to convert
  * connection rules into expression trees when the conf file is read.
  * All parsing structures and types are hidden in the interest of good
  * programming style and to make possible future data structure changes
@@ -29,6 +31,35 @@
  * more closely simulate the actual ircd environment).  crule_eval and
  * the rule functions are made empty functions as in the stand-alone
  * test parser.
+ *
+ * The production rules for the grammar are as follows ("rule" is the
+ * starting production):
+ *
+ *   rule:
+ *     orexpr END          END is end of input or :
+ *   orexpr:
+ *     andexpr
+ *     andexpr || orexpr
+ *   andexpr:
+ *     primary
+ *     primary && andexpr
+ *  primary:
+ *    function
+ *    ! primary
+ *    ( orexpr )
+ *  function:
+ *    word ( )             word is alphanumeric string, first character
+ *    word ( arglist )       must be a letter
+ *  arglist:
+ *    word
+ *    word , arglist
+ */
+
+/* Last update of parser functions taken from ircu on 2023-03-19
+ * matching ircu's ircd/crule.c from 2021-09-04.
+ * Then ported / UnrealIRCd-ized by Syzop and re-adding crule_test()
+ * and such. All the actual "functions" like crule_connected() are
+ * our own and not re-feteched (but were based on older versions).
  */
 
 #ifndef CR_DEBUG
@@ -57,56 +88,67 @@ ID_Copyright("(C) Tony Vincell");
 #define safe_free free
 #endif
 
-/* some constants and shared data types */
-#define CR_MAXARGLEN 80		/* why 80? why not? it's > hostname lengths */
-#define CR_MAXARGS 3		/* there's a better way to do this, but not now */
+/*
+ * Some symbols for easy reading
+ */
 
-/* some symbols for easy reading */
-enum crule_token
-    { CR_UNKNOWN, CR_END, CR_AND, CR_OR, CR_NOT, CR_OPENPAREN, CR_CLOSEPAREN,
-	CR_COMMA, CR_WORD
-};
-enum crule_errcode
-    { CR_NOERR, CR_UNEXPCTTOK, CR_UNKNWTOK, CR_EXPCTAND, CR_EXPCTOR,
-	CR_EXPCTPRIM, CR_EXPCTOPEN, CR_EXPCTCLOSE, CR_UNKNWFUNC, CR_ARGMISMAT
+/** Input scanner tokens. */
+enum crule_token {
+  CR_UNKNOWN,    /**< Unknown token type. */
+  CR_END,        /**< End of input ('\\0' or ':'). */
+  CR_AND,        /**< Logical and operator (&&). */
+  CR_OR,         /**< Logical or operator (||). */
+  CR_NOT,        /**< Logical not operator (!). */
+  CR_OPENPAREN,  /**< Open parenthesis. */
+  CR_CLOSEPAREN, /**< Close parenthesis. */
+  CR_COMMA,      /**< Comma. */
+  CR_WORD        /**< Something that looks like a hostmask (alphanumerics, "*?.-"). */
 };
 
-/* expression tree structure, function pointer, and tree pointer */
-/* local! */
-typedef int (*crule_funcptr) (int, void **);
-struct crule_treestruct {
-	crule_funcptr funcptr;
-	int  numargs;
-	void *arg[CR_MAXARGS];	/* for operators arg points to a tree element;
-				   for functions arg points to a char string */
+/** Parser error codes. */
+enum crule_errcode {
+  CR_NOERR,      /**< No error. */
+  CR_UNEXPCTTOK, /**< Invalid token given context. */
+  CR_UNKNWTOK,   /**< Input did not form a valid token. */
+  CR_EXPCTAND,   /**< Did not see expected && operator. */
+  CR_EXPCTOR,    /**< Did not see expected || operator. */
+  CR_EXPCTPRIM,  /**< Expected a primitive (parentheses, ! or word). */
+  CR_EXPCTOPEN,  /**< Expected an open parenthesis after function name. */
+  CR_EXPCTCLOSE, /**< Expected a close parenthesis to match open parenthesis. */
+  CR_UNKNWFUNC,  /**< Attempt to use an unknown function. */
+  CR_ARGMISMAT   /**< Wrong number of arguments to function. */
 };
-typedef struct crule_treestruct crule_treeelem;
-typedef crule_treeelem *crule_treeptr;
+
+/*
+ * Expression tree structure, function pointer, and tree pointer local!
+ */
 
 /* rule function prototypes - local! */
-int crule_connected(int, void **);
-int crule_directcon(int, void **);
-int crule_via(int, void **);
-int crule_directop(int, void **);
-int crule__andor(int, void **);
-int crule__not(int, void **);
+static int crule_connected(int, void **);
+static int crule_directcon(int, void **);
+static int crule_via(int, void **);
+static int crule_directop(int, void **);
+static int crule__andor(int, void **);
+static int crule__not(int, void **);
 
 /* parsing function prototypes - local! */
-int crule_gettoken(int *, char **);
-void crule_getword(char *, int *, int, char **);
-int crule_parseandexpr(crule_treeptr *, int *, char **);
-int crule_parseorexpr(crule_treeptr *, int *, char **);
-int crule_parseprimary(crule_treeptr *, int *, char **);
-int crule_parsefunction(crule_treeptr *, int *, char **);
-int crule_parsearglist(crule_treeptr, int *, char **);
+static int crule_gettoken(int* token, const char** str);
+static void crule_getword(char*, int*, size_t, const char**);
+static int crule_parseandexpr(CRuleNodePtr*, int *, const char**);
+static int crule_parseorexpr(CRuleNodePtr*, int *, const char**);
+static int crule_parseprimary(CRuleNodePtr*, int *, const char**);
+static int crule_parsefunction(CRuleNodePtr*, int *, const char**);
+static int crule_parsearglist(CRuleNodePtr, int *, const char**);
 
 #if defined(CR_DEBUG) || defined(CR_CHKCONF)
-/* prototypes for the test parser; if not debugging, these are
- * defined in h.h */
-char *crule_parse(char *);
-void crule_free(char **);
+/*
+ * Prototypes for the test parser; if not debugging,
+ * these are defined in h.h
+ */
+struct CRuleNode* crule_parse(const char*);
+void crule_free(struct CRuleNode**);
 #ifdef CR_DEBUG
-void print_tree(crule_treeptr));
+void print_tree(CRuleNodePtr);
 #endif
 #endif
 
@@ -139,7 +181,7 @@ struct crule_funclistent crule_funclist[] = {
 	{"", 0, NULL}		/* this must be here to mark end of list */
 };
 
-int  crule_connected(int numargs, void *crulearg[])
+static int crule_connected(int numargs, void *crulearg[])
 {
 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
 	Client *client;
@@ -156,7 +198,7 @@ int  crule_connected(int numargs, void *crulearg[])
 #endif
 }
 
-int  crule_directcon(int numargs, void *crulearg[])
+static int crule_directcon(int numargs, void *crulearg[])
 {
 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
 	Client *client;
@@ -175,7 +217,7 @@ int  crule_directcon(int numargs, void *crulearg[])
 #endif
 }
 
-int  crule_via(int numargs, void *crulearg[])
+static int crule_via(int numargs, void *crulearg[])
 {
 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
 	Client *client;
@@ -194,7 +236,7 @@ int  crule_via(int numargs, void *crulearg[])
 #endif
 }
 
-int  crule_directop(int numargs, void *crulearg[])
+static int crule_directop(int numargs, void *crulearg[])
 {
 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
 	Client *client;
@@ -212,577 +254,597 @@ int  crule_directop(int numargs, void *crulearg[])
 #endif
 }
 
-int  crule__andor(int numargs, void *crulearg[])
+/** Evaluate a connection rule.
+ * @param[in] rule Rule to evalute.
+ * @return Non-zero if the rule allows the connection, zero otherwise.
+ */
+int crule_eval(struct CRuleNode* rule)
 {
-	int  result1;
-
-	result1 = ((crule_treeptr) crulearg[0])->funcptr
-	    (((crule_treeptr) crulearg[0])->numargs,
-	    (void *)((crule_treeptr) crulearg[0])->arg);
-	if (crulearg[2])	/* or */
-		return (result1 ||
-		    ((crule_treeptr) crulearg[1])->funcptr
-		    (((crule_treeptr) crulearg[1])->numargs,
-		    (void *)((crule_treeptr) crulearg[1])->arg));
-	else
-		return (result1 &&
-		    ((crule_treeptr) crulearg[1])->funcptr
-		    (((crule_treeptr) crulearg[1])->numargs,
-		    (void *)((crule_treeptr) crulearg[1])->arg));
+  return (rule->funcptr(rule->numargs, rule->arg));
 }
 
-int  crule__not(int numargs, void *crulearg[])
+/** Perform an and-or-or test on crulearg[0] and crulearg[1].
+ * If crulearg[2] is non-NULL, it means do OR; if it is NULL, do AND.
+ * @param[in] numargs Number of valid args in \a crulearg.
+ * @param[in] crulearg Argument array.
+ * @return Non-zero if the condition is true, zero if not.
+ */
+static int crule__andor(int numargs, void *crulearg[])
 {
-	return (!((crule_treeptr) crulearg[0])->funcptr
-	    (((crule_treeptr) crulearg[0])->numargs,
-	    (void *)((crule_treeptr) crulearg[0])->arg));
+  int result1;
+
+  result1 = crule_eval(crulearg[0]);
+  if (crulearg[2])              /* or */
+    return (result1 || crule_eval(crulearg[1]));
+  else
+    return (result1 && crule_eval(crulearg[1]));
 }
 
-#if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
-int  crule_eval(rule)
-	char *rule;
+/** Logically invert the result of crulearg[0].
+ * @param[in] numargs Number of valid args in \a crulearg.
+ * @param[in] crulearg Argument array.
+ * @return Non-zero if the condition is true, zero if not.
+ */
+static int crule__not(int numargs, void *crulearg[])
 {
-	return (((crule_treeptr) rule)->funcptr
-	    (((crule_treeptr) rule)->numargs, ((crule_treeptr) rule)->arg));
+  return (!crule_eval(crulearg[0]));
 }
-#endif
 
-int  crule_gettoken(int *next_tokp, char **ruleptr)
+/** Scan an input token from \a ruleptr.
+ * @param[out] next_tokp Receives type of next token.
+ * @param[in,out] ruleptr Next readable character from input.
+ * @return Either CR_UNKNWTOK if the input was unrecognizable, else CR_NOERR.
+ */
+static int crule_gettoken(int* next_tokp, const char** ruleptr)
 {
-	char pending = '\0';
-
-	*next_tokp = CR_UNKNOWN;
-	while (*next_tokp == CR_UNKNOWN)
-		switch (*(*ruleptr)++)
-		{
-		  case ' ':
-		  case '\t':
-			  break;
-		  case '&':
-			  if (pending == '\0')
-				  pending = '&';
-			  else if (pending == '&')
-				  *next_tokp = CR_AND;
-			  else
-				  return (CR_UNKNWTOK);
-			  break;
-		  case '|':
-			  if (pending == '\0')
-				  pending = '|';
-			  else if (pending == '|')
-				  *next_tokp = CR_OR;
-			  else
-				  return (CR_UNKNWTOK);
-			  break;
-		  case '!':
-			  *next_tokp = CR_NOT;
-			  break;
-		  case '(':
-			  *next_tokp = CR_OPENPAREN;
-			  break;
-		  case ')':
-			  *next_tokp = CR_CLOSEPAREN;
-			  break;
-		  case ',':
-			  *next_tokp = CR_COMMA;
-			  break;
-		  case '\0':
-			  (*ruleptr)--;
-			  *next_tokp = CR_END;
-			  break;
-		  /* Both - and : can appear in hostnames so they must not be 
-		   * treated as separators -- codemastr */
-		  default:
-			  if ((isalpha(*(--(*ruleptr)))) || (**ruleptr == '*')
-			      || (**ruleptr == '?') || (**ruleptr == '.')
-			      || (**ruleptr == '-') || (**ruleptr == ':'))
-				  *next_tokp = CR_WORD;
-			  else
-				  return (CR_UNKNWTOK);
-			  break;
-		}
-	return CR_NOERR;
+  char pending = '\0';
+
+  *next_tokp = CR_UNKNOWN;
+  while (*next_tokp == CR_UNKNOWN)
+    switch (*(*ruleptr)++)
+    {
+      case ' ':
+      case '\t':
+        break;
+      case '&':
+        if (pending == '\0')
+          pending = '&';
+        else if (pending == '&')
+          *next_tokp = CR_AND;
+        else
+          return (CR_UNKNWTOK);
+        break;
+      case '|':
+        if (pending == '\0')
+          pending = '|';
+        else if (pending == '|')
+          *next_tokp = CR_OR;
+        else
+          return (CR_UNKNWTOK);
+        break;
+      case '!':
+        *next_tokp = CR_NOT;
+        break;
+      case '(':
+        *next_tokp = CR_OPENPAREN;
+        break;
+      case ')':
+        *next_tokp = CR_CLOSEPAREN;
+        break;
+      case ',':
+        *next_tokp = CR_COMMA;
+        break;
+      case '\0':
+        (*ruleptr)--;
+        *next_tokp = CR_END;
+        break;
+      case ':':
+        *next_tokp = CR_END;
+        break;
+      default:
+        if ((isalpha(*(--(*ruleptr)))) || (**ruleptr == '*') ||
+            (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-'))
+          *next_tokp = CR_WORD;
+        else
+          return (CR_UNKNWTOK);
+        break;
+    }
+  return CR_NOERR;
 }
 
-void crule_getword(char *word, int *wordlenp, int maxlen, char **ruleptr)
+/** Scan a word from \a ruleptr.
+ * @param[out] word Output buffer.
+ * @param[out] wordlenp Length of word written to \a word (not including terminating NUL).
+ * @param[in] maxlen Maximum number of bytes writable to \a word.
+ * @param[in,out] ruleptr Next readable character from input.
+ */
+static void crule_getword(char* word, int* wordlenp, size_t maxlen, const char** ruleptr)
 {
-	char *word_ptr, c;
-
-	word_ptr = word;
-	/* Both - and : can appear in hostnames so they must not be 
-	 * treated as separators -- codemastr */
-
-	while ((isalnum(**ruleptr)) || (**ruleptr == '*') ||
-	    (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-') ||
-	    (**ruleptr == ':'))
-	{
-		c = *(*ruleptr)++;
-		if (maxlen > 1) /* >1 instead of >0 so we (possibly) still have room for NUL */
-		{
-			*word_ptr++ = c;
-			maxlen--;
-		}
-	}
-	if (maxlen)
-		*word_ptr = '\0';
-	*wordlenp = word_ptr - word;
+  char *word_ptr;
+
+  word_ptr = word;
+  while ((size_t)(word_ptr - word) < maxlen
+      && (isalnum(**ruleptr)
+      || **ruleptr == '*' || **ruleptr == '?'
+      || **ruleptr == '.' || **ruleptr == '-'))
+    *word_ptr++ = *(*ruleptr)++;
+  *word_ptr = '\0';
+  *wordlenp = word_ptr - word;
 }
 
-/*
- * Grammar
- *   rule:
- *     orexpr END          END is end of input
- *   orexpr:
- *     andexpr
- *     andexpr || orexpr
- *   andexpr:
- *     primary
- *     primary && andexpr
- *  primary:
- *    function
- *    ! primary
- *    ( orexpr )
- *  function:
- *    word ( )             word is alphanumeric string, first character
- *    word ( arglist )       must be a letter
- *  arglist:
- *    word
- *    word , arglist
+/** Parse an entire rule.
+ * @param[in] rule Text form of rule.
+ * @return CRuleNode for rule, or NULL if there was a parse error.
  */
+struct CRuleNode* crule_parse(const char *rule)
+{
+  const char* ruleptr = rule;
+  int next_tok;
+  struct CRuleNode* ruleroot = 0;
+  int errcode = CR_NOERR;
+
+  if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
+    if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) {
+      if (ruleroot != NULL) {
+        if (next_tok == CR_END)
+          return (ruleroot);
+        else
+          errcode = CR_UNEXPCTTOK;
+      }
+      else
+        errcode = CR_EXPCTOR;
+    }
+  }
+  if (ruleroot != NULL)
+    crule_free(&ruleroot);
+#if defined(CR_DEBUG) || defined(CR_CHKCONF)
+  fprintf(stderr, "%s in rule: %s\n", crule_errstr[errcode], rule);
+#endif
+  return 0;
+}
 
-char *crule_parse(char *rule)
+/** Test-parse an entire rule.
+ * @param[in] rule Text form of rule.
+ * @return error code, or 0 for no failure
+ */
+int crule_test(const char *rule)
 {
-	char *ruleptr = rule;
-	int  next_tok;
-	crule_treeptr ruleroot = NULL;
-	int  errcode = CR_NOERR;
-
-	if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
-		if ((errcode = crule_parseorexpr(&ruleroot, &next_tok,
-		    &ruleptr)) == CR_NOERR) {
-			if (ruleroot != NULL) {
-				if (next_tok == CR_END)
-					return ((char *)ruleroot);
-				else
-					errcode = CR_UNEXPCTTOK;
-			}
-			else
-				errcode = CR_EXPCTOR;
-		}
-	}
-	if (ruleroot != NULL)
-		crule_free((char **)&ruleroot);
-	return NULL;
+  const char* ruleptr = rule;
+  int next_tok;
+  struct CRuleNode* ruleroot = 0;
+  int errcode = CR_NOERR;
+
+  if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
+    if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) {
+      if (ruleroot != NULL) {
+        if (next_tok == CR_END)
+        {
+          /* PASS */
+          crule_free(&ruleroot);
+          return 0;
+        } else {
+          errcode = CR_UNEXPCTTOK;
+        }
+      }
+      else
+        errcode = CR_EXPCTOR;
+    }
+  }
+  if (ruleroot != NULL)
+    crule_free(&ruleroot);
+#if defined(CR_DEBUG) || defined(CR_CHKCONF)
+  fprintf(stderr, "%s in rule: %s\n", crule_errstr[errcode], rule);
+#endif
+  return errcode + 1;
 }
 
-int  crule_parseorexpr(crule_treeptr *orrootp, int *next_tokp, char **ruleptr)
+const char *crule_errstring(int errcode)
 {
-	int  errcode = CR_NOERR;
-	crule_treeptr andexpr;
-	crule_treeptr orptr;
+  if (errcode == 0)
+    return "No error";
+  else
+    return crule_errstr[errcode-1];
+}
 
-	*orrootp = NULL;
-	while (errcode == CR_NOERR)
-	{
-		errcode = crule_parseandexpr(&andexpr, next_tokp, ruleptr);
-		if ((errcode == CR_NOERR) && (*next_tokp == CR_OR))
-		{
-			orptr =
-			    (crule_treeptr) safe_alloc(sizeof(crule_treeelem));
+/** Parse an or expression.
+ * @param[out] orrootp Receives parsed node.
+ * @param[in,out] next_tokp Next input token type.
+ * @param[in,out] ruleptr Next input character.
+ * @return A crule_errcode value.
+ */
+static int crule_parseorexpr(CRuleNodePtr * orrootp, int *next_tokp, const char** ruleptr)
+{
+  int errcode = CR_NOERR;
+  CRuleNodePtr andexpr;
+  CRuleNodePtr orptr;
+
+  *orrootp = NULL;
+  while (errcode == CR_NOERR)
+  {
+    errcode = crule_parseandexpr(&andexpr, next_tokp, ruleptr);
+    if ((errcode == CR_NOERR) && (*next_tokp == CR_OR))
+    {
+      orptr = safe_alloc(sizeof(struct CRuleNode));
 #ifdef CR_DEBUG
-			(void)fprintf(stderr, "allocating or element at %ld\n", orptr);
+      fprintf(stderr, "allocating or element at %ld\n", orptr);
 #endif
-			orptr->funcptr = crule__andor;
-			orptr->numargs = 3;
-			orptr->arg[2] = (void *)1;
-			if (*orrootp != NULL)
-			{
-				(*orrootp)->arg[1] = andexpr;
-				orptr->arg[0] = *orrootp;
-			}
-			else
-				orptr->arg[0] = andexpr;
-			*orrootp = orptr;
-		}
-		else
-		{
-			if (*orrootp != NULL)
-				if (andexpr != NULL)
-				{
-					(*orrootp)->arg[1] = andexpr;
-					return (errcode);
-				}
-				else
-				{
-					(*orrootp)->arg[1] = NULL;	/* so free doesn't seg fault */
-					return (CR_EXPCTAND);
-				}
-			else
-			{
-				*orrootp = andexpr;
-				return (errcode);
-			}
-		}
-		if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-			return (errcode);
-	}
-	return (errcode);
+      orptr->funcptr = crule__andor;
+      orptr->numargs = 3;
+      orptr->arg[2] = (void *)1;
+      if (*orrootp != NULL)
+      {
+        (*orrootp)->arg[1] = andexpr;
+        orptr->arg[0] = *orrootp;
+      }
+      else
+        orptr->arg[0] = andexpr;
+      *orrootp = orptr;
+    }
+    else
+    {
+      if (*orrootp != NULL)
+      {
+        if (andexpr != NULL)
+        {
+          (*orrootp)->arg[1] = andexpr;
+          return (errcode);
+        }
+        else
+        {
+          (*orrootp)->arg[1] = NULL;    /* so free doesn't seg fault */
+          return (CR_EXPCTAND);
+        }
+      }
+      else
+      {
+        *orrootp = andexpr;
+        return (errcode);
+      }
+    }
+    errcode = crule_gettoken(next_tokp, ruleptr);
+  }
+  return (errcode);
 }
 
-int  crule_parseandexpr(crule_treeptr *androotp, int *next_tokp, char **ruleptr)
+/** Parse an and expression.
+ * @param[out] androotp Receives parsed node.
+ * @param[in,out] next_tokp Next input token type.
+ * @param[in,out] ruleptr Next input character.
+ * @return A crule_errcode value.
+ */
+static int crule_parseandexpr(CRuleNodePtr * androotp, int *next_tokp, const char** ruleptr)
 {
-	int  errcode = CR_NOERR;
-	crule_treeptr primary;
-	crule_treeptr andptr;
-
-	*androotp = NULL;
-	while (errcode == CR_NOERR)
-	{
-		errcode = crule_parseprimary(&primary, next_tokp, ruleptr);
-		if ((errcode == CR_NOERR) && (*next_tokp == CR_AND))
-		{
-			andptr =
-			    (crule_treeptr) safe_alloc(sizeof(crule_treeelem));
+  int errcode = CR_NOERR;
+  CRuleNodePtr primary;
+  CRuleNodePtr andptr;
+
+  *androotp = NULL;
+  while (errcode == CR_NOERR)
+  {
+    errcode = crule_parseprimary(&primary, next_tokp, ruleptr);
+    if ((errcode == CR_NOERR) && (*next_tokp == CR_AND))
+    {
+      andptr = safe_alloc(sizeof(struct CRuleNode));
 #ifdef CR_DEBUG
-			(void)fprintf(stderr, "allocating and element at %ld\n", andptr);
+      fprintf(stderr, "allocating and element at %ld\n", andptr);
 #endif
-			andptr->funcptr = crule__andor;
-			andptr->numargs = 3;
-			andptr->arg[2] = (void *)0;
-			if (*androotp != NULL)
-			{
-				(*androotp)->arg[1] = primary;
-				andptr->arg[0] = *androotp;
-			}
-			else
-				andptr->arg[0] = primary;
-			*androotp = andptr;
-		}
-		else
-		{
-			if (*androotp != NULL)
-				if (primary != NULL)
-				{
-					(*androotp)->arg[1] = primary;
-					return (errcode);
-				}
-				else
-				{
-					(*androotp)->arg[1] = NULL;	/* so free doesn't seg fault */
-					return (CR_EXPCTPRIM);
-				}
-			else
-			{
-				*androotp = primary;
-				return (errcode);
-			}
-		}
-		if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-			return (errcode);
-	}
-	return (errcode);
+      andptr->funcptr = crule__andor;
+      andptr->numargs = 3;
+      andptr->arg[2] = (void *)0;
+      if (*androotp != NULL)
+      {
+        (*androotp)->arg[1] = primary;
+        andptr->arg[0] = *androotp;
+      }
+      else
+        andptr->arg[0] = primary;
+      *androotp = andptr;
+    }
+    else
+    {
+      if (*androotp != NULL)
+      {
+        if (primary != NULL)
+        {
+          (*androotp)->arg[1] = primary;
+          return (errcode);
+        }
+        else
+        {
+          (*androotp)->arg[1] = NULL;   /* so free doesn't seg fault */
+          return (CR_EXPCTPRIM);
+        }
+      }
+      else
+      {
+        *androotp = primary;
+        return (errcode);
+      }
+    }
+    errcode = crule_gettoken(next_tokp, ruleptr);
+  }
+  return (errcode);
 }
 
-int  crule_parseprimary(crule_treeptr *primrootp, int *next_tokp, char **ruleptr)
+/** Parse a primary expression.
+ * @param[out] primrootp Receives parsed node.
+ * @param[in,out] next_tokp Next input token type.
+ * @param[in,out] ruleptr Next input character.
+ * @return A crule_errcode value.
+ */
+static int crule_parseprimary(CRuleNodePtr* primrootp, int *next_tokp, const char** ruleptr)
 {
-	crule_treeptr *insertionp;
-	int  errcode = CR_NOERR;
-
-	*primrootp = NULL;
-	insertionp = primrootp;
-	while (errcode == CR_NOERR)
-	{
-		switch (*next_tokp)
-		{
-		  case CR_OPENPAREN:
-			  if ((errcode =
-			      crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-				  break;
-			  if ((errcode =
-			      crule_parseorexpr(insertionp, next_tokp,
-			      ruleptr)) != CR_NOERR)
-				  break;
-			  if (*insertionp == NULL)
-			  {
-				  errcode = CR_EXPCTAND;
-				  break;
-			  }
-			  if (*next_tokp != CR_CLOSEPAREN)
-			  {
-				  errcode = CR_EXPCTCLOSE;
-				  break;
-			  }
-			  errcode = crule_gettoken(next_tokp, ruleptr);
-			  break;
-		  case CR_NOT:
-			  *insertionp =
-			      (crule_treeptr) safe_alloc(sizeof(crule_treeelem));
+  CRuleNodePtr *insertionp;
+  int errcode = CR_NOERR;
+
+  *primrootp = NULL;
+  insertionp = primrootp;
+  while (errcode == CR_NOERR)
+  {
+    switch (*next_tokp)
+    {
+      case CR_OPENPAREN:
+        if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
+          break;
+        if ((errcode = crule_parseorexpr(insertionp, next_tokp, ruleptr)) != CR_NOERR)
+          break;
+        if (*insertionp == NULL)
+        {
+          errcode = CR_EXPCTAND;
+          break;
+        }
+        if (*next_tokp != CR_CLOSEPAREN)
+        {
+          errcode = CR_EXPCTCLOSE;
+          break;
+        }
+        errcode = crule_gettoken(next_tokp, ruleptr);
+        break;
+      case CR_NOT:
+        *insertionp = safe_alloc(sizeof(struct CRuleNode));
 #ifdef CR_DEBUG
-			  (void)fprintf(stderr,
-			      "allocating primary element at %ld\n",
-			      *insertionp);
+        fprintf(stderr, "allocating primary element at %ld\n", *insertionp);
 #endif
-			  (*insertionp)->funcptr = crule__not;
-			  (*insertionp)->numargs = 1;
-			  (*insertionp)->arg[0] = NULL;
-			  insertionp =
-			      (crule_treeptr *) & ((*insertionp)->arg[0]);
-			  if ((errcode =
-			      crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-				  break;
-			  continue;
-		  case CR_WORD:
-			  errcode =
-			      crule_parsefunction(insertionp, next_tokp,
-			      ruleptr);
-			  break;
-		  default:
-			  if (*primrootp == NULL)
-				  errcode = CR_NOERR;
-			  else
-				  errcode = CR_EXPCTPRIM;
-			  break;
-		}
-		return (errcode);
-	}
-	return (errcode);
+        (*insertionp)->funcptr = crule__not;
+        (*insertionp)->numargs = 1;
+        (*insertionp)->arg[0] = NULL;
+        insertionp = (CRuleNodePtr *) & ((*insertionp)->arg[0]);
+        if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
+          break;
+        continue;
+      case CR_WORD:
+        errcode = crule_parsefunction(insertionp, next_tokp, ruleptr);
+        break;
+      default:
+        if (*primrootp == NULL)
+          errcode = CR_NOERR;
+        else
+          errcode = CR_EXPCTPRIM;
+        break;
+    }
+    break; /* loop only continues after a CR_NOT */
+  }
+  return (errcode);
 }
 
-int  crule_parsefunction(crule_treeptr *funcrootp, int *next_tokp, char **ruleptr)
+/** Parse a function call.
+ * @param[out] funcrootp Receives parsed node.
+ * @param[in,out] next_tokp Next input token type.
+ * @param[in,out] ruleptr Next input character.
+ * @return A crule_errcode value.
+ */
+static int crule_parsefunction(CRuleNodePtr* funcrootp, int* next_tokp, const char** ruleptr)
 {
-	int  errcode = CR_NOERR;
-	char funcname[CR_MAXARGLEN];
-	int  namelen;
-	int  funcnum;
-
-	*funcrootp = NULL;
-	crule_getword(funcname, &namelen, CR_MAXARGLEN, ruleptr);
-	if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-		return (errcode);
-	if (*next_tokp == CR_OPENPAREN)
-	{
-		for (funcnum = 0;; funcnum++)
-		{
-			if (strcasecmp(crule_funclist[funcnum].name, funcname) == 0)
-				break;
-			if (crule_funclist[funcnum].name[0] == '\0')
-				return (CR_UNKNWFUNC);
-		}
-		if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-			return (errcode);
-		*funcrootp = (crule_treeptr) safe_alloc(sizeof(crule_treeelem));
+  int errcode = CR_NOERR;
+  char funcname[CR_MAXARGLEN];
+  int namelen;
+  int funcnum;
+
+  *funcrootp = NULL;
+  crule_getword(funcname, &namelen, CR_MAXARGLEN - 1, ruleptr);
+  if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
+    return (errcode);
+  if (*next_tokp == CR_OPENPAREN)
+  {
+    for (funcnum = 0;; funcnum++)
+    {
+      if (0 == strcasecmp(crule_funclist[funcnum].name, funcname))
+        break;
+      if (crule_funclist[funcnum].name[0] == '\0')
+        return (CR_UNKNWFUNC);
+    }
+    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
+      return (errcode);
+    *funcrootp = safe_alloc(sizeof(struct CRuleNode));
 #ifdef CR_DEBUG
-		(void)fprintf(stderr, "allocating function element at %ld\n",
-		    *funcrootp);
+    fprintf(stderr, "allocating function element at %ld\n", *funcrootp);
 #endif
-		(*funcrootp)->funcptr = NULL;	/* for freeing aborted trees */
-		if ((errcode = crule_parsearglist(*funcrootp, next_tokp,
-		    ruleptr)) != CR_NOERR)
-			return (errcode);
-		if (*next_tokp != CR_CLOSEPAREN)
-			return (CR_EXPCTCLOSE);
-		if ((crule_funclist[funcnum].reqnumargs !=
-		    (*funcrootp)->numargs)
-		    && (crule_funclist[funcnum].reqnumargs != -1))
-			return (CR_ARGMISMAT);
-		if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
-			return (errcode);
-		(*funcrootp)->funcptr = crule_funclist[funcnum].funcptr;
-		return (CR_NOERR);
-	}
-	else
-		return (CR_EXPCTOPEN);
+    (*funcrootp)->funcptr = NULL;       /* for freeing aborted trees */
+    if ((errcode =
+        crule_parsearglist(*funcrootp, next_tokp, ruleptr)) != CR_NOERR)
+      return (errcode);
+    if (*next_tokp != CR_CLOSEPAREN)
+      return (CR_EXPCTCLOSE);
+    if ((crule_funclist[funcnum].reqnumargs != (*funcrootp)->numargs) &&
+        (crule_funclist[funcnum].reqnumargs != -1))
+      return (CR_ARGMISMAT);
+    if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
+      return (errcode);
+    (*funcrootp)->funcptr = crule_funclist[funcnum].funcptr;
+    return (CR_NOERR);
+  }
+  else
+    return (CR_EXPCTOPEN);
 }
 
-int  crule_parsearglist(crule_treeptr argrootp, int *next_tokp, char **ruleptr)
+/** Parse the argument list to a CRuleNode.
+ * @param[in,out] argrootp Node whos argument list is being populated.
+ * @param[in,out] next_tokp Next input token type.
+ * @param[in,out] ruleptr Next input character.
+ * @return A crule_errcode value.
+ */
+static int crule_parsearglist(CRuleNodePtr argrootp, int *next_tokp, const char** ruleptr)
 {
-	int  errcode = CR_NOERR;
-	char *argelemp = NULL;
-	char currarg[CR_MAXARGLEN];
-	int  arglen = 0;
-	char word[CR_MAXARGLEN];
-	int  wordlen = 0;
-
-	argrootp->numargs = 0;
-	currarg[0] = '\0';
-	while (errcode == CR_NOERR)
-	{
-		switch (*next_tokp)
-		{
-		  case CR_WORD:
-			  crule_getword(word, &wordlen, CR_MAXARGLEN, ruleptr);
-			  if (currarg[0] != '\0')
-			  {
-				  if ((arglen + wordlen) < (CR_MAXARGLEN - 1))
-				  {
-					  strlcat(currarg, " ", sizeof currarg);
-					  strlcat(currarg, word, sizeof currarg);
-					  arglen += wordlen + 1;
-				  }
-			  }
-			  else
-			  {
-				  strlcpy(currarg, word, sizeof currarg);
-				  arglen = wordlen;
-			  }
-			  errcode = crule_gettoken(next_tokp, ruleptr);
-			  break;
-		  default:
+  int errcode = CR_NOERR;
+  char *argelemp = NULL;
+  char currarg[CR_MAXARGLEN];
+  int arglen = 0;
+  char word[CR_MAXARGLEN];
+  int wordlen = 0;
+
+  argrootp->numargs = 0;
+  currarg[0] = '\0';
+  while (errcode == CR_NOERR)
+  {
+    switch (*next_tokp)
+    {
+      case CR_WORD:
+        crule_getword(word, &wordlen, CR_MAXARGLEN - 1, ruleptr);
+        if (currarg[0] != '\0')
+        {
+          if ((arglen + wordlen) < (CR_MAXARGLEN - 1))
+          {
+            strcat(currarg, " ");
+            strcat(currarg, word);
+            arglen += wordlen + 1;
+          }
+        }
+        else
+        {
+          strcpy(currarg, word);
+          arglen = wordlen;
+        }
+        errcode = crule_gettoken(next_tokp, ruleptr);
+        break;
+      default:
 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
-			  collapse(currarg);
+        collapse(currarg);
 #endif
-			  if (*currarg)
-			  {
-				  safe_strdup(argelemp, currarg);
-				  argrootp->arg[argrootp->numargs++] = (void *)argelemp;
-			  }
-			  if (*next_tokp != CR_COMMA)
-				  return (CR_NOERR);
-			  currarg[0] = '\0';
-			  errcode = crule_gettoken(next_tokp, ruleptr);
-			  break;
-		}
-	}
-	return (errcode);
+        if (currarg[0] != '\0')
+        {
+          argelemp = raw_strdup(currarg);
+          argrootp->arg[argrootp->numargs++] = (void *)argelemp;
+        }
+        if (*next_tokp != CR_COMMA)
+          return (CR_NOERR);
+        currarg[0] = '\0';
+        errcode = crule_gettoken(next_tokp, ruleptr);
+        break;
+    }
+  }
+  return (errcode);
 }
 
 /*
- * this function is recursive..  i wish i knew a nonrecursive way but
- * i dont.  anyway, recursion is fun..  :)
- * DO NOT CALL THIS FUNTION WITH A POINTER TO A NULL POINTER
- * (ie: if *elem is NULL, you're doing it wrong - seg fault)
+ * This function is recursive..  I wish I knew a nonrecursive way but
+ * I don't.  Anyway, recursion is fun..  :)
+ * DO NOT CALL THIS FUNCTION WITH A POINTER TO A NULL POINTER
+ * (i.e.: If *elem is NULL, you're doing it wrong - seg fault)
+ */
+/** Free a connection rule and all its children.
+ * @param[in,out] elem Pointer to pointer to element to free.  MUST NOT BE NULL.
  */
-void crule_free(char **elem)
+void crule_free(struct CRuleNode** elem)
 {
-	int  arg, numargs;
-
-	if ((*((crule_treeptr *) elem))->funcptr == crule__not)
-	{
-		/* type conversions and ()'s are fun! ;)  here have an asprin.. */
-		if ((*((crule_treeptr *) elem))->arg[0] != NULL)
-			crule_free((char **)&((*((crule_treeptr *)
-			    elem))->arg[0]));
-	}
-	else if ((*((crule_treeptr *) elem))->funcptr == crule__andor)
-	{
-		crule_free((char **)&((*((crule_treeptr *) elem))->arg[0]));
-		if ((*((crule_treeptr *) elem))->arg[1] != NULL)
-			crule_free((char **)&((*((crule_treeptr *)
-			    elem))->arg[1]));
-	}
-	else
-	{
-		numargs = (*((crule_treeptr *) elem))->numargs;
-		for (arg = 0; arg < numargs; arg++)
-			safe_free_raw((char *)(*((crule_treeptr *) elem))->arg[arg]);
-	}
+  int arg, numargs;
+
+  if ((*(elem))->funcptr == crule__not)
+  {
+    /* type conversions and ()'s are fun! ;)  here have an aspirin.. */
+    if ((*(elem))->arg[0] != NULL)
+      crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
+  }
+  else if ((*(elem))->funcptr == crule__andor)
+  {
+    crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
+    if ((*(elem))->arg[1] != NULL)
+      crule_free((struct CRuleNode**) &((*(elem))->arg[1]));
+  }
+  else
+  {
+    numargs = (*(elem))->numargs;
+    for (arg = 0; arg < numargs; arg++)
+      safe_free((*(elem))->arg[arg]);
+  }
 #ifdef CR_DEBUG
-	(void)fprintf(stderr, "freeing element at %ld\n", *elem);
+  fprintf(stderr, "freeing element at %ld\n", *elem);
 #endif
-	safe_free(*elem);
-	*elem = NULL;
-}
-
-char *crule_errstring(int errcode)
-{
-	return crule_errstr[errcode-1];
-}
-
-int crule_test(char *rule)
-{
-	char *ruleptr = rule;
-	int  next_tok;
-	crule_treeptr ruleroot = NULL;
-	int  errcode = CR_NOERR;
-
-	if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
-		if ((errcode = crule_parseorexpr(&ruleroot, &next_tok,
-		    &ruleptr)) == CR_NOERR) {
-			if (ruleroot != NULL) {
-				if (next_tok == CR_END)
-				{
-					crule_free((char **)&ruleroot);
-					return 0;
-				}
-				else
-					errcode = CR_UNEXPCTTOK;
-			}
-			else
-				errcode = CR_EXPCTOR;
-		}
-	}
-	if (ruleroot != NULL)
-		crule_free((char **)&ruleroot);
-	return errcode+1;
+  safe_free(*elem);
+  *elem = 0;
 }
 
 #ifdef CR_DEBUG
-void print_tree(crule_treeptr printelem)
+/** Display a connection rule as text.
+ * @param[in] printelem Connection rule to display.
+ */
+static void print_tree(CRuleNodePtr printelem)
 {
-	int  funcnum, arg;
-
-	if (printelem->funcptr == crule__not)
-	{
-		printf("!( ");
-		print_tree((crule_treeptr) printelem->arg[0]);
-		printf(") ");
-	}
-	else if (printelem->funcptr == crule__andor)
-	{
-		printf("( ");
-		print_tree((crule_treeptr) printelem->arg[0]);
-		if (printelem->arg[2])
-			printf("|| ");
-		else
-			printf("&& ");
-		print_tree((crule_treeptr) printelem->arg[1]);
-		printf(") ");
-	}
-	else
-	{
-		for (funcnum = 0;; funcnum++)
-		{
-			if (printelem->funcptr ==
-			    crule_funclist[funcnum].funcptr)
-				break;
-			if (crule_funclist[funcnum].funcptr == NULL)
-			{
-				printf("\nACK!  *koff*  *sputter*\n");
-				exit(1);
-			}
-		}
-		printf("%s(", crule_funclist[funcnum].name);
-		for (arg = 0; arg < printelem->numargs; arg++)
-		{
-			if (arg != 0)
-				printf(",");
-			printf("%s", (char *)printelem->arg[arg]);
-		}
-		printf(") ");
-	}
+  int funcnum, arg;
+
+  if (printelem->funcptr == crule__not)
+  {
+    printf("!( ");
+    print_tree((CRuleNodePtr) printelem->arg[0]);
+    printf(") ");
+  }
+  else if (printelem->funcptr == crule__andor)
+  {
+    printf("( ");
+    print_tree((CRuleNodePtr) printelem->arg[0]);
+    if (printelem->arg[2])
+      printf("|| ");
+    else
+      printf("&& ");
+    print_tree((CRuleNodePtr) printelem->arg[1]);
+    printf(") ");
+  }
+  else
+  {
+    for (funcnum = 0;; funcnum++)
+    {
+      if (printelem->funcptr == crule_funclist[funcnum].funcptr)
+        break;
+      if (crule_funclist[funcnum].funcptr == NULL)
+        MyCoreDump;
+    }
+    printf("%s(", crule_funclist[funcnum].name);
+    for (arg = 0; arg < printelem->numargs; arg++)
+    {
+      if (arg != 0)
+        printf(",");
+      printf("%s", (char *)printelem->arg[arg]);
+    }
+    printf(") ");
+  }
 }
 
 #endif
 
 #ifdef CR_DEBUG
-void main()
+/** Read connection rules from stdin and display parsed forms as text.
+ * @return Zero.
+ */
+int main(void)
 {
-	char indata[256];
-	char *rule;
-
-	printf("rule: ");
-	while (fgets(indata, 256, stdin) != NULL)
-	{
-		indata[strlen(indata) - 1] = '\0';	/* lose the newline */
-		if ((rule = crule_parse(indata)) != NULL)
-		{
-			printf("equivalent rule: ");
-			print_tree((crule_treeptr) rule);
-			printf("\n");
-			crule_free(&rule);
-		}
-		printf("\nrule: ");
-	}
-	printf("\n");
+  char indata[256];
+  CRuleNode* rule;
+
+  printf("rule: ");
+  while (fgets(indata, 256, stdin) != NULL)
+  {
+    indata[strlen(indata) - 1] = '\0';  /* lose the newline */
+    if ((rule = crule_parse(indata)) != NULL)
+    {
+      printf("equivalent rule: ");
+      print_tree((CRuleNodePtr) rule);
+      printf("\n");
+      crule_free(&rule);
+    }
+    printf("\nrule: ");
+  }
+  printf("\n");
+
+  return 0;
 }
+
 #endif
diff --git a/src/dbuf.c b/src/dbuf.c
@@ -189,3 +189,32 @@ int  dbuf_getmsg(dbuf *dyn, char *buf)
 	dbuf_delete(dyn, line_bytes + empty_bytes);
 	return MIN(line_bytes, READBUFSIZE - 2);
 }
+
+/*
+** dbuf_get
+**
+** Get the entire dbuf buffer as a newly allocated string. There is NO CR/LF processing.
+*/
+int dbuf_get(dbuf *dyn, char **buf)
+{
+	dbufbuf *block;
+	char *d;
+	int bytes = 0;
+
+	/* First calculate the room needed... */
+	list_for_each_entry2(block, dbufbuf, &dyn->dbuf_list, dbuf_node)
+		bytes += block->size;
+
+	d = *buf = safe_alloc(bytes + 1);
+
+	list_for_each_entry2(block, dbufbuf, &dyn->dbuf_list, dbuf_node)
+	{
+		memcpy(d, block->data, block->size);
+		d += block->size;
+	}
+	*d = '\0'; /* zero terminate */
+
+	/* Remove what is now unnecessary */
+	dbuf_delete(dyn, bytes);
+	return bytes;
+}
diff --git a/src/dispatch.c b/src/dispatch.c
@@ -160,7 +160,7 @@ void fd_debug(fd_set *f, int highest, char *name)
 		}
 	}
 }
-void fd_select(time_t delay)
+void fd_select(int delay)
 {
 	struct timeval to;
 	int num, fd;
@@ -360,7 +360,7 @@ void fd_refresh(int fd)
 	}
 }
 
-void fd_select(time_t delay)
+void fd_select(int delay)
 {
 	struct timespec ts;
 	int num, p, revents, fd;
@@ -469,7 +469,7 @@ void fd_refresh(int fd)
 	fde->backend_flags = pflags;
 }
 
-void fd_select(time_t delay)
+void fd_select(int delay)
 {
 	int num, p, revents, fd;
 	struct epoll_event *epfd;
@@ -605,7 +605,7 @@ void fd_refresh(int fd)
 	fde->backend_flags = pflags;
 }
 
-void fd_select(time_t delay)
+void fd_select(int delay)
 {
 	int num, p, revents, fd;
 	struct pollfd *pfd;
diff --git a/src/fdlist.c b/src/fdlist.c
@@ -19,7 +19,7 @@
 
 #include "unrealircd.h"
 
-/* new FD management code, based on mowgli.eventloop from atheme, hammered into Unreal by
+/* new FD management code, based on mowgli.eventloop from atheme, hammered into UnrealIRCd by
  * me, nenolod.
  */
 FDEntry fd_table[MAXCONNECTIONS + 1];
diff --git a/src/hash.c b/src/hash.c
@@ -320,7 +320,7 @@ int add_to_client_hash_table(const char *name, Client *client)
 	/*
 	 * If you see this, you have probably found your way to why changing the 
 	 * base version made the IRCd become weird. This has been the case in all
-	 * Unreal versions since 3.0. I'm sick of people ripping the IRCd off and 
+	 * UnrealIRCd versions since 3.0. I'm sick of people ripping the IRCd off and 
 	 * just slapping on some random <theirnet> BASE_VERSION while not changing
 	 * a single bit of code. YOU DID NOT WRITE ALL OF THIS THEREFORE YOU DO NOT
 	 * DESERVE TO BE ABLE TO DO THAT. If you found this however, I'm OK with you 
@@ -720,3 +720,21 @@ int throttle_can_connect(Client *client)
 		return 2;
 	}
 }
+
+/** Find a server by the SID-part of a UID.
+ * Eg you pass "001ABCDEFG" and it would look up server "001".
+ *
+ * @param uid	The UID, eg 001ABCDEFG
+ * @returns Server where the UID would be hosted on, or NULL
+ * if no such server is linked.
+ */
+Client *find_server_by_uid(const char *uid)
+{
+	char sid[SIDLEN+1];
+
+	if (!isdigit(*uid))
+		return NULL; /* not a UID/SID */
+
+	strlcpy(sid, uid, sizeof(sid));
+	return hash_find_id(sid, NULL);
+}
diff --git a/src/ircd.c b/src/ircd.c
@@ -121,12 +121,32 @@ EVENT(handshake_timeout)
 
 	list_for_each_entry_safe(client, next, &unknown_list, lclient_node)
 	{
-		if (client->local->creationtime && ((TStime() - client->local->creationtime) > iConf.handshake_timeout))
+		if (client->local->creationtime &&
+		    ((TStime() - client->local->creationtime) > iConf.handshake_timeout) &&
+		    !(client->local->listener && (client->local->listener->socket_type == SOCKET_TYPE_UNIX)))
 		{
+			Hook *h;
+			int n = HOOK_CONTINUE;
+			const char *quitreason = "Registration Timeout";
+			char reasonbuf[512];
+
 			if (client->server && *client->server->by)
 				continue; /* handled by server module */
 
-			exit_client(client, NULL, "Registration Timeout");
+			for (h = Hooks[HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT]; h; h = h->next)
+			{
+				n = (*(h->func.intfunc))(client, &quitreason);
+				if (n == HOOK_ALLOW)
+					break;
+			}
+			if (n == HOOK_ALLOW)
+				continue; /* Do not exit the client due to registration timeout */
+
+			/* Work on a copy here, since the 'quitreason' may point to
+			 * some kind of buffer that gets freed in the exit code.
+			 */
+			strlcpy(reasonbuf, quitreason ? quitreason : "Registration Timeout", sizeof(reasonbuf));
+			exit_client(client, NULL, reasonbuf);
 			continue;
 		}
 	}
@@ -713,6 +733,9 @@ int InitUnrealIRCd(int argc, char *argv[])
 	fprintf(stderr, "* c-ares %s\n", ares_version(NULL));
 	fprintf(stderr, "* %s\n", pcre2_version());
 #endif
+#if JANSSON_VERSION_HEX >= 0x020D00
+	fprintf(stderr, "* jansson %s\n", jansson_version_str());
+#endif
 	check_user_limit();
 #ifndef _WIN32
 	fprintf(stderr, "\n");
@@ -730,6 +753,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 #endif
 	init_dynconf();
 	init_sys();
+	clicap_init();
 	/*
 	 * Add default class
 	 */
@@ -760,7 +784,6 @@ int InitUnrealIRCd(int argc, char *argv[])
 	make_server(&me);
 	umodes_check_for_changes();
 	charsys_check_for_changes();
-	clicap_init();
 	if (!find_command_simple("PRIVMSG"))
 	{
 		config_error("Someone forgot to load modules with proper commands in them. READ THE DOCUMENTATION");
@@ -854,6 +877,7 @@ int InitUnrealIRCd(int argc, char *argv[])
 	PS_STRINGS->ps_argvstr = me.name;
 #endif
 	module_loadall();
+	loop.config_status = CONFIG_STATUS_COMPLETE;
 
 #ifndef _WIN32
 	SocketLoop(NULL);
diff --git a/src/json.c b/src/json.c
@@ -0,0 +1,580 @@
+/************************************************************************
+ * UnralIRCd JSON functions, src/json.c
+ * (C) 2021-.. Bram Matthys (Syzop) and the UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+/** @file
+ * @brief JSON functions - used for logging and RPC.
+ */
+
+/** Are we currently in the logging code? */
+int log_json_filter = 0;
+
+/** Calculate expansion of a JSON string thanks to double escaping.
+ * orig => JSON => IRC
+ *    " => \"   => \\"
+ *    \ => \\   => \\\\
+ */
+int json_dump_string_length(const char *s)
+{
+	int len = 0;
+	for (; *s; s++)
+	{
+		if (*s == '\\')
+			len += 4;
+		else if (*s == '"')
+			len += 3;
+		else
+			len++;
+	}
+	return len;
+}
+
+/** Convert a regular string value to a JSON string.
+ * In UnrealIRCd, this must be used instead of json_string()
+ * as we may use non-UTF8 sequences. Also, this takes care
+ * of using json_null() if the string was NULL, which is
+ * usually what we want as well.
+ * @param s	Input string
+ * @returns a json string value or json null value.
+ */
+json_t *json_string_unreal(const char *s)
+{
+	char buf1[512], buf2[512];
+	char *verified_s;
+	const char *stripped;
+
+	if (s == NULL)
+		return json_null();
+
+	if (log_json_filter)
+	{
+		stripped = StripControlCodesEx(s, buf1, sizeof(buf1), UNRL_STRIP_LOW_ASCII|UNRL_STRIP_KEEP_LF);
+		verified_s = unrl_utf8_make_valid(buf1, buf2, sizeof(buf2), 0);
+	} else {
+		verified_s = unrl_utf8_make_valid(s, buf2, sizeof(buf2), 0);
+	}
+
+	return json_string(verified_s);
+}
+
+const char *json_object_get_string(json_t *j, const char *name)
+{
+	json_t *v = json_object_get(j, name);
+	return v ? json_string_value(v) : NULL;
+}
+
+/** Get integer value of a JSON object.
+ * @param j		The JSON object that should contain a 'name' item
+ * @param name		The item to search for
+ * @param default_value	The value to return when the JSON object 'name' is not found
+ *			or not an integer.
+ * @returns The integer value, or default_value if the object does not exist or is not an integer.
+ */
+int json_object_get_integer(json_t *j, const char *name, int default_value)
+{
+	json_t *v = json_object_get(j, name);
+	if (!v || !json_is_integer(v))
+		return default_value;
+	return json_integer_value(v);
+}
+
+int json_object_get_boolean(json_t *j, const char *name, int default_value)
+{
+	json_t *v = json_object_get(j, name);
+	if (!v)
+		return default_value;
+	if (json_is_true(v))
+		return 1;
+	return 0;
+}
+
+#define json_string __BAD___DO__NOT__USE__JSON__STRING__PLZ
+
+const char *json_get_value(json_t *t)
+{
+	static char buf[32];
+
+	if (json_is_string(t))
+		return json_string_value(t);
+
+	if (json_is_integer(t))
+	{
+		snprintf(buf, sizeof(buf), "%lld", (long long)json_integer_value(t));
+		return buf;
+	}
+
+	if (json_is_boolean(t))
+	{
+		if (json_is_true(t))
+			return "true";
+		return "false";
+	}
+
+	if (json_is_array(t))
+		return "<array>";
+
+	return NULL;
+}
+
+json_t *json_timestamp(time_t v)
+{
+	const char *ts = timestamp_iso8601(v);
+	if (ts)
+		return json_string_unreal(ts);
+	return json_null();
+}
+
+const char *timestamp_iso8601_now(void)
+{
+	struct timeval t;
+	struct tm *tm;
+	time_t sec;
+	static char buf[64];
+
+	gettimeofday(&t, NULL);
+	sec = t.tv_sec;
+	tm = gmtime(&sec);
+
+	snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
+		tm->tm_year + 1900,
+		tm->tm_mon + 1,
+		tm->tm_mday,
+		tm->tm_hour,
+		tm->tm_min,
+		tm->tm_sec,
+		(int)(t.tv_usec / 1000));
+
+	return buf;
+}
+
+const char *timestamp_iso8601(time_t v)
+{
+	struct tm *tm;
+	static char buf[64];
+
+	if (v == 0)
+		return NULL;
+
+	tm = gmtime(&v);
+
+	if (tm == NULL)
+		return NULL;
+
+	snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
+		tm->tm_year + 1900,
+		tm->tm_mon + 1,
+		tm->tm_mday,
+		tm->tm_hour,
+		tm->tm_min,
+		tm->tm_sec,
+		0);
+
+	return buf;
+}
+
+void json_expand_client_security_groups(json_t *parent, Client *client)
+{
+	SecurityGroup *s;
+	json_t *child = json_array();
+	json_object_set_new(parent, "security-groups", child);
+
+	/* We put known-users or unknown-users at the beginning.
+	 * The latter is special and doesn't actually exist
+	 * in the linked list, hence the special code here,
+	 * and again later in the for loop to skip it.
+	 */
+	if (user_allowed_by_security_group_name(client, "known-users"))
+		json_array_append_new(child, json_string_unreal("known-users"));
+	else
+		json_array_append_new(child, json_string_unreal("unknown-users"));
+
+	for (s = securitygroups; s; s = s->next)
+		if (strcmp(s->name, "known-users") && user_allowed_by_security_group(client, s))
+			json_array_append_new(child, json_string_unreal(s->name));
+}
+
+/* detail=0:	only name, id
+ * detail=1:	only name, id, hostname, ip, details, geoip
+ * detail=2:	everything, except 'channels'
+ * detail=3:	everything, with 'channels' being a max 384 character string (meant for JSON logging only)
+ * detail=4:	everything, with 'channels' object (full).
+ */
+void json_expand_client(json_t *j, const char *key, Client *client, int detail)
+{
+	char buf[BUFSIZE+1];
+	json_t *child;
+	json_t *user = NULL;
+	time_t ts;
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	/* First the information that is available for ALL client types: */
+	json_object_set_new(child, "name", json_string_unreal(client->name));
+	json_object_set_new(child, "id", json_string_unreal(client->id));
+
+	if (detail == 0)
+		return;
+
+	/* hostname is available for all, it just depends a bit on whether it is DNS or IP */
+	if (client->user && *client->user->realhost)
+		json_object_set_new(child, "hostname", json_string_unreal(client->user->realhost));
+	else if (client->local && *client->local->sockhost)
+		json_object_set_new(child, "hostname", json_string_unreal(client->local->sockhost));
+	else
+		json_object_set_new(child, "hostname", json_string_unreal(GetIP(client)));
+
+	/* 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));
+	/* client.details is always available: it is nick!user@host, nick@host, server@host
+	 * server@ip, or just server.
+	 */
+	if (client->user)
+	{
+		snprintf(buf, sizeof(buf), "%s!%s@%s", client->name, client->user->username, client->user->realhost);
+		json_object_set_new(child, "details", json_string_unreal(buf));
+	} else if (client->ip) {
+		if (*client->name)
+			snprintf(buf, sizeof(buf), "%s@%s", client->name, client->ip);
+		else
+			snprintf(buf, sizeof(buf), "[%s]", client->ip);
+		json_object_set_new(child, "details", json_string_unreal(buf));
+	} else {
+		json_object_set_new(child, "details", json_string_unreal(client->name));
+	}
+
+	if (detail < 2)
+	{
+		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child);
+		return;
+	}
+
+	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));
+	if ((ts = get_creationtime(client)))
+		json_object_set_new(child, "connected_since", json_timestamp(ts));
+	if (client->local && client->local->idle_since)
+		json_object_set_new(child, "idle_since", json_timestamp(client->local->idle_since));
+
+	if (client->user)
+	{
+		char buf[512];
+		const char *str;
+		/* client.user */
+		user = json_object();
+		json_object_set_new(child, "user", user);
+
+		json_object_set_new(user, "username", json_string_unreal(client->user->username));
+		if (!BadPtr(client->info))
+			json_object_set_new(user, "realname", json_string_unreal(client->info));
+		if (has_user_mode(client, 'x') && client->user->virthost && strcmp(client->user->virthost, client->user->realhost))
+			json_object_set_new(user, "vhost", json_string_unreal(client->user->virthost));
+		if (*client->user->cloakedhost)
+			json_object_set_new(user, "cloakedhost", json_string_unreal(client->user->cloakedhost));
+		if (client->uplink)
+			json_object_set_new(user, "servername", json_string_unreal(client->uplink->name));
+		if (IsLoggedIn(client))
+			json_object_set_new(user, "account", json_string_unreal(client->user->account));
+		json_object_set_new(user, "reputation", json_integer(GetReputation(client)));
+		json_expand_client_security_groups(user, client);
+
+		/* user modes and snomasks */
+		get_usermode_string_r(client, buf, sizeof(buf));
+		json_object_set_new(user, "modes", json_string_unreal(buf+1));
+		if (client->user->snomask)
+			json_object_set_new(user, "snomasks", json_string_unreal(client->user->snomask));
+
+		/* if oper then we can possibly expand a bit more */
+		str = get_operlogin(client);
+		if (str)
+			json_object_set_new(user, "operlogin", json_string_unreal(str));
+		str = get_operclass(client);
+		if (str)
+			json_object_set_new(user, "operclass", json_string_unreal(str));
+		/* For detail>2 we will include the channels.
+		 * Even if the user is on 0 channels we include "channels":[]
+		 * so it is clear that the user is on 0 channels and it is
+		 * not because of low detail level that channels are skipped.
+		 */
+		if (detail > 2)
+		{
+			Membership *m;
+			int cnt = 0;
+			int len = 0;
+			json_t *channels = json_array();
+			json_object_set_new(user, "channels", channels);
+
+			if (detail == 3)
+			{
+				/* Short format, mainly for JSON logging */
+				for (m = client->user->channel; m; m = m->next)
+				{
+					len += json_dump_string_length(m->channel->name);
+					if (len > 384)
+					{
+						/* Truncated */
+						json_array_append_new(channels, json_string_unreal("..."));
+						break;
+					}
+					json_array_append_new(channels, json_string_unreal(m->channel->name));
+				}
+			} else {
+				/* Long format for JSON-RPC */
+				for (m = client->user->channel; m; m = m->next)
+				{
+					json_t *e = json_object();
+					json_object_set_new(e, "name", json_string_unreal(m->channel->name));
+					if (*m->member_modes)
+						json_object_set_new(e, "level", json_string_unreal(m->member_modes));
+					json_array_append_new(channels, e);
+				}
+			}
+		}
+		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_USER, client, detail, child, user);
+	} else
+	if (IsMe(client))
+	{
+		json_t *server = json_object();
+		json_t *features;
+
+		/* client.server */
+		json_object_set_new(child, "server", server);
+
+		if (!BadPtr(client->info))
+			json_object_set_new(server, "info", json_string_unreal(client->info));
+		json_object_set_new(server, "num_users", json_integer(client->server->users));
+		json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime));
+
+		/* client.server.features */
+		features = json_object();
+		json_object_set_new(server, "features", features);
+		if (!BadPtr(client->server->features.software))
+		{
+			char buf[256];
+			snprintf(buf, sizeof(buf), "UnrealIRCd-%s", buildid);
+			json_object_set_new(features, "software", json_string_unreal(buf));
+		}
+		json_object_set_new(features, "protocol", json_integer(UnrealProtocol));
+		if (!BadPtr(client->server->features.usermodes))
+			json_object_set_new(features, "usermodes", json_string_unreal(umodestring));
+
+		/* client.server.features.chanmodes (array) */
+		{
+			int i;
+			char buf[512];
+			json_t *chanmodes = json_array();
+			json_object_set_new(features, "chanmodes", chanmodes);
+			/* first one is special - wait.. is this still the case? lol. */
+			snprintf(buf, sizeof(buf), "%s%s", CHPAR1, EXPAR1);
+			json_array_append_new(chanmodes, json_string_unreal(buf));
+			for (i=1; i < 4; i++)
+				json_array_append_new(chanmodes, json_string_unreal(extchmstr[i]));
+		}
+		if (!BadPtr(client->server->features.nickchars))
+			json_object_set_new(features, "nick_character_sets", json_string_unreal(charsys_get_current_languages()));
+		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server);
+	} else
+	if (IsServer(client) && client->server)
+	{
+		/* client.server */
+
+		/* Whenever a server is expanded, which is rare,
+		 * we should probably expand as much as info as possible:
+		 */
+		json_t *server = json_object();
+		json_t *features;
+
+		/* client.server */
+		json_object_set_new(child, "server", server);
+		if (!BadPtr(client->info))
+			json_object_set_new(server, "info", json_string_unreal(client->info));
+		if (client->uplink)
+			json_object_set_new(server, "uplink", json_string_unreal(client->uplink->name));
+		json_object_set_new(server, "num_users", json_integer(client->server->users));
+		json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime));
+		json_object_set_new(server, "synced", json_boolean(client->server->flags.synced));
+		json_object_set_new(server, "ulined", json_boolean(IsULine(client)));
+
+		/* client.server.features */
+		features = json_object();
+		json_object_set_new(server, "features", features);
+		if (!BadPtr(client->server->features.software))
+			json_object_set_new(features, "software", json_string_unreal(client->server->features.software));
+		json_object_set_new(features, "protocol", json_integer(client->server->features.protocol));
+		if (!BadPtr(client->server->features.usermodes))
+			json_object_set_new(features, "usermodes", json_string_unreal(client->server->features.usermodes));
+		if (!BadPtr(client->server->features.chanmodes[0]))
+		{
+			/* client.server.features.chanmodes (array) */
+			int i;
+			json_t *chanmodes = json_array();
+			json_object_set_new(features, "chanmodes", chanmodes);
+			for (i=0; i < 4; i++)
+				json_array_append_new(chanmodes, json_string_unreal(client->server->features.chanmodes[i]));
+		}
+		if (!BadPtr(client->server->features.nickchars))
+			json_object_set_new(features, "nick_character_sets", json_string_unreal(client->server->features.nickchars));
+		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server);
+	}
+	RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child);
+}
+
+void json_expand_channel_ban(json_t *child, const char *banlist_name, Ban *banlist)
+{
+	Ban *ban;
+	json_t *list, *e;
+
+	list = json_array();
+	json_object_set_new(child, banlist_name, list);
+	for (ban = banlist; ban; ban = ban->next)
+	{
+		e = json_object();
+		json_array_append_new(list, e);
+		json_object_set_new(e, "name", json_string_unreal(ban->banstr));
+		json_object_set_new(e, "set_by", json_string_unreal(ban->who));
+		json_object_set_new(e, "set_at", json_timestamp(ban->when));
+	}
+}
+
+
+/* detail=1 adds bans, ban_exemptions and invite_exceptions
+ * detail=2 adds members
+ * detail=3+ makes the members more detailed
+ */
+void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail)
+{
+	char mode1[512], mode2[512], modes[512];
+	json_t *child;
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	json_object_set_new(child, "name", json_string_unreal(channel->name));
+	if (detail == 0)
+		return;
+
+	json_object_set_new(child, "creation_time", json_timestamp(channel->creationtime));
+	json_object_set_new(child, "num_users", json_integer(channel->users));
+	if (channel->topic)
+	{
+		json_object_set_new(child, "topic", json_string_unreal(channel->topic));
+		json_object_set_new(child, "topic_set_by", json_string_unreal(channel->topic_nick));
+		json_object_set_new(child, "topic_set_at", json_timestamp(channel->topic_time));
+	}
+
+	/* Add "mode" too */
+	channel_modes(NULL, mode1, mode2, sizeof(mode1), sizeof(mode2), channel, 0);
+	if (*mode2)
+	{
+		snprintf(modes, sizeof(modes), "%s %s", mode1+1, mode2);
+		json_object_set_new(child, "modes", json_string_unreal(modes));
+	} else {
+		json_object_set_new(child, "modes", json_string_unreal(mode1+1));
+	}
+
+	if (detail > 1)
+	{
+		json_expand_channel_ban(child, "bans", channel->banlist);
+		json_expand_channel_ban(child, "ban_exemptions", channel->exlist);
+		json_expand_channel_ban(child, "invite_exceptions", channel->invexlist);
+	}
+
+	if (detail >= 3)
+	{
+		Member *u;
+		json_t *list = json_array();
+		json_object_set_new(child, "members", list);
+
+		for (u = channel->members; u; u = u->next)
+		{
+			json_t *e = json_object();
+			if (*u->member_modes)
+				json_object_set_new(e, "level", json_string_unreal(u->member_modes));
+			json_expand_client(e, NULL, u->client, detail-3);
+			json_array_append_new(list, e);
+		}
+	}
+
+	// Possibly later: If detail is set to 1 then expand more...
+	RunHook(HOOKTYPE_JSON_EXPAND_CHANNEL, channel, detail, child);
+}
+
+void json_expand_tkl(json_t *root, const char *key, TKL *tkl, int detail)
+{
+	char buf[BUFSIZE];
+	json_t *j;
+
+	if (key)
+	{
+		j = json_object();
+		json_object_set_new(root, key, j);
+	} else {
+		j = root;
+	}
+
+	json_object_set_new(j, "type", json_string_unreal(tkl_type_config_string(tkl))); // Eg 'kline'
+	json_object_set_new(j, "type_string", json_string_unreal(tkl_type_string(tkl))); // Eg 'Soft K-Line'
+	json_object_set_new(j, "set_by", json_string_unreal(tkl->set_by));
+	json_object_set_new(j, "set_at", json_timestamp(tkl->set_at));
+	json_object_set_new(j, "expire_at", json_timestamp(tkl->expire_at));
+	*buf = '\0';
+	short_date(tkl->set_at, buf);
+	strlcat(buf, " GMT", sizeof(buf));
+	json_object_set_new(j, "set_at_string", json_string_unreal(buf));
+	if (tkl->expire_at <= 0)
+	{
+		json_object_set_new(j, "expire_at_string", json_string_unreal("Never"));
+		json_object_set_new(j, "duration_string", json_string_unreal("permanent"));
+	} else {
+		*buf = '\0';
+		short_date(tkl->expire_at, buf);
+		strlcat(buf, " GMT", sizeof(buf));
+		json_object_set_new(j, "expire_at_string", json_string_unreal(buf));
+		json_object_set_new(j, "duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->expire_at - tkl->set_at)));
+	}
+	json_object_set_new(j, "set_at_delta", json_integer(TStime() - tkl->set_at));
+	if (tkl->flags & TKL_FLAG_CONFIG)
+		json_object_set_new(j, "set_in_config", json_boolean(1));
+	if (TKLIsServerBan(tkl))
+	{
+		json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0)));
+		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.serverban->reason));
+	} else
+	if (TKLIsNameBan(tkl))
+	{
+		json_object_set_new(j, "name", json_string_unreal(tkl->ptr.nameban->name));
+		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.nameban->reason));
+	} else
+	if (TKLIsBanException(tkl))
+	{
+		json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0)));
+		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.banexception->reason));
+		json_object_set_new(j, "exception_types", json_string_unreal(tkl->ptr.banexception->bantypes));
+	} else
+	if (TKLIsSpamfilter(tkl))
+	{
+		json_object_set_new(j, "name", json_string_unreal(tkl->ptr.spamfilter->match->str));
+		json_object_set_new(j, "match_type", json_string_unreal(unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type)));
+		json_object_set_new(j, "ban_action", json_string_unreal(banact_valtostring(tkl->ptr.spamfilter->action)));
+		json_object_set_new(j, "ban_duration", json_integer(tkl->ptr.spamfilter->tkl_duration));
+		json_object_set_new(j, "ban_duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->ptr.spamfilter->tkl_duration)));
+		json_object_set_new(j, "spamfilter_targets", json_string_unreal(spamfilter_target_inttostring(tkl->ptr.spamfilter->target)));
+		json_object_set_new(j, "reason", json_string_unreal(unreal_decodespace(tkl->ptr.spamfilter->tkl_reason)));
+	}
+}
diff --git a/src/list.c b/src/list.c
@@ -51,6 +51,7 @@ MODVAR struct list_head server_list;		/**< Locally connected servers */
 MODVAR struct list_head oper_list;		/**< Locally connected IRC Operators */
 MODVAR struct list_head global_server_list;	/**< All servers (local and remote) */
 MODVAR struct list_head dead_list;		/**< All dead clients (local and remote) that will soon be freed in the main loop */
+MODVAR struct list_head rpc_remote_list;	/**< All remote RPC clients (very specific use-case) */
 
 static mp_pool_t *client_pool = NULL;
 static mp_pool_t *local_client_pool = NULL;
@@ -75,6 +76,7 @@ void initlists(void)
 	INIT_LIST_HEAD(&control_list);
 	INIT_LIST_HEAD(&global_server_list);
 	INIT_LIST_HEAD(&dead_list);
+	INIT_LIST_HEAD(&rpc_remote_list);
 
 	client_pool = mp_pool_new(sizeof(Client), 512 * 1024);
 	local_client_pool = mp_pool_new(sizeof(LocalClient), 512 * 1024);
@@ -145,6 +147,19 @@ Client *make_client(Client *from, Client *servr)
 	return client;
 }
 
+/** Free the client->rpc struct.
+ * NOTE: if you want to fully free the entire client, call free_client()
+ */
+void free_client_rpc(Client *client)
+{
+	safe_free(client->rpc->rpc_user);
+	safe_free(client->rpc->issuer);
+	if (client->rpc->rehash_request)
+		json_decref(client->rpc->rehash_request);
+	free_log_sources(client->rpc->log_sources);
+	safe_free(client->rpc);
+}
+
 void free_client(Client *client)
 {
 	if (!list_empty(&client->client_node))
@@ -160,6 +175,19 @@ void free_client(Client *client)
 		RunHook(HOOKTYPE_FREE_CLIENT, client);
 		if (client->local)
 		{
+			if (client->local->listener)
+			{
+				if (client->local->listener && !IsOutgoing(client))
+				{
+					ConfigItem_listen *listener = client->local->listener;
+					listener->clients--;
+					if (listener->flag.temporary && (listener->clients == 0))
+					{
+						/* Call listen cleanup */
+						listen_cleanup();
+					}
+				}
+			}
 			safe_free(client->local->passwd);
 			safe_free(client->local->error_str);
 			if (client->local->hostp)
@@ -176,7 +204,10 @@ void free_client(Client *client)
 			del_from_id_hash_table(client->id, client);
 		}
 	}
-	
+
+	if (client->rpc)
+		free_client_rpc(client);
+
 	safe_free(client->ip);
 
 	mp_pool_release(client);
@@ -207,8 +238,12 @@ User *make_user(Client *client)
 		} else {
 			*user->realhost = '\0';
 		}
-		user->virthost = NULL;
-		client->user = user;		
+		client->user = user;
+		/* These may change later (eg when using hostname instead of IP),
+		 * but we now set it early.
+		 */
+		make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
 	}
 	return user;
 }
@@ -309,7 +344,7 @@ void remove_client_from_list(Client *client)
 
 	if (IsUser(client))	/* Only persons can have been added before */
 	{
-		add_history(client, 0);
+		add_history(client, 0, WHOWAS_EVENT_QUIT);
 		off_history(client);	/* Remove all pointers to client */
 	}
 	
@@ -590,6 +625,12 @@ NameValuePrioList *find_nvplist(NameValuePrioList *list, const char *name)
 	return NULL;
 }
 
+const char *get_nvplist(NameValuePrioList *list, const char *name)
+{
+	NameValuePrioList *e = find_nvplist(list, name);
+	return e ? e->value : NULL;
+}
+
 void add_fmt_nvplist(NameValuePrioList **lst, int priority, const char *name, FORMAT_STRING(const char *format), ...)
 {
 	char value[512];
diff --git a/src/log.c b/src/log.c
@@ -30,6 +30,8 @@
 // TODO: Make configurable at compile time (runtime won't do, as we haven't read the config file)
 #define show_event_console 0
 
+#define MAXLOGLENGTH 16384	/**< Maximum length of a log entry (which may be multiple lines) */
+
 /* Variables */
 Log *logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL };
 Log *temp_logs[NUM_LOG_DESTINATIONS] = { NULL, NULL, NULL, NULL, NULL };
@@ -43,59 +45,6 @@ int log_sources_match(LogSource *logsource, LogLevel loglevel, const char *subsy
 void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char *event_id, Client *client, int expand_msg, const char *msg, va_list vl);
 void log_blocks_switchover(void);
 
-/** Calculate expansion of a JSON string thanks to double escaping.
- * orig => JSON => IRC
- *    " => \"   => \\"
- *    \ => \\   => \\\\
- */
-int json_dump_string_length(const char *s)
-{
-	int len = 0;
-	for (; *s; s++)
-	{
-		if (*s == '\\')
-			len += 4;
-		else if (*s == '"')
-			len += 3;
-		else
-			len++;
-	}
-	return len;
-}
-
-/** Convert a regular string value to a JSON string.
- * In UnrealIRCd, this must be used instead of json_string()
- * as we may use non-UTF8 sequences. Also, this takes care
- * of using json_null() if the string was NULL, which is
- * usually what we want as well.
- * @param s	Input string
- * @returns a json string value or json null value.
- */
-json_t *json_string_unreal(const char *s)
-{
-	char buf1[512], buf2[512];
-	char *verified_s;
-	const char *stripped;
-
-	if (s == NULL)
-		return json_null();
-
-	stripped = StripControlCodesEx(s, buf1, sizeof(buf1), UNRL_STRIP_LOW_ASCII|UNRL_STRIP_KEEP_LF);
-	verified_s = unrl_utf8_make_valid(buf1, buf2, sizeof(buf2), 0);
-
-	return json_string(verified_s);
-}
-
-#define json_string __BAD___DO__NOT__USE__JSON__STRING__PLZ
-
-json_t *json_timestamp(time_t v)
-{
-	const char *ts = timestamp_iso8601(v);
-	if (ts)
-		return json_string_unreal(ts);
-	return json_null();
-}
-
 LogType log_type_stringtoval(const char *str)
 {
 	if (!strcmp(str, "json"))
@@ -118,6 +67,18 @@ const char *log_type_valtostring(LogType v)
 	}
 }
 
+/** Checks if 'v' is a valid loglevel */
+int valid_loglevel(int v)
+{
+	if ((v == ULOG_DEBUG) || (v == ULOG_INFO) ||
+	    (v == ULOG_WARNING) || (v == ULOG_ERROR) ||
+	    (v == ULOG_FATAL))
+	{
+		return 1;
+	}
+	return 0;
+}
+
 /***** CONFIGURATION ******/
 
 LogSource *add_log_source(const char *str)
@@ -511,273 +472,6 @@ int config_run_log(ConfigFile *conf, ConfigEntry *block)
 	return 0;
 }
 
-
-
-
-/***** RUNTIME *****/
-
-void json_expand_client_security_groups(json_t *parent, Client *client)
-{
-	SecurityGroup *s;
-	json_t *child = json_array();
-	json_object_set_new(parent, "security-groups", child);
-
-	/* We put known-users or unknown-users at the beginning.
-	 * The latter is special and doesn't actually exist
-	 * in the linked list, hence the special code here,
-	 * and again later in the for loop to skip it.
-	 */
-	if (user_allowed_by_security_group_name(client, "known-users"))
-		json_array_append_new(child, json_string_unreal("known-users"));
-	else
-		json_array_append_new(child, json_string_unreal("unknown-users"));
-
-	for (s = securitygroups; s; s = s->next)
-		if (strcmp(s->name, "known-users") && user_allowed_by_security_group(client, s))
-			json_array_append_new(child, json_string_unreal(s->name));
-}
-
-void json_expand_client(json_t *j, const char *key, Client *client, int detail)
-{
-	char buf[BUFSIZE+1];
-	json_t *child = json_object();
-	json_t *user = NULL;
-	json_object_set_new(j, key, child);
-
-	/* First the information that is available for ALL client types: */
-
-	json_object_set_new(child, "name", json_string_unreal(client->name));
-	json_object_set_new(child, "id", json_string_unreal(client->id));
-
-	/* hostname is available for all, it just depends a bit on whether it is DNS or IP */
-	if (client->user && *client->user->realhost)
-		json_object_set_new(child, "hostname", json_string_unreal(client->user->realhost));
-	else if (client->local && *client->local->sockhost)
-		json_object_set_new(child, "hostname", json_string_unreal(client->local->sockhost));
-	else
-		json_object_set_new(child, "hostname", json_string_unreal(GetIP(client)));
-
-	/* 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.
-	 */
-	if (client->user)
-	{
-		snprintf(buf, sizeof(buf), "%s!%s@%s", client->name, client->user->username, client->user->realhost);
-		json_object_set_new(child, "details", json_string_unreal(buf));
-	} else if (client->ip) {
-		if (*client->name)
-			snprintf(buf, sizeof(buf), "%s@%s", client->name, client->ip);
-		else
-			snprintf(buf, sizeof(buf), "[%s]", client->ip);
-		json_object_set_new(child, "details", json_string_unreal(buf));
-	} else {
-		json_object_set_new(child, "details", json_string_unreal(client->name));
-	}
-
-	if (client->local && client->local->creationtime)
-		json_object_set_new(child, "connected_since", json_timestamp(client->local->creationtime));
-
-	if (client->local && client->local->idle_since)
-		json_object_set_new(child, "idle_since", json_timestamp(client->local->idle_since));
-
-	if (client->user)
-	{
-		char buf[512];
-		const char *str;
-		/* client.user */
-		user = json_object();
-		json_object_set_new(child, "user", user);
-
-		json_object_set_new(user, "username", json_string_unreal(client->user->username));
-		if (!BadPtr(client->info))
-			json_object_set_new(user, "realname", json_string_unreal(client->info));
-		if (has_user_mode(client, 'x') && client->user->virthost && strcmp(client->user->virthost, client->user->realhost))
-			json_object_set_new(user, "vhost", json_string_unreal(client->user->virthost));
-		if (*client->user->cloakedhost)
-			json_object_set_new(user, "cloakedhost", json_string_unreal(client->user->cloakedhost));
-		if (client->uplink)
-			json_object_set_new(user, "servername", json_string_unreal(client->uplink->name));
-		if (IsLoggedIn(client))
-			json_object_set_new(user, "account", json_string_unreal(client->user->account));
-		json_object_set_new(user, "reputation", json_integer(GetReputation(client)));
-		json_expand_client_security_groups(user, client);
-
-		/* user modes and snomasks */
-		get_usermode_string_r(client, buf, sizeof(buf));
-		json_object_set_new(user, "modes", json_string_unreal(buf+1));
-		if (client->user->snomask)
-			json_object_set_new(user, "snomasks", json_string_unreal(client->user->snomask));
-
-		/* if oper then we can possibly expand a bit more */
-		str = get_operlogin(client);
-		if (str)
-			json_object_set_new(user, "operlogin", json_string_unreal(str));
-		str = get_operclass(client);
-		if (str)
-			json_object_set_new(user, "operclass", json_string_unreal(str));
-		if (client->user->channel)
-		{
-			Membership *m;
-			int cnt = 0;
-			int len = 0;
-			json_t *channels = json_array();
-			json_object_set_new(user, "channels", channels);
-			for (m = client->user->channel; m; m = m->next)
-			{
-				len += json_dump_string_length(m->channel->name);
-				if (len > 384)
-				{
-					/* Truncated */
-					json_array_append_new(channels, json_string_unreal("..."));
-					break;
-				}
-				json_array_append_new(channels, json_string_unreal(m->channel->name));
-			}
-		}
-		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_USER, client, detail, child, user);
-	} else
-	if (IsMe(client))
-	{
-		json_t *server = json_object();
-		json_t *features;
-
-		/* client.server */
-		json_object_set_new(child, "server", server);
-
-		if (!BadPtr(client->info))
-			json_object_set_new(server, "info", json_string_unreal(client->info));
-		json_object_set_new(server, "num_users", json_integer(client->server->users));
-		json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime));
-	} else
-	if (IsServer(client) && client->server)
-	{
-		/* client.server */
-
-		/* Whenever a server is expanded, which is rare,
-		 * we should probably expand as much as info as possible:
-		 */
-		json_t *server = json_object();
-		json_t *features;
-
-		/* client.server */
-		json_object_set_new(child, "server", server);
-		if (!BadPtr(client->info))
-			json_object_set_new(server, "info", json_string_unreal(client->info));
-		if (client->uplink)
-			json_object_set_new(server, "uplink", json_string_unreal(client->uplink->name));
-		json_object_set_new(server, "num_users", json_integer(client->server->users));
-		json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime));
-		json_object_set_new(server, "synced", json_boolean(client->server->flags.synced));
-
-		/* client.server.features */
-		features = json_object();
-		json_object_set_new(server, "features", features);
-		if (!BadPtr(client->server->features.software))
-			json_object_set_new(features, "software", json_string_unreal(client->server->features.software));
-		json_object_set_new(features, "protocol", json_integer(client->server->features.protocol));
-		if (!BadPtr(client->server->features.usermodes))
-			json_object_set_new(features, "usermodes", json_string_unreal(client->server->features.usermodes));
-		if (!BadPtr(client->server->features.chanmodes[0]))
-		{
-			/* client.server.features.chanmodes (array) */
-			int i;
-			json_t *chanmodes = json_array();
-			json_object_set_new(features, "chanmodes", chanmodes);
-			for (i=0; i < 4; i++)
-				json_array_append_new(chanmodes, json_string_unreal(client->server->features.chanmodes[i]));
-		}
-		if (!BadPtr(client->server->features.nickchars))
-			json_object_set_new(features, "nick_character_sets", json_string_unreal(client->server->features.nickchars));
-		RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server);
-	}
-	RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child);
-}
-
-void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail)
-{
-	char mode1[512], mode2[512], modes[512];
-
-	json_t *child = json_object();
-	json_object_set_new(j, key, child);
-	json_object_set_new(child, "name", json_string_unreal(channel->name));
-	json_object_set_new(child, "creation_time", json_timestamp(channel->creationtime));
-	json_object_set_new(child, "num_users", json_integer(channel->users));
-	if (channel->topic)
-	{
-		json_object_set_new(child, "topic", json_string_unreal(channel->topic));
-		json_object_set_new(child, "topic_set_by", json_string_unreal(channel->topic_nick));
-		json_object_set_new(child, "topic_set_at", json_timestamp(channel->topic_time));
-	}
-
-	/* Add "mode" too */
-	channel_modes(NULL, mode1, mode2, sizeof(mode1), sizeof(mode2), channel, 0);
-	if (*mode2)
-	{
-		snprintf(modes, sizeof(modes), "%s %s", mode1+1, mode2);
-		json_object_set_new(child, "modes", json_string_unreal(modes));
-	} else {
-		json_object_set_new(child, "modes", json_string_unreal(mode1+1));
-	}
-
-	// Possibly later: If detail is set to 1 then expand more...
-	RunHook(HOOKTYPE_JSON_EXPAND_CHANNEL, channel, detail, child);
-}
-
-const char *timestamp_iso8601_now(void)
-{
-	struct timeval t;
-	struct tm *tm;
-	time_t sec;
-	static char buf[64];
-
-	gettimeofday(&t, NULL);
-	sec = t.tv_sec;
-	tm = gmtime(&sec);
-
-	snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
-		tm->tm_year + 1900,
-		tm->tm_mon + 1,
-		tm->tm_mday,
-		tm->tm_hour,
-		tm->tm_min,
-		tm->tm_sec,
-		(int)(t.tv_usec / 1000));
-
-	return buf;
-}
-
-const char *timestamp_iso8601(time_t v)
-{
-	struct tm *tm;
-	static char buf[64];
-
-	if (v == 0)
-		return NULL;
-
-	tm = gmtime(&v);
-
-	if (tm == NULL)
-		return NULL;
-
-	snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
-		tm->tm_year + 1900,
-		tm->tm_mon + 1,
-		tm->tm_mday,
-		tm->tm_hour,
-		tm->tm_min,
-		tm->tm_sec,
-		0);
-
-	return buf;
-}
-
 LogData *log_data_string(const char *key, const char *str)
 {
 	LogData *d = safe_alloc(sizeof(LogData));
@@ -963,51 +657,7 @@ LogData *log_data_tkl(const char *key, TKL *tkl)
 	safe_strdup(d->key, key);
 	d->value.object = j = json_object();
 
-	json_object_set_new(j, "type", json_string_unreal(tkl_type_config_string(tkl))); // Eg 'kline'
-	json_object_set_new(j, "type_string", json_string_unreal(tkl_type_string(tkl))); // Eg 'Soft K-Line'
-	json_object_set_new(j, "set_by", json_string_unreal(tkl->set_by));
-	json_object_set_new(j, "set_at", json_timestamp(tkl->set_at));
-	json_object_set_new(j, "expire_at", json_timestamp(tkl->expire_at));
-	*buf = '\0';
-	short_date(tkl->set_at, buf);
-	strlcat(buf, " GMT", sizeof(buf));
-	json_object_set_new(j, "set_at_string", json_string_unreal(buf));
-	if (tkl->expire_at <= 0)
-	{
-		json_object_set_new(j, "expire_at_string", json_string_unreal("Never"));
-		json_object_set_new(j, "duration_string", json_string_unreal("permanent"));
-	} else {
-		*buf = '\0';
-		short_date(tkl->expire_at, buf);
-		strlcat(buf, " GMT", sizeof(buf));
-		json_object_set_new(j, "expire_at_string", json_string_unreal(buf));
-		json_object_set_new(j, "duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->expire_at - tkl->set_at)));
-	}
-	json_object_set_new(j, "set_at_delta", json_integer(TStime() - tkl->set_at));
-	if (TKLIsServerBan(tkl))
-	{
-		json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0)));
-		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.serverban->reason));
-	} else
-	if (TKLIsNameBan(tkl))
-	{
-		json_object_set_new(j, "name", json_string_unreal(tkl->ptr.nameban->name));
-		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.nameban->reason));
-	} else
-	if (TKLIsBanException(tkl))
-	{
-		json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0)));
-		json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.banexception->reason));
-		json_object_set_new(j, "exception_types", json_string_unreal(tkl->ptr.banexception->bantypes));
-	} else
-	if (TKLIsSpamfilter(tkl))
-	{
-		json_object_set_new(j, "name", json_string_unreal(tkl->ptr.spamfilter->match->str));
-		json_object_set_new(j, "match_type", json_string_unreal(unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type)));
-		json_object_set_new(j, "ban_action", json_string_unreal(banact_valtostring(tkl->ptr.spamfilter->action)));
-		json_object_set_new(j, "spamfilter_targets", json_string_unreal(spamfilter_target_inttostring(tkl->ptr.spamfilter->target)));
-		json_object_set_new(j, "reason", json_string_unreal(unreal_decodespace(tkl->ptr.spamfilter->tkl_reason)));
-	}
+	json_expand_tkl(j, NULL, tkl, 1);
 
 	return d;
 }
@@ -1111,22 +761,6 @@ int valid_subsystem(const char *s)
 	return 1;
 }
 
-const char *json_get_value(json_t *t)
-{
-	static char buf[32];
-
-	if (json_is_string(t))
-		return json_string_value(t);
-
-	if (json_is_integer(t))
-	{
-		snprintf(buf, sizeof(buf), "%lld", (long long)json_integer_value(t));
-		return buf;
-	}
-
-	return NULL;
-}
-
 // TODO: if in the function below we keep adding auto expanshion shit,
 // like we currently have $client automatically expanding to $client.name
 // and $socket_error to $socket_error.error_string,
@@ -1246,9 +880,9 @@ 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, Client *from_server)
+void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server)
 {
-	static int last_log_file_warning = 0;
+	static time_t last_log_file_warning = 0;
 	Log *l;
 	char timebuf[128];
 	struct stat fstats;
@@ -1259,7 +893,7 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 
 	snprintf(timebuf, sizeof(timebuf), "[%s] ", myctime(TStime()));
 
-	RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json_serialized, timebuf);
+	RunHook(HOOKTYPE_LOG, loglevel, subsystem, event_id, msg, json, json_serialized, timebuf);
 
 	if (!loop.forked && (loglevel > ULOG_DEBUG))
 	{
@@ -1366,17 +1000,27 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 			l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY);
 			if (l->logfd == -1)
 			{
-				if (!loop.booted)
+				if (errno == ENOENT)
 				{
-					config_status("WARNING: Unable to write to '%s': %s", l->file, strerror(errno));
-				} else {
-					if (last_log_file_warning + 300 < TStime())
+					/* Create directory structure and retry */
+					unreal_create_directory_structure_for_file(l->file, 0777);
+					l->logfd = fd_fileopen(l->file, O_CREAT|O_APPEND|O_WRONLY);
+				}
+				if (l->logfd == -1)
+				{
+					/* Still failed! */
+					if (!loop.booted)
 					{
-						config_status("WARNING: Unable to write to '%s': %s. This warning will not re-appear for at least 5 minutes.", l->file, strerror(errno));
-						last_log_file_warning = TStime();
+						config_status("WARNING: Unable to write to '%s': %s", l->file, strerror(errno));
+					} else {
+						if (last_log_file_warning + 300 < TStime())
+						{
+							config_status("WARNING: Unable to write to '%s': %s. This warning will not re-appear for at least 5 minutes.", l->file, strerror(errno));
+							last_log_file_warning = TStime();
+						}
 					}
+					continue;
 				}
-				continue;
 			}
 		}
 
@@ -1397,7 +1041,7 @@ void do_unreal_log_disk(LogLevel loglevel, const char *subsystem, const char *ev
 		{
 			for (m = msg; m; m = m->next)
 			{
-				char text_buf[8192];
+				static char text_buf[MAXLOGLENGTH];
 				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);
@@ -1686,7 +1330,7 @@ void do_unreal_log_remote(LogLevel loglevel, const char *subsystem, const char *
 }
 
 /** 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)
+void do_unreal_log_control(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *j, const char *json_serialized, Client *from_server)
 {
 	Client *client;
 	MultiLine *m;
@@ -1699,9 +1343,16 @@ void do_unreal_log_control(LogLevel loglevel, const char *subsystem, const char 
 		return;
 
 	list_for_each_entry(client, &control_list, lclient_node)
-		if (IsMonitorRehash(client))
+		if (IsMonitorRehash(client) && IsControl(client))
 			for (m = msg; m; m = m->next)
 				sendto_one(client, NULL, "REPLY [%s] %s", log_level_valtostring(loglevel), m->line);
+
+	if (json_rehash_log)
+	{
+		json_t *log = json_object_get(json_rehash_log, "log");
+		if (log)
+			json_array_append(log, j); // not xxx_new, right?
+	}
 }
 
 void do_unreal_log_free_args(va_list vl)
@@ -1777,11 +1428,14 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 	json_t *j = NULL;
 	json_t *j_details = NULL;
 	json_t *t;
-	char msgbuf[8192];
+	char msgbuf[MAXLOGLENGTH];
 	const char *loglevel_string = log_level_valtostring(loglevel);
 	MultiLine *mmsg;
 	Client *from_server = NULL;
 
+	/* Set flag so json_string_unreal() uses more strict filter */
+	log_json_filter = 1;
+
 	if (loglevel_string == NULL)
 	{
 		do_unreal_log_norecursioncheck(ULOG_ERROR, "log", "BUG_LOG_LOGLEVEL", NULL,
@@ -1826,7 +1480,7 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 	 * details later on.
 	 */
 	if (client)
-		json_expand_client(j_details, "client", client, 0);
+		json_expand_client(j_details, "client", client, 3);
 	/* Additional details (if any) */
 	while ((d = va_arg(vl, LogData *)))
 	{
@@ -1842,10 +1496,10 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 					json_object_set_new(j_details, d->key, json_null());
 				break;
 			case LOG_FIELD_CLIENT:
-				json_expand_client(j_details, d->key, d->value.client, 0);
+				json_expand_client(j_details, d->key, d->value.client, 3);
 				break;
 			case LOG_FIELD_CHANNEL:
-				json_expand_channel(j_details, d->key, d->value.channel, 0);
+				json_expand_channel(j_details, d->key, d->value.channel, 1);
 				break;
 			case LOG_FIELD_OBJECT:
 				json_object_set_new(j_details, d->key, d->value.object);
@@ -1884,10 +1538,10 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 
 	/* Now call all the loggers: */
 
-	do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+	do_unreal_log_disk(loglevel, subsystem, event_id, mmsg, j, 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_control(loglevel, subsystem, event_id, mmsg, j, json_serialized, from_server);
 
 	do_unreal_log_opers(loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
 
@@ -1897,27 +1551,52 @@ void do_unreal_log_internal(LogLevel loglevel, const char *subsystem, const char
 
 	// NOTE: code duplication further down!
 
+	/* This one should only be in do_unreal_log_internal()
+	 * and never in do_unreal_log_internal_from_remote()
+	 */
+	if (remote_rehash_client &&
+	    ((!strcmp(event_id, "CONFIG_ERROR_GENERIC") ||
+	      !strcmp(event_id, "CONFIG_WARNING_GENERIC") ||
+	      !strcmp(event_id, "CONFIG_INFO_GENERIC"))
+	     ||
+	     (loop.config_status >= CONFIG_STATUS_TEST)) &&
+	    strcmp(subsystem, "rawtraffic"))
+	{
+		sendto_log(remote_rehash_client, "NOTICE", remote_rehash_client->name,
+		           iConf.server_notice_colors, iConf.server_notice_show_event,
+		           loglevel, subsystem, event_id, mmsg, json_serialized, from_server);
+	}
+
 	/* Free everything */
 	safe_free(json_serialized);
 	safe_free_multiline(mmsg);
 	json_decref(j_details);
 	json_decref(j);
+
+	/* Turn off flag again */
+	log_json_filter = 0;
 }
 
 void do_unreal_log_internal_from_remote(LogLevel loglevel, const char *subsystem, const char *event_id,
-                                        MultiLine *msg, const char *json_serialized, Client *from_server)
+                                        MultiLine *msg, json_t *json, const char *json_serialized, Client *from_server)
 {
 	if (unreal_log_recursion_trap)
 		return;
 	unreal_log_recursion_trap = 1;
 
+	/* Set flag so json_string_unreal() uses more strict filter */
+	log_json_filter = 1;
+
 	/* Call the disk loggers */
-	do_unreal_log_disk(loglevel, subsystem, event_id, msg, json_serialized, from_server);
+	do_unreal_log_disk(loglevel, subsystem, event_id, msg, json, json_serialized, from_server);
 
 	/* 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);
 
+	/* Turn off flag again */
+	log_json_filter = 0;
+
 	unreal_log_recursion_trap = 0;
 }
 
@@ -1943,7 +1622,7 @@ void free_log_block(Log *l)
 
 int log_tests(void)
 {
-	if (snomask_num_destinations <= 1)
+	if (snomask_num_destinations == 0)
 	{
 		unreal_log(ULOG_ERROR, "config", "LOG_SNOMASK_BLOCK_MISSING", NULL,
 		           "Missing snomask logging configuration:\n"
@@ -1951,7 +1630,6 @@ int log_tests(void)
 		           "include \"snomasks.default.conf\";");
 		return 0;
 	}
-	snomask_num_destinations = 0;
 	return 1;
 }
 
@@ -2000,6 +1678,7 @@ void postconf_defaults_log_block(void)
 void log_pre_rehash(void)
 {
 	*snomasks_in_use_testing = '\0';
+	snomask_num_destinations = 0;
 }
 
 /* Called after CONFIG_TEST right before CONFIG_RUN */
diff --git a/src/macosx/UnrealIRCd/Base.lproj/Main.storyboard b/src/macosx/UnrealIRCd/Base.lproj/Main.storyboard
@@ -134,7 +134,7 @@
                             </button>
                             <button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fya-YD-1uu">
                                 <rect key="frame" x="132" y="88" width="274" height="18"/>
-                                <buttonCell key="cell" type="check" title="Automatically start Unreal Agent on login" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VUc-0Q-Rwf">
+                                <buttonCell key="cell" type="check" title="Automatically start UnrealIRCd Agent on login" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VUc-0Q-Rwf">
                                     <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                     <font key="font" metaFont="system"/>
                                 </buttonCell>
diff --git a/src/match.c b/src/match.c
@@ -405,7 +405,10 @@ Match *unreal_create_match(MatchType type, const char *str, char **error)
 		int options = 0;
 		char buf2[512];
 		
-		options = PCRE2_CASELESS|PCRE2_NEVER_UTF|PCRE2_NEVER_UCP;
+		if (iConf.spamfilter_utf8)
+			options = PCRE2_CASELESS|PCRE2_MATCH_INVALID_UTF;
+		else
+			options = PCRE2_CASELESS|PCRE2_NEVER_UTF|PCRE2_NEVER_UCP;
 		
 		m->ext.pcre2_expr = pcre2_compile(str, PCRE2_ZERO_TERMINATED, options, &errorcode, &erroroffset, NULL);
 		if (m->ext.pcre2_expr == NULL)
diff --git a/src/misc.c b/src/misc.c
@@ -471,7 +471,10 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
 			RunHook(HOOKTYPE_REMOTE_QUIT, client, mtags_i, comment);
 
 		new_message_special(client, mtags_i, &mtags_o, ":%s QUIT", client->name);
-		sendto_local_common_channels(client, NULL, 0, mtags_o, ":%s QUIT :%s", client->name, comment);
+		if (find_mtag(mtags_o, "unrealircd.org/real-quit-reason"))
+			quit_sendto_local_common_channels(client, mtags_o, comment);
+		else
+			sendto_local_common_channels(client, NULL, 0, mtags_o, ":%s QUIT :%s", client->name, comment);
 		free_message_tags(mtags_o);
 
 		while ((mp = client->user->channel))
@@ -594,17 +597,6 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons
 			}
 		}
 		free_pending_net(client);
-		if (client->local->listener)
-			if (client->local->listener && !IsOutgoing(client))
-			{
-				listen_conf = client->local->listener;
-				listen_conf->clients--;
-				if (listen_conf->flag.temporary && (listen_conf->clients == 0))
-				{
-					/* Call listen cleanup */
-					listen_cleanup();
-				}
-			}
 		SetClosing(client);
 		if (IsUser(client))
 		{
@@ -623,7 +615,7 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons
 
 		if (client->local->fd >= 0 && !IsConnecting(client))
 		{
-			if (!IsControl(client))
+			if (!IsControl(client) && !IsRPC(client))
 				sendto_one(client, NULL, "ERROR :Closing Link: %s (%s)", get_client_name(client, FALSE), comment);
 		}
 		close_connection(client);
@@ -648,6 +640,7 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons
 	if (IsServer(client))
 	{
 		char splitstr[HOSTLEN + HOSTLEN + 2];
+		Client *acptr, *next;
 
 		assert(client->server != NULL && client->uplink != NULL);
 
@@ -658,6 +651,11 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, cons
 
 		remove_dependents(client, origin, recv_mtags, comment, splitstr);
 
+		/* Special case for remote async RPC, server.rehash in particular.. */
+		list_for_each_entry_safe(acptr, next, &rpc_remote_list, client_node)
+			if (!strncmp(client->id, acptr->id, SIDLEN))
+				free_client(acptr);
+
 		RunHook(HOOKTYPE_SERVER_QUIT, client, recv_mtags);
 	}
 	else if (IsUser(client) && !IsKilled(client))
@@ -733,6 +731,54 @@ int valid_host(const char *host, int strict)
 	return 1;
 }
 
+/** Check if the specified ident / user name does not contain forbidden characters.
+ * @param username	The username / ident to check
+ * @returns 1 if valid, 0 if not.
+ */
+int valid_username(const char *username)
+{
+	const char *s;
+
+	if (strlen(username) > USERLEN)
+		return 0; /* Too long */
+
+	for (s = username; *s; s++)
+	{
+		if ((*s == '~') && (s == username))
+			continue;
+		if (!isallowed(*s))
+			return 0;
+	}
+
+	return 1;
+}
+
+/** Check validity of a vhost which can be both in 'host' or 'user@host' format.
+ * This will call valid_username() and valid_host(xxx, 0) accordingly.
+ * @param userhost the "host" or "user@host"
+ * @returns 1 if valid, 0 if not.
+ */
+int valid_vhost(const char *userhost)
+{
+	char uhost[512], *p;
+	const char *host = userhost;
+
+        strlcpy(uhost, userhost, sizeof(uhost));
+
+	if ((p = strchr(uhost, '@')))
+	{
+		*p++ = '\0';
+		if (!valid_username(uhost))
+			return 0;
+		host = p;
+	}
+
+	if (!valid_host(host, 0))
+		return 0;
+
+	return 1;
+}
+
 /*|| BAN ACTION ROUTINES FOLLOW ||*/
 
 /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */
@@ -832,9 +878,10 @@ char *spamfilter_target_inttostring(int v)
 /** Replace underscores back to the space character.
  * This is used for the spamfilter reason.
  */
-char *unreal_decodespace(char *s)
+char *unreal_decodespace(const char *s)
 {
-	static char buf[512], *i, *o;
+	const char *i;
+	static char buf[512], *o;
 
 	for (i = s, o = buf; (*i) && (o < buf+510); i++)
 		if (*i == '_')
@@ -855,9 +902,10 @@ char *unreal_decodespace(char *s)
 /** Replace spaces to underscore characters.
  * This is used for the spamfilter reason.
  */
-char *unreal_encodespace(char *s)
+char *unreal_encodespace(const char *s)
 {
-	static char buf[512], *i, *o;
+	const char *i;
+	static char buf[512], *o;
 
 	if (!s)
 		return NULL; /* NULL in = NULL out */
@@ -1113,9 +1161,10 @@ void banned_client(Client *client, const char *bantype, const char *reason, int 
 	char buf[512];
 	char *fmt = global ? iConf.reject_message_gline : iConf.reject_message_kline;
 	const char *vars[6], *values[6];
+	MessageTag *mtags = NULL;
 
 	if (!MyConnect(client))
-		abort(); /* hmm... or be more flexible? */
+		abort();
 
 	/* This was: "You are not welcome on this %s. %s: %s. %s" but is now dynamic: */
 	vars[0] = "bantype";
@@ -1154,18 +1203,28 @@ void banned_client(Client *client, const char *bantype, const char *reason, int 
 
 	/* The final message in the ERROR is shorter. */
 	if (HIDE_BAN_REASON && IsRegistered(client))
+	{
+		/* Hide the ban reason, but put the real reason in unrealircd.org/real-quit-reason */
+		MessageTag *m = safe_alloc(sizeof(MessageTag));
+		safe_strdup(m->name, "unrealircd.org/real-quit-reason");
+		snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
+		safe_strdup(m->value, buf);
+		AddListItem(m, mtags);
+		/* And the quit reason for anyone else, goes here.. */
 		snprintf(buf, sizeof(buf), "Banned (%s)", bantype);
-	else
+	} else {
 		snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
+	}
 
 	if (noexit != NO_EXIT_CLIENT)
 	{
-		exit_client(client, NULL, buf);
+		exit_client(client, mtags, buf);
 	} else {
 		/* Special handling for direct Z-line code */
 		send_raw_direct(client, "ERROR :Closing Link: [%s] (%s)",
 		           client->ip, buf);
 	}
+	safe_free_message_tags(mtags);
 }
 
 /** Our stpcpy implementation - discouraged due to lack of bounds checking */
@@ -1395,6 +1454,77 @@ int make_oper_default_handler(Client *client, const char *operblock_name, const 
 	return 0;
 }
 
+void webserver_send_response_default_handler(Client *client, int status, char *msg)
+{
+}
+
+void webserver_close_client_default_handler(Client *client)
+{
+}
+
+int webserver_handle_body_default_handler(Client *client, WebRequest *web, const char *readbuf, int length)
+{
+	return 0;
+}
+
+void rpc_response_default_handler(Client *client, json_t *request, json_t *result)
+{
+}
+
+void rpc_error_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *error_message)
+{
+}
+
+void rpc_error_fmt_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...)
+{
+}
+
+void rpc_send_request_to_remote_default_handler(Client *source, Client *target, json_t *request)
+{
+}
+
+void rpc_send_response_to_remote_default_handler(Client *source, Client *target, json_t *response)
+{
+}
+
+int rrpc_supported_simple_default_handler(Client *target, char **problem_server)
+{
+	if (problem_server)
+		*problem_server = me.name;
+	return 0;
+}
+
+int rrpc_supported_default_handler(Client *target, const char *module, const char *minimum_version, char **problem_server)
+{
+	if (problem_server)
+		*problem_server = me.name;
+	return 0;
+}
+
+int websocket_handle_websocket_default_handler(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
+{
+	return -1;
+}
+
+int websocket_create_packet_default_handler(int opcode, char **buf, int *len)
+{
+	return -1;
+}
+
+int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
+{
+	return -1;
+}
+
+int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len)
+{
+	return -1;
+}
+
+void mtag_add_issued_by_default_handler(MessageTag **mtags, Client *client, MessageTag *recv_mtags)
+{
+}
+
 /** my_timegm: mktime()-like function which will use GMT/UTC.
  * Strangely enough there is no standard function for this.
  * On some *NIX OS's timegm() may be available, sometimes only
@@ -2068,7 +2198,12 @@ int terminal_supports_color(void)
 #ifndef _WIN32
 	char *s;
 
-	/* Yeah we check all of stdin, stdout, stderr, because
+	/* Support NO_COLOR as per https://no-color.org */
+	s = getenv("NO_COLOR");
+	if (s != NULL && s[0] != '\0')
+		return 0;
+
+	/* Yeah we check all of stdin, stdout, stderr, because one
 	 * or more may be redirected (bin/unrealircd >log 2>&1),
 	 * and then we want to say no to color support.
 	 */
@@ -2424,6 +2559,7 @@ void server_reboot(const char *mesg)
 		WinExec(cmdLine, SW_SHOWDEFAULT);
 	}
 #endif
+	loop.terminating = 1;
 	unload_all_modules();
 #ifdef _WIN32
 	if (IsService)
@@ -2504,11 +2640,15 @@ const char *StripControlCodesEx(const char *text, char *output, size_t outputlen
 
 	while (len > 0) 
 	{
-		if ( col && ((isdigit(*text) && nc < 2) || (*text == ',' && nc < 3)))
+		if ((col && isdigit(*text) && nc < 2) ||
+		    ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3)))
 		{
 			nc++;
 			if (*text == ',')
+			{
 				nc = 0;
+				col++;
+			}
 		}
 		/* Syntax for RGB is ^DHHHHHH where H is a hex digit.
 		 * If < 6 hex digits are specified, the code is displayed
@@ -2611,3 +2751,11 @@ const char *StripControlCodes(const char *text)
 
 	return StripControlCodesEx(text, new_str, sizeof(new_str), 0);
 }
+
+const char *command_issued_by_rpc(MessageTag *mtags)
+{
+	MessageTag *m = find_mtag(mtags, "unrealircd.org/issued-by");
+	if (m && m->value && !strncmp(m->value, "RPC:", 4))
+		return m->value;
+	return NULL;
+}
diff --git a/src/modulemanager.c b/src/modulemanager.c
@@ -1173,7 +1173,7 @@ int mm_compile(ManagedModule *m, char *tmpfile, int test)
 		return 0;
 
 	snprintf(cmd, sizeof(cmd),
-	         "cd \"%s\"; make custommodule MODULEFILE=\"%s\"",
+	         "cd \"%s\"; $MAKE custommodule MODULEFILE=\"%s\"",
 	         BUILDDIR,
 	         filename_strip_suffix(basename, ".c")
 	         );
@@ -1312,7 +1312,7 @@ void mm_make_install(void)
 	if (no_make_install)
 		return;
 	printf("Running 'make install'...\n");
-	snprintf(cmd, sizeof(cmd), "cd \"%s\"; make install 1>/dev/null 2>&1", BUILDDIR);
+	snprintf(cmd, sizeof(cmd), "cd \"%s\"; $MAKE install 1>/dev/null 2>&1", BUILDDIR);
 	n = system(cmd);
 }
 
@@ -1652,6 +1652,46 @@ void mm_parse_c_file(int argc, char *args[])
 	exit(0);
 }
 
+int mm_detect_make_is_gmake(void)
+{
+	FILE *fd;
+	char buf[512], *s;
+
+	fd = popen("$MAKE --version 2>&1", "r");
+	if (fd)
+	{
+		*buf = '\0';
+		s = fgets(buf, sizeof(buf), fd);
+		pclose(fd);
+		if (s && strstr(s, "GNU Make"))
+			return 1; /* Good! We are done. */
+	}
+	return 0;
+}
+
+void mm_detect_make(void)
+{
+	FILE *fd;
+	char *s;
+	char buf[512];
+
+	/* Get or set $MAKE */
+	s = getenv("MAKE");
+	if (!s)
+		setenv("MAKE", "make", 1);
+
+	if (mm_detect_make_is_gmake())
+		return;
+
+	/* Try again with MAKE=gmake */
+	setenv("MAKE", "gmake", 1);
+	if (mm_detect_make_is_gmake())
+		return;
+
+	fprintf(stderr, "ERROR: GNU Make is not found as 'make' or 'gmake'\n");
+	exit(-1);
+}
+
 void mm_self_test(void)
 {
 	char name[512];
@@ -1672,6 +1712,7 @@ void mm_self_test(void)
 			exit(-1);
 		}
 	}
+	mm_detect_make();
 }
 
 void modulemanager(int argc, char *args[])
diff --git a/src/modules.c b/src/modules.c
@@ -430,18 +430,19 @@ const char *Module_Create(const char *path_)
 		if (Mod_Handle)
 			*Mod_Handle = mod;
 		irc_dlsym(Mod, "Mod_Test", Mod_Test);
+		/* add module here, so ModuleSetOptions() w/MOD_OPT_PRIORITY is available in Mod_Test() */
+		AddListItemPrio(mod, Modules, 0);
 		if (Mod_Test)
 		{
-			if ((ret = (*Mod_Test)(&mod->modinfo)) < MOD_SUCCESS) {
-				ircsnprintf(errorbuf, sizeof(errorbuf), "Mod_Test returned %i",
-					   ret);
+			if ((ret = (*Mod_Test)(&mod->modinfo)) < MOD_SUCCESS)
+			{
+				ircsnprintf(errorbuf, sizeof(errorbuf), "Mod_Test returned %i", ret);
 				/* We EXPECT the module to have cleaned up its mess */
 				Module_free(mod);
 				return (errorbuf);
 			}
 		}
 		mod->flags = MODFLAG_TESTING;		
-		AddListItemPrio(mod, Modules, 0);
 		return NULL;
 	}
 	else
@@ -560,6 +561,9 @@ void FreeModObj(ModuleObject *obj, Module *m)
 	else if (obj->type == MOBJ_HISTORY_BACKEND) {
 		HistoryBackendDel(obj->object.history_backend);
 	}
+	else if (obj->type == MOBJ_RPC) {
+		RPCHandlerDel(obj->object.rpc);
+	}
 	else
 	{
 		unreal_log(ULOG_FATAL, "module", "FREEMODOBJ_UNKNOWN_TYPE", NULL,
@@ -731,6 +735,8 @@ void module_loadall(void)
 	iFP	fp;
 	Module *mi, *next;
 	
+	loop.config_status = CONFIG_STATUS_LOAD;
+
 	/* Run through all modules and check for module load */
 	for (mi = Modules; mi; mi = next)
 	{
@@ -1216,7 +1222,7 @@ void ModuleSetOptions(Module *module, unsigned int options, int action)
 {
 	unsigned int oldopts = module->options;
 
-	if (options == MOD_OPT_UNLOAD_PRIORITY)
+	if (options == MOD_OPT_PRIORITY)
 	{
 		DelListItem(module, Modules);
 		AddListItemPrio(module, Modules, action);
@@ -1335,6 +1341,16 @@ int is_module_loaded(const char *name)
 		if (mi->flags & MODFLAG_DELAYED)
 			continue; /* unloading (delayed) */
 
+		/* During config_posttest ignore modules that are loaded,
+		 * since we only care about the 'future' state.
+		 */
+		if ((loop.config_status < CONFIG_STATUS_LOAD) &&
+		    (loop.config_status >= CONFIG_STATUS_POSTTEST) &&
+		    (mi->flags == MODFLAG_LOADED))
+		{
+			continue;
+		}
+
 		if (!strcasecmp(mi->relpath, name))
 			return 1;
 	}
@@ -1444,6 +1460,38 @@ void SavePersistentLongX(ModuleInfo *modinfo, const char *varshortname, long var
 	moddata_local_variable(m).l = var;
 }
 
+int LoadPersistentLongLongX(ModuleInfo *modinfo, const char *varshortname, long long *var)
+{
+	ModDataInfo *m;
+	const char *fullname = mod_var_name(modinfo, varshortname);
+
+	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
+	if (m)
+	{
+		*var = moddata_local_variable(m).ll;
+		return 1;
+	} else {
+		ModDataInfo mreq;
+		memset(&mreq, 0, sizeof(mreq));
+		mreq.type = MODDATATYPE_LOCAL_VARIABLE;
+		mreq.name = strdup(fullname);
+		mreq.free = NULL;
+		m = ModDataAdd(modinfo->handle, mreq);
+		moddata_local_variable(m).ll = 0;
+		safe_free(mreq.name);
+		return 0;
+	}
+}
+
+void SavePersistentLongLongX(ModuleInfo *modinfo, const char *varshortname, long long var)
+{
+	ModDataInfo *m;
+	const char *fullname = mod_var_name(modinfo, varshortname);
+
+	m = findmoddata_byname(fullname, MODDATATYPE_LOCAL_VARIABLE);
+	moddata_local_variable(m).ll = var;
+}
+
 extern int module_has_moddata(Module *mod);
 extern int module_has_extcmode_param_mode(Module *mod);
 
diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in
@@ -39,10 +39,10 @@ MODULES= \
 	sethost.so chghost.so chgident.so setname.so \
 	setident.so sdesc.so svsmode.so swhois.so\
 	svsmotd.so svsnline.so who_old.so whox.so mkpasswd.so \
-	away.so svsnoop.so svsnick.so svso.so \
+	away.so svsnoop.so svsnick.so svso.so svslogin.so \
 	chgname.so kill.so \
 	lag.so message.so oper.so pingpong.so \
-	quit.so sendumode.so sqline.so \
+	quit.so sendumode.so sqline.so sreply.so \
 	tsctl.so unsqline.so whois.so \
 	tkl.so vhost.so cycle.so svsjoin.so svspart.so \
 	svswatch.so svssilence.so sendsno.so svssno.so \
@@ -59,13 +59,14 @@ MODULES= \
 	botmotd.so lusers.so names.so svsnolag.so addmotd.so \
 	svslusers.so starttls.so webredir.so cap.so \
 	sasl.so md.so certfp.so \
-	tls_antidos.so webirc.so websocket.so \
+	tls_antidos.so connect-flood.so max-unknown-connections-per-ip.so \
+	webirc.so webserver.so websocket_common.so websocket.so \
 	blacklist.so jointhrottle.so \
 	antirandom.so hideserver.so jumpserver.so \
 	ircops.so staff.so nocodes.so \
 	charsys.so antimixedutf8.so authprompt.so sinfo.so \
 	reputation.so connthrottle.so history_backend_mem.so \
-	history_backend_null.so tkldb.so channeldb.so \
+	history_backend_null.so tkldb.so channeldb.so whowasdb.so \
 	restrict-commands.so rmtkl.so require-module.so \
 	account-notify.so \
 	message-tags.so batch.so \
@@ -79,7 +80,9 @@ MODULES= \
 	monitor.so slog.so tls_cipher.so operinfo.so creationtime.so \
 	unreal_server_compat.so \
 	extended-monitor.so geoip_csv.so \
-	geoip_base.so extjwt.so \
+	geoip_base.so extjwt.so tline.so \
+	standard-replies.so issued-by-tag.so \
+	real-quit-reason.so \
 	$(GEOIP_CLASSIC_OBJECTS) $(GEOIP_MAXMIND_OBJECTS)
 
 MODULEFLAGS=@MODULEFLAGS@
@@ -94,6 +97,7 @@ build: $(MODULES)
 	cd chanmodes; $(MAKE) all
 	cd usermodes; $(MAKE) all
 	cd extbans; $(MAKE) all
+	cd rpc; $(MAKE) all
 	cd third; $(MAKE) all
 
 clean:
@@ -101,6 +105,7 @@ clean:
 	cd chanmodes; $(MAKE) clean
 	cd usermodes; $(MAKE) clean
 	cd extbans; $(MAKE) clean
+	cd rpc; $(MAKE) clean
 	cd third; $(MAKE) clean
 
 # Generic *.so rule:
diff --git a/src/modules/antimixedutf8.c b/src/modules/antimixedutf8.c
@@ -205,7 +205,7 @@ CMD_OVERRIDE_FUNC(override_msg)
 		/* Short circuit for: remote clients, insufficient parameters,
 		 * antimixedutf8::except.
 		 */
-		CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+		CALL_NEXT_COMMAND_OVERRIDE();
 		return;
 	}
 
@@ -227,7 +227,7 @@ CMD_OVERRIDE_FUNC(override_msg)
 		}
 	}
 
-	CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+	CALL_NEXT_COMMAND_OVERRIDE();
 }
 
 /*** rest is module and config stuff ****/
diff --git a/src/modules/authprompt.c b/src/modules/authprompt.c
@@ -40,6 +40,7 @@ struct {
 typedef struct APUser APUser;
 struct APUser {
 	char *authmsg;
+	char *reason;
 };
 
 /* Global variables */
@@ -55,6 +56,7 @@ int authprompt_sasl_continuation(Client *client, const char *buf);
 int authprompt_sasl_result(Client *client, int success);
 int authprompt_place_host_ban(Client *client, int action, const char *reason, long duration);
 int authprompt_find_tkline_match(Client *client, TKL *tk);
+int authprompt_pre_local_handshake_timeout(Client *client, const char **comment);
 int authprompt_pre_connect(Client *client);
 CMD_FUNC(cmd_auth);
 void authprompt_md_free(ModData *md);
@@ -93,6 +95,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_SASL_RESULT, 0, authprompt_sasl_result);
 	HookAdd(modinfo->handle, HOOKTYPE_PLACE_HOST_BAN, 0, authprompt_place_host_ban);
 	HookAdd(modinfo->handle, HOOKTYPE_FIND_TKLINE_MATCH, 0, authprompt_find_tkline_match);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT, 0, authprompt_pre_local_handshake_timeout);
 	/* For HOOKTYPE_PRE_LOCAL_CONNECT we want a low priority, so we are called last.
 	 * This gives hooks like the one from the blacklist module (pending softban)
 	 * a chance to be handled first.
@@ -118,7 +121,7 @@ static void init_config(void)
 {
 	/* This sets some default values */
 	memset(&cfg, 0, sizeof(cfg));
-	cfg.enabled = 0;
+	cfg.enabled = 1;
 }
 
 static void config_postdefaults(void)
@@ -229,6 +232,7 @@ void authprompt_md_free(ModData *md)
 	if (se)
 	{
 		safe_free(se->authmsg);
+		safe_free(se->reason);
 		safe_free(se);
 		md->ptr = se = NULL;
 	}
@@ -294,6 +298,7 @@ void send_first_auth(Client *client)
 	Client *sasl_server;
 	char *addr = BadPtr(client->ip) ? "0" : client->ip;
 	const char *certfp = moddata_client_get(client, "certfp");
+
 	sasl_server = find_client(SASL_SERVER, NULL);
 	if (!sasl_server)
 	{
@@ -301,6 +306,10 @@ void send_first_auth(Client *client)
 		return;
 	}
 
+	/* Make them a user, needed for CHGHOST etc that we may receive */
+	if (!client->user)
+		make_user(client);
+
 	sendto_one(sasl_server, NULL, ":%s SASL %s %s H %s %s",
 	    me.id, SASL_SERVER, client->id, addr, addr);
 
@@ -364,15 +373,26 @@ CMD_FUNC(cmd_auth)
 	send_first_auth(client);
 }
 
-void authprompt_tag_as_auth_required(Client *client)
+void authprompt_tag_as_auth_required(Client *client, const char *reason)
 {
 	/* Allocate, and therefore indicate, that we are going to handle SASL for this user */
 	if (!SEUSER(client))
 		SetAPUser(client, safe_alloc(sizeof(APUser)));
+	safe_strdup(SEUSER(client)->reason, reason);
 }
 
 void authprompt_send_auth_required_message(Client *client)
 {
+	/* Send the standard-reply ACCOUNT_REQUIRED_TO_CONNECT if the client supports receiving it */
+	if (HasCapability(client, "standard-replies"))
+	{
+		const char *reason = SEUSER(client) && SEUSER(client)->reason ? SEUSER(client)->reason : NULL;
+		if (reason)
+			sendto_one(client, NULL, "FAIL * ACCOUNT_REQUIRED_TO_CONNECT :An account is required to connect: %s", reason);
+		else
+			sendto_one(client, NULL, "FAIL * ACCOUNT_REQUIRED_TO_CONNECT :An account is required to connect");
+	}
+
 	/* Display set::authentication-prompt::message */
 	sendnotice_multiline(client, cfg.message);
 }
@@ -383,14 +403,10 @@ int authprompt_place_host_ban(Client *client, int action, const char *reason, lo
 	/* If it's a soft-xx action and the user is not logged in
 	 * and the user is not yet online, then we will handle this user.
 	 */
-	if (IsSoftBanAction(action) && !IsLoggedIn(client) && !IsUser(client))
+	if (IsSoftBanAction(action) && !IsLoggedIn(client) && !IsUser(client) && cfg.enabled)
 	{
-		/* Send ban reason */
-		if (reason)
-			sendnotice(client, "%s", reason);
-
 		/* And tag the user */
-		authprompt_tag_as_auth_required(client);
+		authprompt_tag_as_auth_required(client, reason);
 		authprompt_send_auth_required_message(client);
 		return 1; /* pretend user is killed */
 	}
@@ -403,17 +419,14 @@ int authprompt_find_tkline_match(Client *client, TKL *tkl)
 	/* If it's a soft-xx action and the user is not logged in
 	 * and the user is not yet online, then we will handle this user.
 	 */
-	if (TKLIsServerBan(tkl) &&
+	if (cfg.enabled &&
+		TKLIsServerBan(tkl) &&
 	   (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) &&
 	   !IsLoggedIn(client) &&
 	   !IsUser(client))
 	{
-		/* Send ban reason */
-		if (tkl->ptr.serverban->reason)
-			sendnotice(client, "%s", tkl->ptr.serverban->reason);
-
 		/* And tag the user */
-		authprompt_tag_as_auth_required(client);
+		authprompt_tag_as_auth_required(client, tkl->ptr.serverban->reason);
 		authprompt_send_auth_required_message(client);
 		return 1; /* pretend user is killed */
 	}
@@ -423,7 +436,7 @@ int authprompt_find_tkline_match(Client *client, TKL *tkl)
 int authprompt_pre_connect(Client *client)
 {
 	/* If the user is tagged as auth required and not logged in, then.. */
-	if (SEUSER(client) && !IsLoggedIn(client))
+	if (SEUSER(client) && !IsLoggedIn(client) && cfg.enabled)
 	{
 		authprompt_send_auth_required_message(client);
 		return HOOK_DENY; /* do not process register_user() */
@@ -478,3 +491,17 @@ int authprompt_sasl_result(Client *client, int success)
 
 	return 1; /* inhibit success/failure message */
 }
+
+/** Override the default "Registration timeout" quit reason */
+int authprompt_pre_local_handshake_timeout(Client *client, const char **comment)
+{
+	if (SEUSER(client))
+	{
+		if (SEUSER(client)->reason)
+			*comment = SEUSER(client)->reason;
+		else
+			*comment = "Account required to connect";
+	}
+
+	return HOOK_CONTINUE;
+}
diff --git a/src/modules/blacklist.c b/src/modules/blacklist.c
@@ -765,8 +765,8 @@ int blacklist_action(Client *client, char *opernotice, BanAction ban_action, cha
 
 void blacklist_hit(Client *client, Blacklist *bl, int reply)
 {
-	char opernotice[512], banbuf[512];
-	const char *name[4], *value[4];
+	char opernotice[512], banbuf[512], reply_num[5];
+	const char *name[6], *value[6];
 	BLUser *blu = BLUSER(client);
 
 	if (find_tkline_match(client, 1))
@@ -779,12 +779,21 @@ void blacklist_hit(Client *client, Blacklist *bl, int reply)
 		snprintf(opernotice, sizeof(opernotice), "[Blacklist] IP %s matches blacklist %s (%s/reply=%d)",
 			GetIP(client), bl->name, bl->backend->dns->name, reply);
 
+	snprintf(reply_num, sizeof(reply_num), "%d", reply);
+
 	name[0] = "ip";
 	value[0] = GetIP(client);
 	name[1] = "server";
 	value[1] = me.name;
-	name[2] = NULL;
-	value[2] = NULL;
+	name[2] = "blacklist";
+	value[2] = bl->name;
+	name[3] = "dnsname";
+	value[3] = bl->backend->dns->name;
+	name[4] = "dnsreply";
+	value[4] = reply_num;
+	name[5] = NULL;
+	value[5] = NULL;
+	/* when adding more, be sure to update the array elements number in the definition of const char *name[] and value[] */
 
 	buildvarstring(bl->reason, banbuf, sizeof(banbuf), name, value);
 
diff --git a/src/modules/botmotd.c b/src/modules/botmotd.c
@@ -67,11 +67,9 @@ CMD_FUNC(cmd_botmotd)
 		return;
 
 	tld = find_tld(client);
-
-	motdline = NULL;
-	if (tld)
+	if (tld && tld->botmotd.lines)
 		motdline = tld->botmotd.lines;
-	if (!motdline)
+	else
 		motdline = botmotd.lines;
 
 	if (!motdline)
diff --git a/src/modules/certfp.c b/src/modules/certfp.c
@@ -27,7 +27,7 @@ void certfp_unserialize(const char *str, ModData *m);
 int certfp_handshake(Client *client);
 int certfp_connect(Client *client);
 int certfp_whois(Client *client, Client *target, NameValuePrioList **list);
-int certfp_log(Client *client, int detail, json_t *j);
+int certfp_json_expand_client(Client *client, int detail, json_t *j);
 
 ModDataInfo *certfp_md; /* Module Data structure which we acquire */
 
@@ -52,7 +52,7 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, certfp_handshake);
 	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, certfp_handshake);
 	HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, certfp_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, certfp_log);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, certfp_json_expand_client);
 
 	return MOD_SUCCESS;
 }
@@ -161,11 +161,14 @@ void certfp_unserialize(const char *str, ModData *m)
 	safe_strdup(m->str, str);
 }
 
-int certfp_log(Client *client, int detail, json_t *j)
+int certfp_json_expand_client(Client *client, int detail, json_t *j)
 {
 	json_t *tls;
 	const char *str;
 
+	if (detail < 2)
+		return 0;
+
 	str = moddata_client_get(client, "certfp");
 	if (!str)
 		return 0;
diff --git a/src/modules/chanmodes/delayjoin.c b/src/modules/chanmodes/delayjoin.c
@@ -264,7 +264,7 @@ int delayjoin_is_ok(Client *client, Channel *channel, char mode, const char *par
 
 int visible_in_channel(Client *client, Channel *channel)
 {
-	return channel_is_delayed(channel) && moded_user_invisible(client, channel);
+	return (channel_is_delayed(channel) || channel_is_post_delayed(channel)) && moded_user_invisible(client, channel);
 }
 
 
diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c
@@ -1,6 +1,6 @@
 /*
- * Channel Mode +f
- * (C) Copyright 2019 Syzop and the UnrealIRCd team
+ * Channel Mode +f and +F
+ * (C) Copyright 2019-.. Syzop and the UnrealIRCd team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,8 +22,8 @@
 ModuleHeader MOD_HEADER
   = {
 	"chanmodes/floodprot",
-	"5.0",
-	"Channel Mode +f",
+	"6.0",
+	"Channel Mode +f and +F",
 	"UnrealIRCd Team",
 	"unrealircd-6",
     };
@@ -43,7 +43,11 @@ typedef enum Flood {
 struct {
 	unsigned char modef_default_unsettime;
 	unsigned char modef_max_unsettime;
-	long modef_boot_delay;
+	long boot_delay;
+	long split_delay;
+	int modef_alternate_action_percentage_threshold;
+	unsigned char modef_alternative_ban_action_unsettime;
+	char *default_profile;
 } cfg;
 
 typedef struct FloodType {
@@ -52,27 +56,28 @@ typedef struct FloodType {
 	char *description;
 	char default_action;
 	char *actions;
+	char *alternative_ban_action;
 	int timedban_required;
 } FloodType;
 
 /* All the floodtypes that are tracked.
- * IMPORTANT: MUST be in alphabetic order!!
+ * IMPORTANT: the first row MUST be in alphabetic order!!
  */
 FloodType floodtypes[] = {
-	{ 'c', CHFLD_CTCP,	"CTCPflood",		'C',	"mM",	0, },
-	{ 'j', CHFLD_JOIN,	"joinflood",		'i',	"R",	0, },
-	{ 'k', CHFLD_KNOCK,	"knockflood",		'K',	"",	0, },
-	{ 'm', CHFLD_MSG,	"msg/noticeflood",	'm',	"M",	0, },
-	{ 'n', CHFLD_NICK,	"nickflood",		'N',	"",	0, },
-	{ 't', CHFLD_TEXT,	"msg/noticeflood",	'\0',	"bd",	1, },
-	{ 'r', CHFLD_REPEAT,	"repeating",		'\0',	"bd",	1, },
+	{ 'c', CHFLD_CTCP,	"CTCPflood",		'C',	"",	NULL,						0, },
+	{ 'j', CHFLD_JOIN,	"joinflood",		'i',	"R",	"~security-group:unknown-users",		0, },
+	{ 'k', CHFLD_KNOCK,	"knockflood",		'K',	"",	NULL,						0, },
+	{ 'm', CHFLD_MSG,	"msg/noticeflood",	'm',	"M",	"~quiet:~security-group:unknown-users",		0, },
+	{ 'n', CHFLD_NICK,	"nickflood",		'N',	"",	"~nickchange:~security-group:unknown-users",	0, },
+	{ 't', CHFLD_TEXT,	"msg/noticeflood",	'\0',	"bd",	NULL,						1, },
+	{ 'r', CHFLD_REPEAT,	"repeating",		'\0',	"bd",	NULL,						1, },
 };
 
 #define MODEF_DEFAULT_UNSETTIME		cfg.modef_default_unsettime
 #define MODEF_MAX_UNSETTIME		cfg.modef_max_unsettime
-#define MODEF_BOOT_DELAY		cfg.modef_boot_delay
 
 typedef struct ChannelFloodProtection ChannelFloodProtection;
+typedef struct ChannelFloodProfile ChannelFloodProfile;
 typedef struct RemoveChannelModeTimer RemoveChannelModeTimer;
 
 struct RemoveChannelModeTimer {
@@ -93,52 +98,72 @@ struct MemberFlood {
 
 /* Maximum timers, iotw: max number of possible actions.
  * Currently this is: CNmMKiRd (8)
+ * But bumped to 15 because we now have cmode.flood_type_action
+ * so there could be more ;).
  */
-#define MAXCHMODEFACTIONS 8
+#define MAXCHMODEFACTIONS 15
 
 /** Per-channel flood protection settings and counters */
 struct ChannelFloodProtection {
 	unsigned short	per; /**< setting: per <XX> seconds */
 	time_t		timer[NUMFLD]; /**< runtime: timers */
 	unsigned short	counter[NUMFLD]; /**< runtime: counters */
+	unsigned short	counter_unknown_users[NUMFLD]; /**< runtime: counters */
 	unsigned short	limit[NUMFLD]; /**< setting: limit */
 	unsigned char	action[NUMFLD]; /**< setting: action */
 	unsigned char	remove_after[NUMFLD]; /**< setting: remove-after <this> minutes */
 	unsigned char   timers_running[MAXCHMODEFACTIONS+1]; /**< if for example a '-m' timer is running then this contains 'm' */
+	char *profile;
+};
+
+struct ChannelFloodProfile {
+	ChannelFloodProfile *prev, *next;
+	ChannelFloodProtection settings;
 };
 
 /* Global variables */
 ModDataInfo *mdflood = NULL;
 Cmode_t EXTMODE_FLOODLIMIT = 0L;
+Cmode_t EXTMODE_FLOOD_PROFILE = 0L;
 static int timedban_available = 1; /**< Set to 1 if extbans/timedban module is loaded. Assumed 1 during config load due to set::modes-on-join race. */
 RemoveChannelModeTimer *removechannelmodetimer_list = NULL;
+ChannelFloodProfile *channel_flood_profiles = NULL;
 char *floodprot_msghash_key = NULL;
+long long floodprot_splittime = 0;
 
-#define IsFloodLimit(x)	((x)->mode.mode & EXTMODE_FLOODLIMIT)
+#define IsFloodLimit(x)	(((x)->mode.mode & EXTMODE_FLOODLIMIT) || ((x)->mode.mode & EXTMODE_FLOOD_PROFILE) || (cfg.default_profile && GETPARASTRUCT(channel, 'F')))
 
 /* Forward declarations */
 static void init_config(void);
 int floodprot_rehash_complete(void);
-int floodprot_config_test(ConfigFile *, ConfigEntry *, int, int *);
-int floodprot_config_run(ConfigFile *, ConfigEntry *, int);
-void floodprottimer_del(Channel *channel, char mflag);
+int floodprot_config_test_set_block(ConfigFile *, ConfigEntry *, int, int *);
+int floodprot_config_run_set_block(ConfigFile *, ConfigEntry *, int);
+int floodprot_config_test_antiflood_block(ConfigFile *, ConfigEntry *, int, int *);
+int floodprot_config_run_antiflood_block(ConfigFile *, ConfigEntry *, int);
+void floodprottimer_del(Channel *channel, ChannelFloodProtection *fld, char mflag);
 void floodprottimer_stopchantimers(Channel *channel);
 static inline char *chmodefstrhelper(char *buf, char t, char tdef, unsigned short l, unsigned char a, unsigned char r);
 static int compare_floodprot_modes(ChannelFloodProtection *a, ChannelFloodProtection *b);
 static int do_floodprot(Channel *channel, Client *client, int what);
 char *channel_modef_string(ChannelFloodProtection *x, char *str);
 void do_floodprot_action(Channel *channel, int what);
-void floodprottimer_add(Channel *channel, char mflag, time_t when);
+void floodprottimer_add(Channel *channel, ChannelFloodProtection *fld, char mflag, time_t when);
 uint64_t gen_floodprot_msghash(const char *text);
 int cmodef_is_ok(Client *client, Channel *channel, char mode, const char *para, int type, int what);
 void *cmodef_put_param(void *r_in, const char *param);
 const char *cmodef_get_param(void *r_in);
 const char *cmodef_conv_param(const char *param_in, Client *client, Channel *channel);
-void cmodef_free_param(void *r);
+int cmodef_free_param(void *r, int soft);
 void *cmodef_dup_struct(void *r_in);
 int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx);
+int cmodef_profile_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what);
+void *cmodef_profile_put_param(void *r_in, const char *param);
+const char *cmodef_profile_get_param(void *r_in);
+const char *cmodef_profile_conv_param(const char *param_in, Client *client, Channel *channel);
+int cmodef_profile_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int floodprot_join(Client *client, Channel *channel, MessageTag *mtags);
 EVENT(modef_event);
+int cmodef_channel_create(Channel *channel);
 int cmodef_channel_destroy(Channel *channel, int *should_destroy);
 int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
 int floodprot_post_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype);
@@ -149,10 +174,18 @@ void memberflood_free(ModData *md);
 int floodprot_stats(Client *client, const char *flag);
 void floodprot_free_removechannelmodetimer_list(ModData *m);
 void floodprot_free_msghash_key(ModData *m);
+CMD_OVERRIDE_FUNC(floodprot_override_mode);
+ChannelFloodProtection *get_channel_flood_profile(const char *name);
+int parse_channel_mode_flood(const char *param, ChannelFloodProtection *fld, int strict, Client *client, const char **error_out);
+int parse_channel_mode_flood_failed(const char **error_out, ChannelFloodProtection *fld, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,3,4)));
+int floodprot_server_quit(Client *client, MessageTag *mtags);
+void inherit_settings(ChannelFloodProtection *from, ChannelFloodProtection *to);
+void reapply_profiles(void);
 
 MOD_TEST()
 {
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, floodprot_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, floodprot_config_test_set_block);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, floodprot_config_test_antiflood_block);
 	return MOD_SUCCESS;
 }
 
@@ -163,6 +196,8 @@ MOD_INIT()
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
+	LoadPersistentLongLong(modinfo, floodprot_splittime);
+
 	memset(&creq, 0, sizeof(creq));
 	creq.paracount = 1;
 	creq.is_ok = cmodef_is_ok;
@@ -176,6 +211,18 @@ MOD_INIT()
 	creq.sjoin_check = cmodef_sjoin_check;
 	CmodeAdd(modinfo->handle, creq, &EXTMODE_FLOODLIMIT);
 
+	memset(&creq, 0, sizeof(creq));
+	creq.paracount = 1;
+	creq.is_ok = cmodef_profile_is_ok;
+	creq.letter = 'F';
+	creq.put_param = cmodef_profile_put_param;
+	creq.get_param = cmodef_profile_get_param;
+	creq.conv_param = cmodef_profile_conv_param;
+	creq.free_param = cmodef_free_param; // +f & +F uses same code
+	creq.dup_struct = cmodef_dup_struct; // +f & +F uses same code
+	creq.sjoin_check = cmodef_profile_sjoin_check;
+	CmodeAdd(modinfo->handle, creq, &EXTMODE_FLOOD_PROFILE);
+
 	init_config();
 
 	LoadPersistentPointer(modinfo, removechannelmodetimer_list, floodprot_free_removechannelmodetimer_list);
@@ -194,7 +241,8 @@ MOD_INIT()
 		siphash_generate_key(floodprot_msghash_key);
 	}
 
-	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, floodprot_config_run);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, floodprot_config_run_set_block);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, floodprot_config_run_antiflood_block);
 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, floodprot_can_send_to_channel);
 	HookAdd(modinfo->handle, HOOKTYPE_CHANMSG, 0, floodprot_post_chanmsg);
 	HookAdd(modinfo->handle, HOOKTYPE_KNOCK, 0, floodprot_knock);
@@ -203,23 +251,49 @@ MOD_INIT()
 	HookAdd(modinfo->handle, HOOKTYPE_MODECHAR_DEL, 0, floodprot_chanmode_del);
 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, floodprot_join);
 	HookAdd(modinfo->handle, HOOKTYPE_REMOTE_JOIN, 0, floodprot_join);
+	HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_CREATE, 0, cmodef_channel_create);
 	HookAdd(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, 0, cmodef_channel_destroy);
 	HookAdd(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, 0, floodprot_rehash_complete);
 	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, floodprot_stats);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, floodprot_server_quit);
 	return MOD_SUCCESS;
 }
 
 MOD_LOAD()
 {
 	EventAdd(modinfo->handle, "modef_event", modef_event, NULL, 10000, 0);
+	CommandOverrideAdd(modinfo->handle, "MODE", 0, floodprot_override_mode);
 	floodprot_rehash_complete();
+	reapply_profiles();
 	return MOD_SUCCESS;
 }
 
+void free_channel_flood_profile(ChannelFloodProfile *f)
+{
+	safe_free(f->settings.profile);
+	safe_free(f);
+}
+
+void free_channel_flood_profiles(void)
+{
+	ChannelFloodProfile *f, *f_next;
+
+	for (f = channel_flood_profiles; f; f = f_next)
+	{
+		f_next = f->next;
+		DelListItem(f, channel_flood_profiles);
+		free_channel_flood_profile(f);
+	}
+}
+
 MOD_UNLOAD()
 {
 	SavePersistentPointer(modinfo, removechannelmodetimer_list);
 	SavePersistentPointer(modinfo, floodprot_msghash_key);
+	SavePersistentLongLong(modinfo, floodprot_splittime);
+
+	free_channel_flood_profiles();
+
 	return MOD_SUCCESS;
 }
 
@@ -229,16 +303,75 @@ int floodprot_rehash_complete(void)
 	return 0;
 }
 
+/** Set a new channel anti flood profile.
+ * Caller MUST ensure that the 'value' is valid, eg by calling
+ * parse_channel_mode_flood() or is_ok() prior.
+ */
+static void set_channel_flood_profile(const char *name, const char *value)
+{
+	ChannelFloodProfile *f;
+
+	for (f = channel_flood_profiles; f; f = f->next)
+		if (!strcasecmp(f->settings.profile, name))
+			break;
+	if (!f)
+	{
+		f = safe_alloc(sizeof(ChannelFloodProfile));
+		AddListItem(f, channel_flood_profiles);
+	}
+
+	safe_strdup(f->settings.profile, name);
+	cmodef_put_param(&f->settings, value);
+}
+
+static void init_default_channel_flood_profiles(void)
+{
+	ChannelFloodProfile *f;
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	cmodef_put_param(&f->settings, "[10j#R10,30m#M10,7c#C15,5n#N15,10k#K15]:15");
+	safe_strdup(f->settings.profile, "very-strict");
+	AddListItem(f, channel_flood_profiles);
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	cmodef_put_param(&f->settings, "[15j#R10,40m#M10,7c#C15,8n#N15,10k#K15]:15");
+	safe_strdup(f->settings.profile, "strict");
+	AddListItem(f, channel_flood_profiles);
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	cmodef_put_param(&f->settings, "[30j#R10,40m#M10,7c#C15,8n#N15,10k#K15]:15");
+	safe_strdup(f->settings.profile, "normal");
+	AddListItem(f, channel_flood_profiles);
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	cmodef_put_param(&f->settings, "[45j#R10,60m#M10,7c#C15,10n#N15,10k#K15]:15");
+	safe_strdup(f->settings.profile, "relaxed");
+	AddListItem(f, channel_flood_profiles);
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	cmodef_put_param(&f->settings, "[60j#R10,90m#M10,7c#C15,10n#N15,10k#K15]:15");
+	safe_strdup(f->settings.profile, "very-relaxed");
+	AddListItem(f, channel_flood_profiles);
+
+	f = safe_alloc(sizeof(ChannelFloodProfile));
+	safe_strdup(f->settings.profile, "off");
+	AddListItem(f, channel_flood_profiles);
+}
+
 static void init_config(void)
 {
 	/* This sets some default values */
 	memset(&cfg, 0, sizeof(cfg));
 	cfg.modef_default_unsettime = 0;
 	cfg.modef_max_unsettime = 60; /* 1 hour seems enough :p */
-	cfg.modef_boot_delay = 75;
+	cfg.boot_delay = 75;
+	cfg.split_delay = 75;
+	cfg.modef_alternate_action_percentage_threshold = 75; /* 75% */
+	cfg.modef_alternative_ban_action_unsettime = 15; /* 15min */
+	init_default_channel_flood_profiles();
 }
 
-int floodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+int floodprot_config_test_set_block(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
 	int errors = 0;
 
@@ -281,20 +414,10 @@ int floodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	} else
 	if (!strcmp(ce->name, "modef-boot-delay"))
 	{
-		if (!ce->value)
-		{
-			config_error_empty(ce->file->filename, ce->line_number,
-				"set", ce->name);
-			errors++;
-		} else {
-			long v = config_checkval(ce->value, CFG_TIME);
-			if ((v < 0) || (v > 600))
-			{
-				config_error("%s:%i: set::modef-boot-delay: value '%ld' out of range (should be 0-600)",
-					ce->file->filename, ce->line_number, v);
-				errors++;
-			}
-		}
+		config_error("%s:%i: set::modef-boot-delay is now called set::anti-flood::channel::boot-delay. "
+		             "See https://www.unrealircd.org/docs/Channel_anti-flood_settings#config",
+		             ce->file->filename, ce->line_number);
+		errors++;
 	} else
 	{
 		/* Not handled by us */
@@ -305,7 +428,7 @@ int floodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	return errors ? -1 : 1;
 }
 
-int floodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+int floodprot_config_run_set_block(ConfigFile *cf, ConfigEntry *ce, int type)
 {
 	if (type != CONFIG_SET)
 		return 0;
@@ -314,174 +437,268 @@ int floodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 		cfg.modef_default_unsettime = (unsigned char)atoi(ce->value);
 	else if (!strcmp(ce->name, "modef-max-unsettime"))
 		cfg.modef_max_unsettime = (unsigned char)atoi(ce->value);
-	else if (!strcmp(ce->name, "modef-boot-delay"))
-		cfg.modef_boot_delay = config_checkval(ce->value, CFG_TIME);
 	else
 		return 0; /* not handled by us */
 
 	return 1;
 }
 
-FloodType *find_floodprot_by_letter(char c)
+/** Check if 'str' is a flood profile name
+ */
+int valid_flood_profile_name(const char *str)
 {
-	int i;
-	for (i=0; i < ARRAY_SIZEOF(floodtypes); i++)
-		if (floodtypes[i].letter == c)
-			return &floodtypes[i];
-
-	return NULL;
+	if (strlen(str) > 24)
+		return 0;
+	for (; *str; str++)
+		if (!islower(*str) && !isdigit(*str) && !strchr("_-", *str))
+			return 0;
+	return 1;
 }
 
-FloodType *find_floodprot_by_index(Flood index)
+int floodprot_config_test_antiflood_block(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
-	int i;
-	for (i=0; i < ARRAY_SIZEOF(floodtypes); i++)
-		if (floodtypes[i].index == index)
-			return &floodtypes[i];
+	int errors = 0;
+	ConfigEntry *cep;
 
-	return NULL;
-}
+	/* We only deal with set::anti-flood::channel */
+	if ((type != CONFIG_SET_ANTI_FLOOD) || strcmp(ce->parent->name, "channel"))
+		return 0;
 
-int cmodef_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
-{
-	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	for (; ce; ce = ce->next)
 	{
-		if (IsUser(client) && check_channel_access(client, channel, "oaq"))
-			return EX_ALLOW;
-		if (type == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
-			sendnumeric(client, ERR_NOTFORHALFOPS, 'f');
-		return EX_DENY;
-	} else
-	if (type == EXCHK_PARAM)
-	{
-		ChannelFloodProtection newf;
-		char xbuf[256], c, a, *p, *p2, *x = xbuf+1;
-		int v;
-		unsigned short warnings = 0, breakit;
-		unsigned char r;
-		FloodType *floodtype;
-		Flood index;
-
-		memset(&newf, 0, sizeof(newf));
-
-		/* old +f was like +f 10:5 or +f *10:5 - no longer supported */
-		if ((param[0] != '[') || strlen(param) < 3)
-			goto invalidsyntax;;
-
-		/* '['<number><1 letter>[optional: '#'+1 letter],[next..]']'':'<number> */
-		strlcpy(xbuf, param, sizeof(xbuf));
-		p2 = strchr(xbuf+1, ']');
-		if (!p2)
-			goto invalidsyntax;
-		*p2 = '\0';
-		if (*(p2+1) != ':')
-			goto invalidsyntax;
-
-		breakit = 0;
-		for (x = strtok(xbuf+1, ","); x; x = strtok(NULL, ","))
+		if (!strcmp(ce->name, "default-profile"))
 		{
-			/* <number><1 letter>[optional: '#'+1 letter] */
-			p = x;
-			while(isdigit(*p)) { p++; }
-			c = *p;
-			floodtype = find_floodprot_by_letter(c);
-			if (!floodtype)
+			if (!ce->value)
 			{
-				if (MyUser(client) && *p && (warnings++ < 3))
-					sendnotice(client, "warning: channelmode +f: floodtype '%c' unknown, ignored.", *p);
-				continue; /* continue instead of break for forward compatability. */
+				config_error_noname(ce->file->filename, ce->line_number,
+				                    "set::anti-flood::channel::default-profile");
+				errors++;
+				continue;
 			}
-			*p = '\0';
-			v = atoi(x);
-			if ((v < 1) || (v > 999)) /* out of range... */
+		} else
+		if (!strcmp(ce->name, "boot-delay") || !strcmp(ce->name, "split-delay"))
+		{
+			if (!ce->value)
 			{
-				if (MyUser(client))
+				config_error_empty(ce->file->filename, ce->line_number,
+					"set", ce->name);
+				errors++;
+			} else {
+				long v = config_checkval(ce->value, CFG_TIME);
+				if ((v < 0) || (v > 600))
 				{
-					sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'f', "value should be from 1-999");
-					goto invalidsyntax;
-				} else
-					continue; /* just ignore for remote servers */
+					config_error("%s:%i: set::anti-flood::channel::%s: value '%ld' out of range (should be 0-600)",
+						ce->file->filename, ce->line_number,
+						ce->name,
+						v);
+					errors++;
+				}
+			}
+		} else
+		if (!strcmp(ce->name, "profile"))
+		{
+			if (!ce->value)
+			{
+				config_error_noname(ce->file->filename, ce->line_number,
+				                    "set::anti-flood::channel::profile");
+				errors++;
+				continue;
 			}
-			p++;
-			a = '\0';
-			r = MyUser(client) ? MODEF_DEFAULT_UNSETTIME : 0;
-			if (*p != '\0')
+			if (!valid_flood_profile_name(ce->value))
 			{
-				if (*p == '#')
+				config_error("%s:%i: set::anti-flood::channel: profile '%s' name is invalid. "
+				             "Name can be 24 characters max and may only contain characters a-z, 0-9, _ and -",
+				             ce->file->filename, ce->line_number, ce->value);
+				errors++;
+				continue;
+			}
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "flood-mode"))
 				{
-					p++;
-					a = *p;
-					p++;
-					if (*p != '\0')
+					ChannelFloodProtection fld;
+					const char *err;
+
+					if (!cep->value)
 					{
-						int tv;
-						tv = atoi(p);
-						if (tv <= 0)
-							tv = 0; /* (ignored) */
-						if (tv > (MyUser(client) ? MODEF_MAX_UNSETTIME : 255))
-							tv = (MyUser(client) ? MODEF_MAX_UNSETTIME : 255); /* set to max */
-						r = (unsigned char)tv;
+						config_error("%s:%i: set::anti-flood::channel::profile %s::flood-mode has no value",
+						             cep->file->filename, cep->line_number, ce->value);
+						errors++;
+						continue;
 					}
+					memset(&fld, 0, sizeof(fld));
+					if (!parse_channel_mode_flood(cep->value, &fld, 1, NULL, &err))
+					{
+						config_error("%s:%i: set::anti-flood::channel::profile %s::flood-mode: %s",
+						             cep->file->filename, cep->line_number,
+						             ce->value,
+						             cep->value);
+						errors++;
+					} else if (!BadPtr(err))
+					{
+						config_warn("%s:%i: set::anti-flood::channel::profile %s::flood-mode: %s",
+						             cep->file->filename, cep->line_number,
+						             ce->value,
+						             err);
+					}
+					if (fld.limit[CHFLD_TEXT] || fld.limit[CHFLD_REPEAT])
+					{
+						config_error("%s:%i: set::anti-flood::channel::profile %s::flood-mode: "
+						             "subtypes 't' and 'r' are not supported for +F profiles at the moment.",
+						             cep->file->filename, cep->line_number,
+						             ce->value);
+						errors++;
+					}
+				} else {
+					config_error_unknown(cep->file->filename, cep->line_number,
+							     "set::anti-flood::channel::profile", cep->name);
+					errors++;
 				}
 			}
+		} else
+		{
+			config_error_unknown(ce->file->filename, ce->line_number,
+			                     "set::anti-flood::channel", ce->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -2 : 2;
+}
+
+int floodprot_config_run_antiflood_block(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	/* We only deal with set::anti-flood::channel */
+	if ((type != CONFIG_SET_ANTI_FLOOD) || strcmp(ce->parent->name, "channel"))
+		return 0;
 
-			index = floodtype->index;
-			newf.limit[index] = v;
-			if (a && strchr(floodtype->actions, a))
-				newf.action[index] = a;
-			else
-				newf.action[index] = floodtype->default_action;
-			if (!floodtype->timedban_required || (floodtype->timedban_required && timedban_available))
-				newf.remove_after[index] = r;
-		} /* for */
-		/* parse 'per' */
-		p2++;
-		if (*p2 != ':')
-			goto invalidsyntax;
-		p2++;
-		if (!*p2)
-			goto invalidsyntax;
-		v = atoi(p2);
-		if ((v < 1) || (v > 999)) /* 'per' out of range */
+	for (; ce; ce = ce->next)
+	{
+		if (!strcmp(ce->name, "default-profile"))
+		{
+			safe_strdup(cfg.default_profile, ce->value);
+		} else
+		if (!strcmp(ce->name, "boot-delay"))
+		{
+			cfg.boot_delay = config_checkval(ce->value, CFG_TIME);
+		} else
+		if (!strcmp(ce->name, "split-delay"))
 		{
-			if (MyUser(client))
-				sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'f', "time range should be 1-999");
-			goto invalidsyntax;
+			cfg.split_delay = config_checkval(ce->value, CFG_TIME);
+		} else
+		if (!strcmp(ce->name, "profile"))
+		{
+			for (cep = ce->items; cep; cep = cep->next)
+			{
+				if (!strcmp(cep->name, "flood-mode"))
+					set_channel_flood_profile(ce->value, cep->value);
+			}
 		}
-		newf.per = v;
+	}
+	return 2;
+}
 
-		/* Is anything turned on? (to stop things like '+f []:15' */
-		breakit = 1;
-		for (v=0; v < NUMFLD; v++)
-			if (newf.limit[v])
-				breakit=0;
-		if (breakit)
-			goto invalidsyntax;
+FloodType *find_floodprot_by_letter(char c)
+{
+	int i;
+	for (i=0; i < ARRAY_SIZEOF(floodtypes); i++)
+		if (floodtypes[i].letter == c)
+			return &floodtypes[i];
 
-		return EX_ALLOW;
-invalidsyntax:
-		sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'f', "Invalid syntax for MODE +f");
-		return EX_DENY;
+	return NULL;
+}
+
+FloodType *find_floodprot_by_index(Flood index)
+{
+	int i;
+	for (i=0; i < ARRAY_SIZEOF(floodtypes); i++)
+		if (floodtypes[i].index == index)
+			return &floodtypes[i];
+
+	return NULL;
+}
+
+ChannelFloodProtection *get_channel_flood_profile(const char *name)
+{
+	ChannelFloodProfile *f;
+
+	for (f = channel_flood_profiles; f; f = f->next)
+		if (!strcasecmp(f->settings.profile, name))
+			return &f->settings;
+
+	return NULL;
+}
+
+/** Helper function for parse_channel_mode_flood() */
+int parse_channel_mode_flood_failed(const char **error_out, ChannelFloodProtection *fld, const char *fmt, ...)
+{
+	static char retbuf[512];
+	int v;
+
+	va_list vl;
+	va_start(vl, fmt);
+	vsnprintf(retbuf, sizeof(retbuf), fmt, vl);
+	va_end(vl);
+
+	/* Zero out all settings */
+	for (v=0; v < NUMFLD; v++)
+	{
+		fld->limit[v] = 0;
+		fld->action[v] = 0;
+		fld->remove_after[v] = 0;
 	}
 
-	/* fallthrough -- should not be used */
-	return EX_DENY;
+	if (error_out)
+		*error_out = retbuf;
+
+	return 0;
 }
 
-void *cmodef_put_param(void *fld_in, const char *param)
+int floodprot_valid_alternate_action(char action, FloodType *floodtype)
 {
-	ChannelFloodProtection *fld = (ChannelFloodProtection *)fld_in;
+	Cmode *cm;
+
+	/* Built-in actions */
+	if (strchr(floodtype->actions, action))
+		return 1;
+
+	cm = find_channel_mode_handler(action);
+	if (cm && cm->flood_type_action == floodtype->letter)
+		return 1;
+
+	return 0;
+}
+
+/** Parse channel mode +f string.
+ * @param param		The parameter string to parse
+ * @param fld		The setting struct to fill, this MAY already contain data.
+ * @param strict	If set to 1 then reject invalid setting, used for ex .is_ok().
+ *			If set to 0 then do your best to make something out of it,
+ *			and skip invalid stuff for forward-compatibility, eg for .put_param().
+ * @param client	The client requesting the mode change, can be NULL
+ *			(used for local vs remote things, not for sending errors)
+ * @param error		Used for returning the error or warning string, can be NULL.
+ * @retval 1 On success, although there could still be a warning stored in *error_out.
+ * @retval 0 On failure, the error will be in *error_out
+ */
+int parse_channel_mode_flood(const char *param, ChannelFloodProtection *fld, int strict, Client *client, const char **error_out)
+{
+	static char retbuf[512];
 	char xbuf[256], c, a, *p, *p2, *x = xbuf+1;
 	int v;
 	unsigned short breakit;
 	unsigned char r;
 	FloodType *floodtype;
 	Flood index;
+	char localclient = (client && MyUser(client)) ? 1 : 0;
+	char warn_unknown_flood_type[32];
 
-	strlcpy(xbuf, param, sizeof(xbuf));
-
-	if (!fld)
-		fld = safe_alloc(sizeof(ChannelFloodProtection));
+	*warn_unknown_flood_type = '\0';
+	if (error_out)
+		*error_out = NULL;
 
 	/* always reset settings (l, a, r) */
 	for (v=0; v < NUMFLD; v++)
@@ -491,13 +708,18 @@ void *cmodef_put_param(void *fld_in, const char *param)
 		fld->remove_after[v] = 0;
 	}
 
+	strlcpy(xbuf, param, sizeof(xbuf));
+
+	if (*xbuf != '[')
+		return parse_channel_mode_flood_failed(error_out, fld, "Invalid format (brackets missing)");
+
 	/* '['<number><1 letter>[optional: '#'+1 letter],[next..]']'':'<number> */
 	p2 = strchr(xbuf+1, ']');
 	if (!p2)
-		goto fail_cmodef_put_param; /* FAIL */
+		return parse_channel_mode_flood_failed(error_out, fld, "Invalid format (brackets missing)");
 	*p2 = '\0';
 	if (*(p2+1) != ':')
-		goto fail_cmodef_put_param; /* FAIL */
+		return parse_channel_mode_flood_failed(error_out, fld, "Invalid format (:XX period missing)");
 
 	breakit = 0;
 	for (x = strtok(xbuf+1, ","); x; x = strtok(NULL, ","))
@@ -505,17 +727,33 @@ void *cmodef_put_param(void *fld_in, const char *param)
 		/* <number><1 letter>[optional: '#'+1 letter] */
 		p = x;
 		while(isdigit(*p)) { p++; }
+
+		/* letter */
 		c = *p;
 		floodtype = find_floodprot_by_letter(c);
 		if (!floodtype)
+		{
+			strlcat_letter(warn_unknown_flood_type, c, sizeof(warn_unknown_flood_type));
 			continue; /* continue instead of break for forward compatability. */
+		}
 		*p = '\0';
+
+		/* floodcount (number) */
 		v = atoi(x);
+		if (strict)
+		{
+			if ((v < 1) || (v > 999))
+				return parse_channel_mode_flood_failed(error_out, fld, "Flood count for '%c' must be 1-999 (got %d)", c, v);
+		}
 		if (v < 1)
 			v = 1;
+		if (v > 999)
+			v = 999;
 		p++;
 		a = '\0';
-		r = 0;
+
+		/* Removal */
+		r = localclient ? MODEF_DEFAULT_UNSETTIME : 0;
 		if (*p != '\0')
 		{
 			if (*p == '#')
@@ -529,6 +767,10 @@ void *cmodef_put_param(void *fld_in, const char *param)
 					tv = atoi(p);
 					if (tv <= 0)
 						tv = 0; /* (ignored) */
+					if (tv > 255)
+						tv = 255; /* always max limit, as it is a char */
+					if (strict && localclient && (tv > MODEF_MAX_UNSETTIME))
+						tv = MODEF_MAX_UNSETTIME;
 					r = (unsigned char)tv;
 				}
 			}
@@ -536,7 +778,7 @@ void *cmodef_put_param(void *fld_in, const char *param)
 
 		index = floodtype->index;
 		fld->limit[index] = v;
-		if (a && strchr(floodtype->actions, a))
+		if (a && floodprot_valid_alternate_action(a, floodtype))
 			fld->action[index] = a;
 		else
 			fld->action[index] = floodtype->default_action;
@@ -547,10 +789,10 @@ void *cmodef_put_param(void *fld_in, const char *param)
 	/* parse 'per' */
 	p2++;
 	if (*p2 != ':')
-		goto fail_cmodef_put_param; /* FAIL */
+		return parse_channel_mode_flood_failed(error_out, fld, "Invalid format (:XX period missing)");
 	p2++;
 	if (!*p2)
-		goto fail_cmodef_put_param; /* FAIL */
+		return parse_channel_mode_flood_failed(error_out, fld, "Invalid format (:XX period missing)");
 	v = atoi(p2);
 	if (v < 1)
 		v = 1;
@@ -563,26 +805,272 @@ void *cmodef_put_param(void *fld_in, const char *param)
 		{
 			fld->timer[i] = 0;
 			fld->counter[i] = 0;
+			fld->counter_unknown_users[i] = 0;
 		}
 	}
 	fld->per = v;
 
-	/* Is anything turned on? (to stop things like '+f []:15' */
-	breakit = 1;
-	for (v=0; v < NUMFLD; v++)
-		if (fld->limit[v])
-			breakit=0;
-	if (breakit)
-		goto fail_cmodef_put_param; /* FAIL */
+	/* Is anything turned on? (to stop things like '+f []:15' */
+	breakit = 1;
+	for (v=0; v < NUMFLD; v++)
+		if (fld->limit[v])
+			breakit=0;
+	if (breakit)
+	{
+		/* Nothing is turned on.. */
+		if (*warn_unknown_flood_type)
+			return parse_channel_mode_flood_failed(error_out, fld, "Unknown flood type(s) '%s'", warn_unknown_flood_type);
+		return parse_channel_mode_flood_failed(error_out, fld, "None of the floodtypes set");
+	}
+
+	/* Finally, this is a warning only */
+	if (*warn_unknown_flood_type && error_out)
+	{
+		snprintf(retbuf, sizeof(retbuf), "Unknown flood type(s) '%s'", warn_unknown_flood_type);
+		*error_out = retbuf;
+	}
+
+	return 1;
+}
+
+int cmodef_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		if (IsUser(client) && check_channel_access(client, channel, "oaq"))
+			return EX_ALLOW;
+		if (type == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
+			sendnumeric(client, ERR_NOTFORHALFOPS, 'f');
+		return EX_DENY;
+	} else
+	if (type == EXCHK_PARAM)
+	{
+		ChannelFloodProtection fld;
+		const char *err;
+
+		memset(&fld, 0, sizeof(fld));
+		if (!parse_channel_mode_flood(param, &fld, 1, client, &err))
+		{
+			sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'f', err);
+			return EX_DENY;
+		} else if (err)
+		{
+			sendnotice(client, "WARNING: Channel mode +f: %s", err);
+			/* fallthrough */
+		}
+		return EX_ALLOW;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
+
+void *cmodef_put_param(void *fld_in, const char *param)
+{
+	ChannelFloodProtection *fld = (ChannelFloodProtection *)fld_in;
+	int v;
+
+	if (!fld)
+		fld = safe_alloc(sizeof(ChannelFloodProtection));
+
+	parse_channel_mode_flood(param, fld, 0, NULL, NULL);
+
+	return fld;
+}
+
+const char *cmodef_get_param(void *r_in)
+{
+	ChannelFloodProtection *r = (ChannelFloodProtection *)r_in;
+	static char retbuf[512];
+
+	if (!r)
+		return NULL;
+
+	channel_modef_string(r, retbuf);
+	return retbuf;
+}
+
+/** Convert parameter to something proper.
+ * NOTE: client may be NULL if called for e.g. set::modes-on-join
+ */
+const char *cmodef_conv_param(const char *param_in, Client *client, Channel *channel)
+{
+	static char retbuf[256];
+	ChannelFloodProtection fld;
+	const char *err;
+
+	memset(&fld, 0, sizeof(fld));
+	if (!parse_channel_mode_flood(param_in, &fld, 0, client, &err))
+		return NULL;
+
+	*retbuf = '\0';
+	channel_modef_string(&fld, retbuf);
+	return retbuf;
+}
+
+int cmodef_free_param(void *r, int soft)
+{
+	ChannelFloodProtection *fld = (ChannelFloodProtection *)r;
+
+	if (!fld)
+		return 0;
+
+	if (soft && fld->profile && cfg.default_profile)
+	{
+		/* Resist freeing */
+		if (strcmp(fld->profile, cfg.default_profile))
+		{
+			/* But reset */
+			ChannelFloodProtection *base = get_channel_flood_profile(cfg.default_profile);
+			if (!base)
+				base = get_channel_flood_profile("normal"); /* fallback, always exists */
+			inherit_settings(base, fld);
+			safe_strdup(fld->profile, base->profile);
+		}
+		return 1; /* NO FREE */
+	} else
+	{
+		// TODO: consider cancelling timers just to be sure? or maybe in DEBUGMODE?
+		safe_free(fld->profile);
+		safe_free(r);
+	}
+	return 0;
+}
+
+void *cmodef_dup_struct(void *r_in)
+{
+	ChannelFloodProtection *r = (ChannelFloodProtection *)r_in;
+	ChannelFloodProtection *w = safe_alloc(sizeof(ChannelFloodProtection));
+
+	/* We can copy most members in a lazy way... */
+	memcpy(w, r, sizeof(ChannelFloodProtection));
+	/* ... except this one.
+	 * NOTE: can't use safe_strdup() here because
+	 * w->profile = r->profile at this point due
+	 * to the memcpy and safe_strdup() would free
+	 * it (and thus both).
+	 */
+	if (r->profile)
+		w->profile = raw_strdup(r->profile);
+	return (void *)w;
+}
+
+int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx)
+{
+	ChannelFloodProtection *our = (ChannelFloodProtection *)ourx;
+	ChannelFloodProtection *their = (ChannelFloodProtection *)theirx;
+	int i;
+
+	if (compare_floodprot_modes(our, their) == 0)
+		return EXSJ_SAME;
+
+	our->per = MAX(our->per, their->per);
+	for (i=0; i < NUMFLD; i++)
+	{
+		our->limit[i] = MAX(our->limit[i], their->limit[i]);
+		our->action[i] = MAX(our->action[i], their->action[i]);
+		our->remove_after[i] = MAX(our->remove_after[i], their->remove_after[i]);
+	}
+
+	return EXSJ_MERGE;
+}
+
+void floodprot_show_profiles(Client *client)
+{
+	ChannelFloodProfile *fld;
+	char buf[512];
+	int padding;
+	int max_length = 0;
+
+	sendnotice(client, "List of available flood profiles for +F:");
+	for (fld = channel_flood_profiles; fld; fld = fld->next)
+	{
+		int n = strlen(fld->settings.profile);
+		if (n > max_length)
+			max_length = n;
+	}
+
+	for (fld = channel_flood_profiles; fld; fld = fld->next)
+	{
+		*buf = '\0';
+		channel_modef_string(&fld->settings, buf);
+		padding = max_length - strlen(fld->settings.profile);
+		sendnotice(client, " %*s%s: %s",
+		           padding, "",
+		           fld->settings.profile,
+		           buf);
+	}
+	sendnotice(client, "See also https://www.unrealircd.org/docs/Channel_anti-flood_settings");
+}
+
+int cmodef_profile_is_ok(Client *client, Channel *channel, char mode, const char *param, int type, int what)
+{
+	if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR))
+	{
+		if (IsUser(client) && check_channel_access(client, channel, "oaq"))
+			return EX_ALLOW;
+		if (type == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
+			sendnumeric(client, ERR_NOTFORHALFOPS, 'f');
+		return EX_DENY;
+	} else
+	if (type == EXCHK_PARAM)
+	{
+		if (get_channel_flood_profile(param))
+			return EX_ALLOW;
+		sendnumeric(client, ERR_CANNOTCHANGECHANMODE, 'F', "Invalid flood profile specified for +F");
+		floodprot_show_profiles(client);
+		return EX_DENY;
+	}
+
+	/* fallthrough -- should not be used */
+	return EX_DENY;
+}
+
+void inherit_settings(ChannelFloodProtection *from, ChannelFloodProtection *to)
+{
+	int i;
+
+	/* If new 'per xxx seconds' is smaller than current 'per' then reset timers/counters (t, c) */
+	if (from->per < to->per)
+	{
+		for (i=0; i < NUMFLD; i++)
+		{
+			to->timer[i] = 0;
+			to->counter[i] = 0;
+			to->counter_unknown_users[i] = 0;
+		}
+	}
+
+	/* inherit settings (limit/action/remove_after) */
+	for (i=0; i < NUMFLD; i++)
+	{
+		to->limit[i] = from->limit[i];
+		to->action[i] = from->action[i];
+		to->remove_after[i] = from->remove_after[i];
+	}
+	to->per = from->per;
+}
+
+void *cmodef_profile_put_param(void *fld_in, const char *param)
+{
+	ChannelFloodProtection *fld = (ChannelFloodProtection *)fld_in;
+	ChannelFloodProtection *base;
+	int v;
 
-	return (void *)fld;
+	if (!fld)
+		fld = safe_alloc(sizeof(ChannelFloodProtection));
 
-fail_cmodef_put_param:
-	memset(fld, 0, sizeof(ChannelFloodProtection));
-	return fld; /* FAIL */
+	base = get_channel_flood_profile(param);
+	if (!base)
+		base = get_channel_flood_profile("normal"); /* fallback, always exists */
+
+	safe_strdup(fld->profile, param);
+	inherit_settings(base, fld);
+
+	return (void *)fld;
 }
 
-const char *cmodef_get_param(void *r_in)
+const char *cmodef_profile_get_param(void *r_in)
 {
 	ChannelFloodProtection *r = (ChannelFloodProtection *)r_in;
 	static char retbuf[512];
@@ -590,151 +1078,88 @@ const char *cmodef_get_param(void *r_in)
 	if (!r)
 		return NULL;
 
-	channel_modef_string(r, retbuf);
+	strlcpy(retbuf, r->profile ? r->profile : "???", sizeof(retbuf));
 	return retbuf;
 }
 
 /** Convert parameter to something proper.
  * NOTE: client may be NULL if called for e.g. set::modes-on-join
  */
-const char *cmodef_conv_param(const char *param_in, Client *client, Channel *channel)
+const char *cmodef_profile_conv_param(const char *param_in, Client *client, Channel *channel)
 {
 	static char retbuf[256];
-	char param[256];
-	ChannelFloodProtection newf;
-	int localclient = (!client || MyUser(client)) ? 1 : 0;
-	char xbuf[256], c, a, *p, *p2, *x = xbuf+1;
-	int v;
-	unsigned short breakit;
-	unsigned char r;
-	FloodType *floodtype;
-	Flood index;
-
-	memset(&newf, 0, sizeof(newf));
-
-	strlcpy(param, param_in, sizeof(param));
-
-	/* old +f was like +f 10:5 or +f *10:5 - no longer supported */
-	if (param[0] != '[')
-		return NULL;
-
-	/* '['<number><1 letter>[optional: '#'+1 letter],[next..]']'':'<number> */
-	strlcpy(xbuf, param, sizeof(xbuf));
-	p2 = strchr(xbuf+1, ']');
-	if (!p2)
-		return NULL;
-	*p2 = '\0';
-	if (*(p2+1) != ':')
-		return NULL;
-	breakit = 0;
-	for (x = strtok(xbuf+1, ","); x; x = strtok(NULL, ","))
-	{
-		/* <number><1 letter>[optional: '#'+1 letter] */
-		p = x;
-		while(isdigit(*p)) { p++; }
-		c = *p;
-		floodtype = find_floodprot_by_letter(c);
-		if (!floodtype)
-			continue; /* continue instead of break for forward compatability. */
-		*p = '\0';
-		v = atoi(x);
-		if ((v < 1) || (v > 999)) /* out of range... */
-		{
-			if (localclient || (v < 1))
-				return NULL;
-		}
-		p++;
-		a = '\0';
-		r = localclient ? MODEF_DEFAULT_UNSETTIME : 0;
-		if (*p != '\0')
-		{
-			if (*p == '#')
-			{
-				p++;
-				a = *p;
-				p++;
-				if (*p != '\0')
-				{
-					int tv;
-					tv = atoi(p);
-					if (tv <= 0)
-						tv = 0; /* (ignored) */
-					if (tv > (localclient ? MODEF_MAX_UNSETTIME : 255))
-						tv = (localclient ? MODEF_MAX_UNSETTIME : 255); /* set to max */
-					r = (unsigned char)tv;
-				}
-			}
-		}
+	ChannelFloodProtection *fld;
 
-		index = floodtype->index;
-		newf.limit[index] = v;
-		if (a && strchr(floodtype->actions, a))
-			newf.action[index] = a;
-		else
-			newf.action[index] = floodtype->default_action;
-		if (!floodtype->timedban_required || (floodtype->timedban_required && timedban_available))
-			newf.remove_after[index] = r;
-	} /* for */
-	/* parse 'per' */
-	p2++;
-	if (*p2 != ':')
-		return NULL;
-	p2++;
-	if (!*p2)
+	fld = get_channel_flood_profile(param_in);
+	if (!fld)
 		return NULL;
-	v = atoi(p2);
-	if ((v < 1) || (v > 999)) /* 'per' out of range */
-	{
-		if (localclient || (v < 1))
-			return NULL;
-	}
-	newf.per = v;
 
-	/* Is anything turned on? (to stop things like '+f []:15' */
-	breakit = 1;
-	for (v=0; v < NUMFLD; v++)
-		if (newf.limit[v])
-			breakit=0;
-	if (breakit)
-		return NULL;
+	strlcpy(retbuf, fld->profile, sizeof(retbuf));
 
-	channel_modef_string(&newf, retbuf);
 	return retbuf;
 }
 
-void cmodef_free_param(void *r)
+int cmodef_profile_sjoin_check(Channel *channel, void *ourx, void *theirx)
 {
-	// TODO: consider cancelling timers just to e sure? or maybe in DEBUGMODE?
-	safe_free(r);
-}
+	ChannelFloodProtection *our = (ChannelFloodProtection *)ourx;
+	ChannelFloodProtection *their = (ChannelFloodProtection *)theirx;
+	int i;
 
-void *cmodef_dup_struct(void *r_in)
-{
-	ChannelFloodProtection *r = (ChannelFloodProtection *)r_in;
-	ChannelFloodProtection *w = safe_alloc(sizeof(ChannelFloodProtection));
+	if (!strcmp(our->profile, their->profile))
+		return EXSJ_SAME;
 
-	memcpy(w, r, sizeof(ChannelFloodProtection));
-	return (void *)w;
+	if (strcmp(our->profile, their->profile) < 0)
+		return EXSJ_THEYWON;
+
+	return EXSJ_WEWON;
 }
 
-int cmodef_sjoin_check(Channel *channel, void *ourx, void *theirx)
+int is_floodprot_exempt(Client *client, Channel *channel, char flood_type_letter)
 {
-	ChannelFloodProtection *our = (ChannelFloodProtection *)ourx;
-	ChannelFloodProtection *their = (ChannelFloodProtection *)theirx;
-	int i;
+	Ban *ban;
+	char *p;
+	BanContext *b = safe_alloc(sizeof(BanContext));
 
-	if (compare_floodprot_modes(our, their) == 0)
-		return EXSJ_SAME;
+	b->client = client;
+	b->channel = channel;
+	b->ban_check_types = BANCHK_MSG;
 
-	our->per = MAX(our->per, their->per);
-	for (i=0; i < NUMFLD; i++)
+	for (ban = channel->exlist; ban; ban=ban->next)
 	{
-		our->limit[i] = MAX(our->limit[i], their->limit[i]);
-		our->action[i] = MAX(our->action[i], their->action[i]);
-		our->remove_after[i] = MAX(our->remove_after[i], their->remove_after[i]);
+		char *p, *x;
+		char *matchby;
+		char type[16];
+
+		if (!strncmp(ban->banstr, "~F:", 3))
+			p = ban->banstr + 3;
+		else if (!strncmp(ban->banstr, "~flood:", 7))
+			p = ban->banstr + 7;
+		else
+			continue;
+
+		strlcpy(type, p, sizeof(type));
+		x = strchr(type, ':');
+		if (x)
+			*x = '\0';
+
+		if (!strcmp(type, "*") || strchr(type, flood_type_letter))
+		{
+			matchby = strchr(p, ':');
+			if (!matchby)
+				continue;
+			matchby++;
+
+			b->banstr = matchby;
+			if (ban_check_mask(b))
+			{
+				safe_free(b);
+				return HOOK_ALLOW; /* Yes, user is exempt */
+			}
+		}
 	}
 
-	return EXSJ_MERGE;
+	safe_free(b);
+	return HOOK_CONTINUE; /* No, may NOT bypass. */
 }
 
 int floodprot_join(Client *client, Channel *channel, MessageTag *mtags)
@@ -744,6 +1169,7 @@ int floodprot_join(Client *client, Channel *channel, MessageTag *mtags)
 	 * 2. local client OR synced server
 	 * 3. server uptime more than XX seconds (if this information is available)
 	 * 4. is not a uline
+	 * call do_floodprot, which will:
 	 * 5. then, increase floodcounter
 	 * 6. if we reached the limit AND only if source was a local client.. do the action (+i).
 	 * Nr 6 is done because otherwise you would have a noticeflood with 'joinflood detected'
@@ -751,7 +1177,8 @@ int floodprot_join(Client *client, Channel *channel, MessageTag *mtags)
 	 */
 	if (IsFloodLimit(channel) &&
 	    (MyUser(client) || client->uplink->server->flags.synced) &&
-	    (client->uplink->server->boottime && (TStime() - client->uplink->server->boottime >= MODEF_BOOT_DELAY)) &&
+	    (client->uplink->server->boottime && (TStime() - client->uplink->server->boottime >= cfg.boot_delay)) &&
+	    (TStime() - floodprot_splittime >= cfg.split_delay) &&
 	    !IsULine(client))
 	{
 	    do_floodprot(channel, client, CHFLD_JOIN);
@@ -764,6 +1191,26 @@ int cmodef_cleanup_user2(Client *client)
 	return 0;
 }
 
+/** Install a default +F profile, if set::anti-flood::channel::default-profile is set */
+int cmodef_channel_create(Channel *channel)
+{
+	ChannelFloodProtection *base;
+	ChannelFloodProtection *fld;
+
+	if (!cfg.default_profile)
+		return 0;
+
+	base = get_channel_flood_profile(cfg.default_profile);
+	if (!base)
+		base = get_channel_flood_profile("normal"); /* fallback, always exists */
+
+	GETPARASTRUCT(channel, 'F') = fld = safe_alloc(sizeof(ChannelFloodProtection));
+	inherit_settings(base, fld);
+	safe_strdup(fld->profile, base->profile);
+
+	return 0;
+}
+
 int cmodef_channel_destroy(Channel *channel, int *should_destroy)
 {
 	floodprottimer_stopchantimers(channel);
@@ -819,10 +1266,28 @@ char *channel_modef_string(ChannelFloodProtection *x, char *retbuf)
 	return retbuf;
 }
 
+ChannelFloodProtection *get_channel_flood_settings(Channel *channel, int what)
+{
+	ChannelFloodProtection *fld;
+
+	if (channel->mode.mode & EXTMODE_FLOODLIMIT)
+	{
+		fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+		if (fld->action[what])
+			return fld;
+	}
+
+	fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'F');
+	if (fld && fld->action[what])
+		return fld;
+
+	return NULL;
+}
+
 int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
 {
 	Membership *mb;
-	ChannelFloodProtection *chp;
+	ChannelFloodProtection *fld;
 	MemberFlood *memberflood;
 	uint64_t msghash;
 	unsigned char is_flooding_text=0, is_flooding_repeat=0;
@@ -841,9 +1306,12 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	if (!(mb = find_membership_link(client->user->channel, channel)))
 		return HOOK_CONTINUE; /* not in channel */
 
-	chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+	/* config test rejects having 't' in +F and 'r' in +f or vice versa,
+	 * otherwise we would be screwed here :D.
+	 */
+	fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
 
-	if (!chp || !(chp->limit[CHFLD_TEXT] || chp->limit[CHFLD_REPEAT]))
+	if (!fld || !(fld->limit[CHFLD_TEXT] || fld->limit[CHFLD_REPEAT]))
 		return HOOK_CONTINUE;
 
 	if (moddata_membership(mb, mdflood).ptr == NULL)
@@ -854,13 +1322,13 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 
 	memberflood = (MemberFlood *)moddata_membership(mb, mdflood).ptr;
 
-	if ((TStime() - memberflood->firstmsg) >= chp->per)
+	if ((TStime() - memberflood->firstmsg) >= fld->per)
 	{
 		/* Reset due to moving into a new time slot */
 		memberflood->firstmsg = TStime();
 		memberflood->nmsg = 1;
 		memberflood->nmsg_repeat = 1;
-		if (chp->limit[CHFLD_REPEAT])
+		if (fld->limit[CHFLD_REPEAT])
 		{
 			memberflood->lastmsg = gen_floodprot_msghash(*msg);
 			memberflood->prevmsg = 0;
@@ -869,7 +1337,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 	}
 
 	/* Anti-repeat ('r') */
-	if (chp->limit[CHFLD_REPEAT])
+	if (fld->limit[CHFLD_REPEAT])
 	{
 		msghash = gen_floodprot_msghash(*msg);
 		if (memberflood->lastmsg)
@@ -877,7 +1345,7 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 			if ((memberflood->lastmsg == msghash) || (memberflood->prevmsg == msghash))
 			{
 				memberflood->nmsg_repeat++;
-				if (memberflood->nmsg_repeat > chp->limit[CHFLD_REPEAT])
+				if (memberflood->nmsg_repeat > fld->limit[CHFLD_REPEAT])
 					is_flooding_repeat = 1;
 			}
 			memberflood->prevmsg = memberflood->lastmsg;
@@ -885,11 +1353,11 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 		memberflood->lastmsg = msghash;
 	}
 
-	if (chp->limit[CHFLD_TEXT])
+	if (fld->limit[CHFLD_TEXT])
 	{
 		/* increase msgs */
 		memberflood->nmsg++;
-		if (memberflood->nmsg > chp->limit[CHFLD_TEXT])
+		if (memberflood->nmsg > fld->limit[CHFLD_TEXT])
 			is_flooding_text = 1;
 	}
 
@@ -900,6 +1368,15 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 		MessageTag *mtags;
 		int flood_type;
 
+		if ((is_flooding_text && is_floodprot_exempt(client, channel, 't')) ||
+		    (is_flooding_repeat && is_floodprot_exempt(client, channel, 'r')))
+		{
+			/* No action taken */
+			memberflood->nmsg = 0;
+			memberflood->nmsg_repeat = 0;
+			return HOOK_CONTINUE;
+		}
+
 		/* Repeat takes precedence over text flood */
 		if (is_flooding_repeat)
 		{
@@ -907,26 +1384,26 @@ int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *
 			flood_type = CHFLD_REPEAT;
 		} else
 		{
-			snprintf(errbuf, sizeof(errbuf), "Flooding (Limit is %i lines per %i seconds)", chp->limit[CHFLD_TEXT], chp->per);
+			snprintf(errbuf, sizeof(errbuf), "Flooding (Limit is %i lines per %i seconds)", fld->limit[CHFLD_TEXT], fld->per);
 			flood_type = CHFLD_TEXT;
 		}
 
-		if (chp->action[flood_type] == 'd')
+		if (fld->action[flood_type] == 'd')
 		{
 			/* Drop the message */
 			*errmsg = errbuf;
 			return HOOK_DENY;
 		}
 
-		if (chp->action[flood_type] == 'b')
+		if (fld->action[flood_type] == 'b')
 		{
 			/* Ban the user */
-			if (timedban_available && (chp->remove_after[flood_type] > 0))
+			if (timedban_available && (fld->remove_after[flood_type] > 0))
 			{
 				if (iConf.named_extended_bans)
-					snprintf(mask, sizeof(mask), "~time:%d:*!*@%s", chp->remove_after[flood_type], GetHost(client));
+					snprintf(mask, sizeof(mask), "~time:%d:*!*@%s", fld->remove_after[flood_type], GetHost(client));
 				else
-					snprintf(mask, sizeof(mask), "~t:%d:*!*@%s", chp->remove_after[flood_type], GetHost(client));
+					snprintf(mask, sizeof(mask), "~t:%d:*!*@%s", fld->remove_after[flood_type], GetHost(client));
 			} else {
 				snprintf(mask, sizeof(mask), "*!*@%s", GetHost(client));
 			}
@@ -977,9 +1454,14 @@ int floodprot_nickchange(Client *client, MessageTag *mtags, const char *oldnick)
 {
 	Membership *mp;
 
+	/* Ignore u-lines, as usual */
 	if (IsULine(client))
 		return 0;
 
+	/* Don't count forced nick changes, eg from NickServ */
+	if (find_mtag(mtags, "unrealircd.org/issued-by"))
+		return 0;
+
 	for (mp = client->user->channel; mp; mp = mp->next)
 	{
 		Channel *channel = mp->channel;
@@ -991,47 +1473,69 @@ int floodprot_nickchange(Client *client, MessageTag *mtags, const char *oldnick)
 	return 0;
 }
 
-int floodprot_chanmode_del(Channel *channel, int modechar)
+void floodprot_chanmode_del_helper(ChannelFloodProtection *fld, char modechar)
 {
-	ChannelFloodProtection *chp;
-
-	if (!IsFloodLimit(channel))
-		return 0;
-
-	chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
-	if (!chp)
-		return 0;
-
 	/* reset joinflood on -i, reset msgflood on -m, etc.. */
 	switch(modechar)
 	{
 		case 'C':
-			chp->counter[CHFLD_CTCP] = 0;
+			fld->counter[CHFLD_CTCP] = 0;
+			fld->counter_unknown_users[CHFLD_CTCP] = 0;
 			break;
 		case 'N':
-			chp->counter[CHFLD_NICK] = 0;
+			fld->counter[CHFLD_NICK] = 0;
+			fld->counter_unknown_users[CHFLD_NICK] = 0;
 			break;
 		case 'm':
-			chp->counter[CHFLD_MSG] = 0;
-			chp->counter[CHFLD_CTCP] = 0;
+			fld->counter[CHFLD_MSG] = 0;
+			fld->counter[CHFLD_CTCP] = 0;
+			fld->counter_unknown_users[CHFLD_MSG] = 0;
+			fld->counter_unknown_users[CHFLD_CTCP] = 0;
 			break;
 		case 'K':
-			chp->counter[CHFLD_KNOCK] = 0;
+			fld->counter[CHFLD_KNOCK] = 0;
+			fld->counter_unknown_users[CHFLD_KNOCK] = 0;
 			break;
 		case 'i':
-			chp->counter[CHFLD_JOIN] = 0;
+			fld->counter[CHFLD_JOIN] = 0;
+			fld->counter_unknown_users[CHFLD_JOIN] = 0;
 			break;
 		case 'M':
-			chp->counter[CHFLD_MSG] = 0;
-			chp->counter[CHFLD_CTCP] = 0;
+			fld->counter[CHFLD_MSG] = 0;
+			fld->counter[CHFLD_CTCP] = 0;
+			fld->counter_unknown_users[CHFLD_MSG] = 0;
+			fld->counter_unknown_users[CHFLD_CTCP] = 0;
 			break;
 		case 'R':
-			chp->counter[CHFLD_JOIN] = 0;
+			fld->counter[CHFLD_JOIN] = 0;
+			fld->counter_unknown_users[CHFLD_JOIN] = 0;
 			break;
 		default:
 			break;
 	}
-	floodprottimer_del(channel, modechar);
+}
+
+int floodprot_chanmode_del(Channel *channel, int modechar)
+{
+	ChannelFloodProtection *fld;
+
+	if (!IsFloodLimit(channel))
+		return 0;
+
+	fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+	if (fld)
+	{
+		floodprot_chanmode_del_helper(fld, modechar);
+		floodprottimer_del(channel, fld, modechar);
+	}
+
+	fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'F');
+	if (fld)
+	{
+		floodprot_chanmode_del_helper(fld, modechar);
+		floodprottimer_del(channel, fld, modechar);
+	}
+
 	return 0;
 }
 
@@ -1067,13 +1571,12 @@ void strccat(char *s, char c)
  *   do not modify it yourself.
  * - channel->mode.floodprot is asumed to be non-NULL.
  */
-void floodprottimer_add(Channel *channel, char mflag, time_t when)
+void floodprottimer_add(Channel *channel, ChannelFloodProtection *fld, char mflag, time_t when)
 {
 	RemoveChannelModeTimer *e = NULL;
 	unsigned char add=1;
-	ChannelFloodProtection *chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
 
-	if (strchr(chp->timers_running, mflag))
+	if (strchr(fld->timers_running, mflag))
 	{
 		/* Already exists... */
 		e = floodprottimer_find(channel, mflag);
@@ -1081,17 +1584,17 @@ void floodprottimer_add(Channel *channel, char mflag, time_t when)
 			add = 0;
 	}
 
-	if (!strchr(chp->timers_running, mflag))
+	if (!strchr(fld->timers_running, mflag))
 	{
-		if (strlen(chp->timers_running)+1 >= sizeof(chp->timers_running))
+		if (strlen(fld->timers_running)+1 >= sizeof(fld->timers_running))
 		{
 			unreal_log(ULOG_WARNING, "flood", "BUG_FLOODPROTTIMER_ADD", NULL,
 			           "[BUG] floodprottimer_add: too many timers running for $channel ($timers_running)",
 			           log_data_channel("channel", channel),
-			           log_data_string("timers_running", chp->timers_running));
+			           log_data_string("timers_running", fld->timers_running));
 			return;
 		}
-		strccat(chp->timers_running, mflag); /* bounds already checked ^^ */
+		strccat(fld->timers_running, mflag); /* bounds already checked ^^ */
 	}
 
 	if (add)
@@ -1105,12 +1608,11 @@ void floodprottimer_add(Channel *channel, char mflag, time_t when)
 		AddListItem(e, removechannelmodetimer_list);
 }
 
-void floodprottimer_del(Channel *channel, char mflag)
+void floodprottimer_del(Channel *channel, ChannelFloodProtection *fld, char mflag)
 {
 	RemoveChannelModeTimer *e;
-	ChannelFloodProtection *chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
 
-	if (chp && !strchr(chp->timers_running, mflag))
+	if (fld && !strchr(fld->timers_running, mflag))
 		return; /* nothing to remove.. */
 	e = floodprottimer_find(channel, mflag);
 	if (!e)
@@ -1119,15 +1621,15 @@ void floodprottimer_del(Channel *channel, char mflag)
 	DelListItem(e, removechannelmodetimer_list);
 	safe_free(e);
 
-	if (chp)
+	if (fld)
         {
                 char newtf[MAXCHMODEFACTIONS+1];
                 char *i, *o;
-                for (i=chp->timers_running, o=newtf; *i; i++)
+                for (i=fld->timers_running, o=newtf; *i; i++)
                         if (*i != mflag)
                                 *o++ = *i;
                 *o = '\0';
-                strcpy(chp->timers_running, newtf); /* always shorter (or equal) */
+                strcpy(fld->timers_running, newtf); /* always shorter (or equal) */
         }
 }
 
@@ -1183,22 +1685,35 @@ void floodprottimer_stopchantimers(Channel *channel)
 
 int do_floodprot(Channel *channel, Client *client, int what)
 {
-	ChannelFloodProtection *chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+	ChannelFloodProtection *fld = get_channel_flood_settings(channel, what);
+	FloodType *floodtype = find_floodprot_by_index(what);
+	char unknown_user;
 
-	if (!chp)
+	if (!fld)
 		return 0; /* no +f active */
 
-	if (chp->limit[what])
+	if (floodtype && is_floodprot_exempt(client, channel, floodtype->letter))
+		return 0; /* exempt: not counted and no action taken */
+
+	unknown_user = user_allowed_by_security_group_name(client, "known-users") ? 0 : 1;
+
+	if (fld->limit[what])
 	{
-		if (TStime() - chp->timer[what] >= chp->per)
+		if (TStime() - fld->timer[what] >= fld->per)
 		{
-			chp->timer[what] = TStime();
-			chp->counter[what] = 1;
+			/* reset */
+			fld->timer[what] = TStime();
+			fld->counter[what] = 1;
+			fld->counter_unknown_users[what] = unknown_user;
 		} else
 		{
-			chp->counter[what]++;
-			if ((chp->counter[what] > chp->limit[what]) &&
-			    (TStime() - chp->timer[what] < chp->per))
+			fld->counter[what]++;
+
+			if (unknown_user)
+				fld->counter_unknown_users[what]++;
+
+			if ((fld->counter[what] > fld->limit[what]) &&
+			    (TStime() - fld->timer[what] < fld->per))
 			{
 				if (MyUser(client))
 					do_floodprot_action(channel, what);
@@ -1209,67 +1724,146 @@ int do_floodprot(Channel *channel, Client *client, int what)
 	return 0;
 }
 
+/** Helper for do_floodprot_action() - standard action +i/+R/etc.. */
+void do_floodprot_action_standard(Channel *channel, int what, FloodType *floodtype, Cmode_t extmode, char m)
+{
+	ChannelFloodProtection *fld = get_channel_flood_settings(channel, what);
+	char comment[512], target[CHANNELLEN + 8];
+	MessageTag *mtags;
+	const char *text = floodtype->description;
+
+	/* First the notice to the chanops */
+	mtags = NULL;
+	new_message(&me, NULL, &mtags);
+	ircsnprintf(comment, sizeof(comment), "*** Channel %s detected (limit is %d per %d seconds), setting mode +%c",
+		text, fld->limit[what], fld->per, m);
+	ircsnprintf(target, sizeof(target), "%%%s", channel->name);
+	sendto_channel(channel, &me, NULL, "ho",
+		       0, SEND_ALL, mtags,
+		       ":%s NOTICE %s :%s", me.name, target, comment);
+	free_message_tags(mtags);
+
+	/* Then the MODE broadcast */
+	mtags = NULL;
+	new_message(&me, NULL, &mtags);
+	sendto_server(NULL, 0, 0, mtags, ":%s MODE %s +%c 0", me.id, channel->name, m);
+	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +%c", me.name, channel->name, m);
+	free_message_tags(mtags);
+
+	/* Actually set the mode internally */
+	channel->mode.mode |= extmode;
+
+	/* Add remove-chanmode timer */
+	if (fld->remove_after[what])
+	{
+		floodprottimer_add(channel, fld, m, TStime() + ((long)fld->remove_after[what] * 60) - 5);
+		/* (since the floodprot timer event is called every 10s, we do -5 here so the accurancy will
+		 *  be -5..+5, without it it would be 0..+10.)
+		 */
+	}
+}
+
+/** Helper for do_floodprot_action() - alternative action like +b ~security-group:unknown-users */
+int do_floodprot_action_alternative(Channel *channel, int what, FloodType *floodtype)
+{
+	ChannelFloodProtection *fld = get_channel_flood_settings(channel, what);
+	char ban[512];
+	char comment[512], target[CHANNELLEN + 8];
+	MessageTag *mtags;
+	const char *text = floodtype->description;
+
+	snprintf(ban, sizeof(ban), "~time:%d:%s",
+	         fld->remove_after[what] ? fld->remove_after[what] : cfg.modef_alternative_ban_action_unsettime,
+	         floodtype->alternative_ban_action);
+
+	/* Add the ban internally */
+	if (add_listmode(&channel->banlist, &me, channel, ban) == -1)
+		return 0; /* ban list full (or ban already exists) */
+
+	/* First the notice to the chanops */
+	mtags = NULL;
+	new_message(&me, NULL, &mtags);
+	ircsnprintf(comment, sizeof(comment),
+	            "*** Channel %s detected (limit is %d per %d seconds), "
+	            "mostly caused by 'unknown-users', setting mode +b %s",
+		text, fld->limit[what], fld->per, ban);
+	ircsnprintf(target, sizeof(target), "%%%s", channel->name);
+	sendto_channel(channel, &me, NULL, "ho",
+		       0, SEND_ALL, mtags,
+		       ":%s NOTICE %s :%s", me.name, target, comment);
+	free_message_tags(mtags);
+
+	/* Then the MODE broadcast */
+	mtags = NULL;
+	new_message(&me, NULL, &mtags);
+	sendto_server(NULL, 0, 0, mtags, ":%s MODE %s +b %s 0", me.id, channel->name, ban);
+	sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +b %s", me.name, channel->name, ban);
+	free_message_tags(mtags);
+
+	return 1;
+}
+
+
 void do_floodprot_action(Channel *channel, int what)
 {
-	char m;
 	Cmode_t extmode = 0;
-	ChannelFloodProtection *chp = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+	ChannelFloodProtection *fld = get_channel_flood_settings(channel, what);
 	FloodType *floodtype = find_floodprot_by_index(what);
-	char *text;
-
-	if (!floodtype)
-		return;
-	text = floodtype->description;
+	char ban_exists;
+	double perc;
+	char m;
 
-	m = chp->action[what];
-	if (!m)
+	if (!fld || !floodtype)
 		return;
 
 	/* For drop action we don't actually have to do anything here, but we still have to prevent Unreal
 	 * from setting chmode +d (which is useless against floods anyways) =]
 	 */
-	if (chp->action[what] == 'd')
+	if (fld->action[what] == 'd')
+		return;
+
+	m = fld->action[what];
+	if (!m)
 		return;
 
 	extmode = get_extmode_bitbychar(m);
 	if (!extmode)
 		return;
 
-	if (!(extmode && (channel->mode.mode & extmode)))
-	{
-		char comment[512], target[CHANNELLEN + 8];
-		MessageTag *mtags;
-
-		/* First the notice to the chanops */
-		mtags = NULL;
-		new_message(&me, NULL, &mtags);
-		ircsnprintf(comment, sizeof(comment), "*** Channel %s detected (limit is %d per %d seconds), setting mode +%c",
-			text, chp->limit[what], chp->per, m);
-		ircsnprintf(target, sizeof(target), "%%%s", channel->name);
-		sendto_channel(channel, &me, NULL, "ho",
-		               0, SEND_ALL, mtags,
-		               ":%s NOTICE %s :%s", me.name, target, comment);
-		free_message_tags(mtags);
-
-		/* Then the MODE broadcast */
-		mtags = NULL;
-		new_message(&me, NULL, &mtags);
-		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s +%c 0", me.id, channel->name, m);
-		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s +%c", me.name, channel->name, m);
-		free_message_tags(mtags);
+	if (extmode && (channel->mode.mode & extmode))
+		return; /* channel mode is already set, so nothing to do */
 
-		/* Actually set the mode internally */
-		channel->mode.mode |= extmode;
+	/* Do we have other options, instead of setting the channel +i/etc ? */
+	if (floodtype->alternative_ban_action)
+	{
+		/* The 'ban_exists' assignments and checks below are carefully ordered and checked.
+		 * Don't try to "optimize" this code by rearranging or scratching certain checks
+		 * or assignments! -- Syzop
+		 */
+		ban_exists = ban_exists_ignore_time(channel->banlist, floodtype->alternative_ban_action);
 
-		/* Add remove-chanmode timer */
-		if (chp->remove_after[what])
+		if (!ban_exists)
 		{
-			floodprottimer_add(channel, m, TStime() + ((long)chp->remove_after[what] * 60) - 5);
-			/* (since the floodprot timer event is called every 10s, we do -5 here so the accurancy will
-			 *  be -5..+5, without it it would be 0..+10.)
-			 */
+			/* Calculate the percentage of unknown-users that is responsible for the action trigger */
+			perc = ((double)fld->counter_unknown_users[what] / (double)fld->counter[what])*100;
+			if (perc >= cfg.modef_alternate_action_percentage_threshold)
+			{
+				/* ACTION: We need to add the ban (+b) */
+				ban_exists = do_floodprot_action_alternative(channel, what, floodtype);
+			}
 		}
+
+		/* Now recheck, before we fallback to do_floodprot_action_standard() below,
+		 * taking into account that all unknown-users are banned now
+		 * or will be banned (eg: some actions still go through because of lag
+		 * between servers).
+		 */
+		if (ban_exists && (fld->counter[what] - fld->counter_unknown_users[what] <= fld->limit[what]))
+			return; /* flood limit not reached by known-users group */
 	}
+
+	/* ACTION: We need to set the channel mode */
+	do_floodprot_action_standard(channel, what, floodtype, extmode, m);
 }
 
 uint64_t gen_floodprot_msghash(const char *text)
@@ -1357,3 +1951,137 @@ void floodprot_free_msghash_key(ModData *m)
 {
 	safe_free(floodprot_msghash_key);
 }
+
+CMD_OVERRIDE_FUNC(floodprot_override_mode)
+{
+	if (MyUser(client) && (parc == 3) &&
+	    (parv[1][0] == '#') &&
+	    (!strcasecmp(parv[2], "f") || !strcasecmp(parv[2], "+f")))
+	{
+		/* Query (not set!) request for #channel */
+		Channel *channel = find_channel(parv[1]);
+		ChannelFloodProtection *profile, *advanced;
+		char buf[512];
+
+		if (!channel)
+		{
+			sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
+			return;
+		}
+
+		advanced = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f');
+		profile = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'F');
+		if (!advanced && !profile)
+		{
+			sendnotice(client, "No channel mode +f/+F is active on %s", channel->name);
+		} else
+		if (advanced && !profile)
+		{
+			channel_modef_string(advanced, buf);
+			sendnotice(client, "Channel '%s' has effective flood setting '%s' (custom settings via +f)",
+			           channel->name, buf);
+		} else
+		if (profile && !advanced)
+		{
+			channel_modef_string(profile, buf);
+			sendnotice(client, "Channel '%s' has effective flood setting '%s' (flood profile '%s')",
+			           channel->name, buf, profile->profile);
+		} else {
+			/* Both +f and +F are set */
+			int v;
+			ChannelFloodProtection mix;
+			FloodType *t;
+			char overridden[64];
+			*overridden = '\0';
+			memcpy(&mix, profile, sizeof(mix));
+			for (v=0; v < NUMFLD; v++)
+			{
+				if ((advanced->limit[v]>0) && (mix.limit[v]>0))
+				{
+					mix.limit[v] = 0;
+					mix.action[v] = 0;
+					t = find_floodprot_by_index(v);
+					if (t)
+						strlcat_letter(overridden, t->letter, sizeof(overridden));
+				}
+			}
+			channel_modef_string(&mix, buf);
+			if (*overridden)
+			{
+				sendnotice(client, "Channel '%s' uses flood profile '%s', without action(s) '%s' as they are overridden by +f.",
+					   channel->name, profile->profile, overridden);
+				sendnotice(client, "Effective flood setting via +F: '%s'", buf);
+			} else {
+				sendnotice(client, "Channel '%s' has effective flood setting '%s' (flood profile '%s')",
+					   channel->name, buf, profile->profile);
+			}
+			channel_modef_string(advanced, buf);
+			sendnotice(client, "Plus flood setting via +f: '%s'", buf);
+		}
+		sendnotice(client, "-");
+		floodprot_show_profiles(client);
+		return;
+	}
+
+	CALL_NEXT_COMMAND_OVERRIDE();
+}
+
+int floodprot_server_quit(Client *client, MessageTag *mtags)
+{
+	if (!IsULine(client))
+		floodprot_splittime = TStime();
+	return 0;
+}
+
+void reapply_profiles(void)
+{
+	Channel *channel;
+
+	for (channel = channels; channel; channel=channel->nextch)
+	{
+		ChannelFloodProtection *fld = GETPARASTRUCT(channel, 'F');
+		ChannelFloodProtection *base;
+
+		if (channel->mode.mode & EXTMODE_FLOOD_PROFILE)
+		{
+			/* Channel is +F: simply re-apply the setting the +F <profile> */
+			base = get_channel_flood_profile(fld->profile);
+			if (base)
+				inherit_settings(base, fld);
+		} else
+		{
+			/* Channel is -F */
+			if (cfg.default_profile)
+			{
+				/* We are -F and set::anti-flood::channel::default-profile
+				 * is configured, so apply it or re-apply it.
+				 */
+				if (!fld)
+				{
+					cmodef_channel_create(channel);
+				} else {
+					base = get_channel_flood_profile(cfg.default_profile);
+					if (base)
+					{
+						inherit_settings(base, fld);
+						safe_strdup(fld->profile, cfg.default_profile);
+					}
+				}
+			} else {
+				if (fld)
+				{
+					/* Not +F, previously we had a default profile
+					 * and now set::anti-flood::channel::default-profile
+					 * is unset, so remove the flood profile.
+					 */
+					cmodef_free_param(fld, 0);
+					GETPARASTRUCT(channel, 'F') = NULL;
+				}
+			}
+		}
+	}
+}
+
+// TODO: handle mismatch of flood profiles between servers
+
+// TODO: perhaps an option to turn off +F in the IRCd without turning off +f ? maybe as a multi-option.
diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c
@@ -50,7 +50,7 @@ int history_chanmode_is_ok(Client *client, Channel *channel, char mode, const ch
 void *history_chanmode_put_param(void *r_in, const char *param);
 const char *history_chanmode_get_param(void *r_in);
 const char *history_chanmode_conv_param(const char *param, Client *client, Channel *channel);
-void history_chanmode_free_param(void *r);
+int history_chanmode_free_param(void *r, int soft);
 void *history_chanmode_dup_struct(void *r_in);
 int history_chanmode_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int history_channel_destroy(Channel *channel, int *should_destroy);
@@ -576,9 +576,10 @@ const char *history_chanmode_get_param(void *h_in)
 }
 
 /** Free channel mode */
-void history_chanmode_free_param(void *r)
+int history_chanmode_free_param(void *r, int soft)
 {
 	safe_free(r);
+	return 0;
 }
 
 /** Duplicate the channel mode +H settings */
@@ -731,7 +732,7 @@ CMD_OVERRIDE_FUNC(override_mode)
 			had_r = 1;
 		}
 	}
-	CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+	CALL_NEXT_COMMAND_OVERRIDE();
 
 	/* If..
 	 * - channel was +r
diff --git a/src/modules/chanmodes/key.c b/src/modules/chanmodes/key.c
@@ -45,7 +45,7 @@ int cmode_key_is_ok(Client *client, Channel *channel, char mode, const char *par
 void *cmode_key_put_param(void *r_in, const char *param);
 const char *cmode_key_get_param(void *r_in);
 const char *cmode_key_conv_param(const char *param_in, Client *client, Channel *channel);
-void cmode_key_free_param(void *r);
+int cmode_key_free_param(void *r, int soft);
 void *cmode_key_dup_struct(void *r_in);
 int cmode_key_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int is_valid_key(const char *key);
@@ -162,9 +162,10 @@ const char *cmode_key_conv_param(const char *param, Client *client, Channel *cha
 	return retbuf;
 }
 
-void cmode_key_free_param(void *r)
+int cmode_key_free_param(void *r, int soft)
 {
 	safe_free(r);
+	return 0;
 }
 
 void *cmode_key_dup_struct(void *r_in)
diff --git a/src/modules/chanmodes/limit.c b/src/modules/chanmodes/limit.c
@@ -48,7 +48,7 @@ int cmode_limit_is_ok(Client *client, Channel *channel, char mode, const char *p
 void *cmode_limit_put_param(void *r_in, const char *param);
 const char *cmode_limit_get_param(void *r_in);
 const char *cmode_limit_conv_param(const char *param_in, Client *client, Channel *channel);
-void cmode_limit_free_param(void *r);
+int cmode_limit_free_param(void *r, int soft);
 void *cmode_limit_dup_struct(void *r_in);
 int cmode_limit_sjoin_check(Channel *channel, void *ourx, void *theirx);
 int transform_channel_limit(const char *param);
@@ -159,9 +159,10 @@ const char *cmode_limit_conv_param(const char *param, Client *client, Channel *c
 	return retbuf;
 }
 
-void cmode_limit_free_param(void *r)
+int cmode_limit_free_param(void *r, int soft)
 {
 	safe_free(r);
+	return 0;
 }
 
 void *cmode_limit_dup_struct(void *r_in)
diff --git a/src/modules/chanmodes/link.c b/src/modules/chanmodes/link.c
@@ -49,7 +49,7 @@ int cmodeL_is_ok(Client *client, Channel *channel, char mode, const char *para, 
 void *cmodeL_put_param(void *r_in, const char *param);
 const char *cmodeL_get_param(void *r_in);
 const char *cmodeL_conv_param(const char *param_in, Client *client, Channel *channel);
-void cmodeL_free_param(void *r);
+int cmodeL_free_param(void *r, int soft);
 void *cmodeL_dup_struct(void *r_in);
 int cmodeL_sjoin_check(Channel *channel, void *ourx, void *theirx);
 
@@ -178,9 +178,10 @@ const char *cmodeL_conv_param(const char *param, Client *client, Channel *channe
 	return param;
 }
 
-void cmodeL_free_param(void *r)
+int cmodeL_free_param(void *r, int soft)
 {
 	safe_free(r);
+	return 0;
 }
 
 void *cmodeL_dup_struct(void *r_in)
diff --git a/src/modules/channeldb.c b/src/modules/channeldb.c
@@ -93,7 +93,7 @@ MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	/* We must unload early, when all channel modes and such are still in place: */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -99999999);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
 
 	LoadPersistentLong(modinfo, channeldb_next_event);
 
@@ -359,14 +359,6 @@ int write_channel_entry(UnrealDB *db, const char *tmpfname, Channel *channel)
 	return 1;
 }
 
-int ban_exists(Ban *lst, Ban *e)
-{
-	for (; lst; lst = lst->next)
-		if (!mycmp(lst->banstr, e->banstr))
-			return 1;
-	return 0;
-}
-
 #define R_SAFE(x) \
 	do { \
 		if (!(x)) { \
@@ -409,7 +401,7 @@ int read_listmode(UnrealDB *db, Ban **lst)
 		}
 		safe_strdup(e->banstr, str);
 
-		if (ban_exists(*lst, e))
+		if (ban_exists(*lst, e->banstr))
 		{
 			/* Free again - duplicate item */
 			safe_free(e->banstr);
@@ -545,7 +537,7 @@ int read_channeldb(void)
 		safe_strdup(channel->topic_nick, topic_nick);
 		channel->topic_time = topic_time;
 		safe_strdup(channel->mode_lock, mode_lock);
-		set_channel_mode(channel, modes1, modes2);
+		set_channel_mode(channel, NULL, modes1, modes2);
 		R_SAFE(read_listmode(db, &channel->banlist));
 		R_SAFE(read_listmode(db, &channel->exlist));
 		R_SAFE(read_listmode(db, &channel->invexlist));
diff --git a/src/modules/chathistory.c b/src/modules/chathistory.c
@@ -210,6 +210,18 @@ void chathistory_targets(Client *client, HistoryFilter *filter, int limit)
 		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
 }
 
+void send_empty_batch(Client *client, const char *target)
+{
+	char batch[BATCHLEN+1];
+
+	if (HasCapability(client, "batch"))
+	{
+		generate_batch_id(batch);
+		sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, target);
+		sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
+	}
+}
+
 CMD_FUNC(cmd_chathistory)
 {
 	HistoryFilter *filter = NULL;
@@ -261,6 +273,13 @@ CMD_FUNC(cmd_chathistory)
 		goto end;
 	}
 
+	/* We don't support retrieving chathistory for PM's. Send empty response/batch, similar to channels without +H. */
+	if (parv[2][0] != '#')
+	{
+		send_empty_batch(client, parv[2]);
+		return;
+	}
+
 	channel = find_channel(parv[2]);
 	if (!channel)
 	{
@@ -279,14 +298,7 @@ CMD_FUNC(cmd_chathistory)
 	/* Channel is not +H? Send empty response/batch (as per IRCv3 discussion) */
 	if (!has_channel_mode(channel, 'H'))
 	{
-		if (HasCapability(client, "batch"))
-		{
-			char batch[BATCHLEN+1];
-
-			generate_batch_id(batch);
-			sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, channel->name);
-			sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch);
-		}
+		send_empty_batch(client, channel->name);
 		return;
 	}
 
diff --git a/src/modules/chghost.c b/src/modules/chghost.c
@@ -288,7 +288,21 @@ CMD_FUNC(cmd_chghost)
 		return;
 	}
 
-	if (!(target = find_user(parv[1], NULL)))
+	target = find_client(parv[1], NULL);
+	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
+	{
+		/* CHGHOST for a UID that is not online.
+		 * Let's assume it may not YET be online and forward the message to
+		 * the remote server and stop processing ourselves.
+		 * That server will then handle pre-registered processing of the
+		 * CHGHOST and later communicate the host when the user actually
+		 * comes online in the UID message.
+		 */
+		sendto_one(target, recv_mtags, ":%s CHGHOST %s %s", client->id, parv[1], parv[2]);
+		return;
+	}
+
+	if (!target || !target->user)
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -328,17 +342,31 @@ CMD_FUNC(cmd_chghost)
 
 	if (!IsULine(client))
 	{
-		unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
-		           "CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname",
-		           log_data_string("change_type", "hostname"),
-			   log_data_client("target", target),
-		           log_data_string("new_hostname", parv[2]));
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
+				   "CHGHOST: $issuer changed the virtual hostname of $target.details to be $new_hostname",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "hostname"),
+				   log_data_client("target", target),
+				   log_data_string("new_hostname", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client,
+				   "CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname",
+				   log_data_string("change_type", "hostname"),
+				   log_data_client("target", target),
+				   log_data_string("new_hostname", parv[2]));
+		}
 	}
 
 	target->umodes |= UMODE_HIDE;
 	target->umodes |= UMODE_SETHOST;
-	sendto_server(client, 0, 0, NULL, ":%s CHGHOST %s %s", client->id, target->id, parv[2]);
+
+	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
+	if (IsUser(target))
+		sendto_server(client, 0, 0, recv_mtags, ":%s CHGHOST %s %s", client->id, target->id, parv[2]);
+
 	safe_strdup(target->user->virthost, parv[2]);
-	
 	userhost_changed(target);
 }
diff --git a/src/modules/chgident.c b/src/modules/chgident.c
@@ -85,24 +85,27 @@ CMD_FUNC(cmd_chgident)
 		return;
 	}
 
-	/* illegal?! */
-	for (s = parv[2]; *s; s++)
+	if (!valid_username(parv[2]))
 	{
-		if ((*s == '~') && (s == parv[2]))
-			continue;
-		if (!isallowed(*s))
-		{
-			legalident = 0;
-		}
+		sendnotice(client, "*** /ChgIdent Error: A ident may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
+		return;
 	}
 
-	if (legalident == 0)
+	target = find_client(parv[1], NULL);
+	if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1])))
 	{
-		sendnotice(client, "*** /ChgIdent Error: A ident may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them");
+		/* CHGIDENT for a UID that is not online.
+		 * Let's assume it may not YET be online and forward the message to
+		 * the remote server and stop processing ourselves.
+		 * That server will then handle pre-registered processing of the
+		 * CHGIDENT and later communicate the host when the user actually
+		 * comes online in the UID message.
+		 */
+		sendto_one(target, recv_mtags, ":%s CHGIDENT %s %s", client->id, parv[1], parv[2]);
 		return;
 	}
 
-	if (!(target = find_user(parv[1], NULL)))
+	if (!target || !target->user)
 	{
 		sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
 		return;
@@ -134,16 +137,28 @@ CMD_FUNC(cmd_chgident)
 	}
 	if (!IsULine(client))
 	{
-		unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
-		           "CHGIDENT: $client changed the username of $target.details to be $new_username",
-		           log_data_string("change_type", "username"),
-			   log_data_client("target", target),
-		           log_data_string("new_username", parv[2]));
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
+				   "CHGIDENT: $issuer changed the username of $target.details to be $new_username",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "username"),
+				   log_data_client("target", target),
+				   log_data_string("new_username", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGIDENT_COMMAND", client,
+				   "CHGIDENT: $client changed the username of $target.details to be $new_username",
+				   log_data_string("change_type", "username"),
+				   log_data_client("target", target),
+				   log_data_string("new_username", parv[2]));
+		}
 	}
 
-	sendto_server(client, 0, 0, NULL, ":%s CHGIDENT %s %s",
-	    client->id, target->id, parv[2]);
-	ircsnprintf(target->user->username, sizeof(target->user->username), "%s", parv[2]);
+	/* Send to other servers too, unless the client is still in the registration phase (SASL) */
+	if (IsUser(target))
+		sendto_server(client, 0, 0, recv_mtags, ":%s CHGIDENT %s %s", client->id, target->id, parv[2]);
 
+	ircsnprintf(target->user->username, sizeof(target->user->username), "%s", parv[2]);
 	userhost_changed(target);
 }
diff --git a/src/modules/chgname.c b/src/modules/chgname.c
@@ -97,11 +97,22 @@ CMD_FUNC(cmd_chgname)
 	/* Let's log this first */
 	if (!IsULine(client))
 	{
-		unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
-		           "CHGNAME: $client changed the realname of $target.details to be $new_realname",
-		           log_data_string("change_type", "realname"),
-			   log_data_client("target", target),
-		           log_data_string("new_realname", parv[2]));
+		const char *issuer = command_issued_by_rpc(recv_mtags);
+		if (issuer)
+		{
+			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
+				   "CHGNAME: $issuer changed the realname of $target.details to be $new_realname",
+				   log_data_string("issuer", issuer),
+				   log_data_string("change_type", "realname"),
+				   log_data_client("target", target),
+				   log_data_string("new_realname", parv[2]));
+		} else {
+			unreal_log(ULOG_INFO, "chgcmds", "CHGNAME_COMMAND", client,
+				   "CHGNAME: $client changed the realname of $target.details to be $new_realname",
+				   log_data_string("change_type", "realname"),
+				   log_data_client("target", target),
+				   log_data_string("new_realname", parv[2]));
+		}
 	}
 
 	/* set the realname to make ban checking work */
@@ -118,6 +129,6 @@ CMD_FUNC(cmd_chgname)
 		}
 	}
 
-	sendto_server(client, 0, 0, NULL, ":%s CHGNAME %s :%s",
+	sendto_server(client, 0, 0, recv_mtags, ":%s CHGNAME %s :%s",
 	    client->id, target->name, parv[2]);
 }
diff --git a/src/modules/connect-flood.c b/src/modules/connect-flood.c
@@ -0,0 +1,89 @@
+/*
+ * Connection throttling (set::anti-flood::connect-flood)
+ * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"connect-flood",
+	"6.0.0",
+	"set::anti-flood::connect-flood",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declaration */
+int connect_flood_accept(Client *client);
+int connect_flood_ip_change(Client *client, const char *oldip);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -3000, connect_flood_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -3000, connect_flood_ip_change);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int connect_flood_throttle(Client *client, int exitflags)
+{
+	int val;
+	char zlinebuf[512];
+
+	if (!(val = throttle_can_connect(client)))
+	{
+		if (exitflags & NO_EXIT_CLIENT)
+		{
+			ircsnprintf(zlinebuf, sizeof(zlinebuf),
+				"ERROR :Closing Link: [%s] (Throttled: Reconnecting too fast) - "
+				"Email %s for more information.\r\n",
+				client->ip, KLINE_ADDRESS);
+			(void)send(client->local->fd, zlinebuf, strlen(zlinebuf), 0);
+			return HOOK_DENY;
+		} else {
+			ircsnprintf(zlinebuf, sizeof(zlinebuf),
+				    "Throttled: Reconnecting too fast - "
+				    "Email %s for more information.",
+				    KLINE_ADDRESS);
+			/* WAS: exit_client(client, NULL, zlinebuf);
+			 * Can't use exit_client() here because HOOKTYPE_IP_CHANGE call
+			 * may be too deep. Eg: read_packet -> webserver_packet_in ->
+			 * webserver_handle_request_header -> webserver_handle_request ->
+			 * RunHook().... and then returning without touching anything
+			 * after an exit_client() would not be feasible.
+			 */
+			dead_socket(client, zlinebuf);
+			return HOOK_DENY;
+		}
+	}
+	else if (val == 1)
+		add_throttling_bucket(client);
+
+	return 0;
+}
+
+int connect_flood_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
+		return 0;
+	return connect_flood_throttle(client, NO_EXIT_CLIENT);
+}
+
+int connect_flood_ip_change(Client *client, const char *oldip)
+{
+	return connect_flood_throttle(client, 0);
+}
diff --git a/src/modules/connect.c b/src/modules/connect.c
@@ -62,8 +62,8 @@ CMD_FUNC(cmd_connect)
 {
 	int  retval;
 	ConfigItem_link	*aconf;
-	ConfigItem_deny_link *deny;
 	Client *server;
+	const char *str;
 
 	if (!IsServer(client) && MyConnect(client) && !ValidatePermissionsForPath("route:global",client,NULL,NULL,NULL) && parc > 3)
 	{			/* Only allow LocOps to make */
@@ -92,12 +92,7 @@ CMD_FUNC(cmd_connect)
 		return;
 	}
 
-	for (aconf = conf_link; aconf; aconf = aconf->next)
-		if (match_simple(parv[1], aconf->servername))
-			break;
-
-	/* Checked first servernames, then try hostnames. */
-
+	aconf = find_link(parv[1]);
 	if (!aconf)
 	{
 		sendnotice(client,
@@ -114,25 +109,15 @@ CMD_FUNC(cmd_connect)
 		return;
 	}
 
-	/* Evaluate deny link */
-	for (deny = conf_deny_link; deny; deny = deny->next)
+	if ((str = check_deny_link(aconf, 0)))
 	{
-		if (deny->flag.type == CRULE_ALL && unreal_mask_match_string(aconf->servername, deny->mask)
-			&& crule_eval(deny->rule))
-		{
-			sendnotice(client, "*** Connect: Disallowed by connection rule");
-			return;
-		}
+		sendnotice(client, "*** Connect: Disallowed by connection rule: %s", str);
+		return;
 	}
 
-	/* Notify all operators about remote connect requests */
-	if (!MyUser(client))
-	{
-		sendto_server(NULL, 0, 0, NULL,
-		    ":%s SENDUMODE o :Remote CONNECT %s %s from %s",
-		    me.id, parv[1], parv[2] ? parv[2] : "",
-		    get_client_name(client, FALSE));
-	}
+	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
+		   "CONNECT: Link to $link_block requested by $client",
+		   log_data_link_block(aconf));
 
 	connect_server(aconf, client, NULL);
 }
diff --git a/src/modules/creationtime.c b/src/modules/creationtime.c
@@ -9,7 +9,7 @@
 ModuleHeader MOD_HEADER
   = {
 	"creationtime",
-	"6.0",
+	"6.1",
 	"Store and retrieve creation time of clients",
 	"UnrealIRCd Team",
 	"unrealircd-6",
@@ -38,6 +38,7 @@ MOD_INIT()
 	mreq.free = creationtime_free;
 	mreq.serialize = creationtime_serialize;
 	mreq.unserialize = creationtime_unserialize;
+	mreq.self_write = 1;
 	mreq.sync = MODDATA_SYNC_EARLY;
 	mreq.type = MODDATATYPE_CLIENT;
 	creationtime_md = ModDataAdd(modinfo->handle, mreq);
diff --git a/src/modules/echo-message.c b/src/modules/echo-message.c
@@ -26,7 +26,7 @@ ModuleHeader MOD_HEADER
   = {
 	"echo-message",
 	"5.0",
-	"Batch CAP", 
+	"echo-message CAP",
 	"UnrealIRCd Team",
 	"unrealircd-6",
 	};
diff --git a/src/modules/extbans/Makefile.in b/src/modules/extbans/Makefile.in
@@ -35,7 +35,7 @@ R_MODULES= \
 	join.so quiet.so nickchange.so inchannel.so realname.so \
 	account.so operclass.so certfp.so textban.so msgbypass.so \
 	timedban.so partmsg.so securitygroup.so \
-	country.so
+	country.so flood.so
 
 MODULES=$(R_MODULES)
 MODULEFLAGS=@MODULEFLAGS@
diff --git a/src/modules/extbans/account.c b/src/modules/extbans/account.c
@@ -66,6 +66,8 @@ MOD_INIT()
 		return MOD_FAILED;
 	}
 
+	ISupportAdd(modinfo->handle, "ACCOUNTEXTBAN", "account,a");
+
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
 }
@@ -100,15 +102,15 @@ const char *extban_account_conv_param(BanContext *b, Extban *extban)
 int extban_account_is_banned(BanContext *b)
 {
 	/* ~a:0 is special and matches all unauthenticated users */
-	if (!strcmp(b->banstr, "0") && !IsLoggedIn(b->client))
-		return 1;
+	if (!strcmp(b->banstr, "0"))
+		return IsLoggedIn(b->client) ? 0 : 1;
 
 	/* ~a:* matches all authenticated users
 	 * (Yes this special code is needed because account
 	 *  is 0 or * for unauthenticated users)
 	 */
-	if (!strcmp(b->banstr, "*") && IsLoggedIn(b->client))
-		return 1;
+	if (!strcmp(b->banstr, "*"))
+		return IsLoggedIn(b->client) ? 1 : 0;
 
 	if (b->client->user && match_simple(b->banstr, b->client->user->account))
 		return 1;
diff --git a/src/modules/extbans/flood.c b/src/modules/extbans/flood.c
@@ -0,0 +1,187 @@
+/*
+ * Extended ban to exempt from +f/+F checking.
+ * Eg: +e ~flood:*:~account:TrustedBot
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 1, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"extbans/flood",
+	"1.0",
+	"Extban ~flood - exempt from +f/+F checks",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/** Maximum length of the ~flood ban exemption */
+#define MAX_FLOODBAN_LENGTH 128
+
+/* Forward declarations */
+int extban_flood_is_banned(BanContext *b);
+int flood_extban_is_ok(BanContext *b);
+const char *flood_extban_conv_param(BanContext *b, Extban *extban);
+
+/** Called upon module init */
+MOD_INIT()
+{
+	ExtbanInfo req;
+
+	memset(&req, 0, sizeof(req));
+	req.letter = 'F';
+	req.name = "flood";
+	req.is_ok = flood_extban_is_ok;
+	req.conv_param = flood_extban_conv_param;
+	req.options = EXTBOPT_ACTMODIFIER;
+	if (!ExtbanAdd(modinfo->handle, req))
+	{
+		config_error("could not register extended ban type 'flood'");
+		return MOD_FAILED;
+	}
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	return MOD_SUCCESS;
+}
+
+/** Called upon module load */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Called upon unload */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** Check if letters in 'str' are valid flood-types.
+ * TODO: ideally this would call a function in chanmode +F module!!
+ */
+static int flood_type_ok(char *str)
+{
+	char *p;
+
+	/* The * (asterisk) simply means ALL. */
+	if (!strcmp(str, "*"))
+		return 1;
+
+	for (p = str; *p; p++)
+		if (!strchr("cjkmntr", *p))
+			return 0;
+
+	return 1;
+}
+
+const char *flood_extban_conv_param(BanContext *b, Extban *extban)
+{
+	static char retbuf[MAX_FLOODBAN_LENGTH+1];
+	char para[MAX_FLOODBAN_LENGTH+1];
+	char tmpmask[MAX_FLOODBAN_LENGTH+1];
+	char *type; /**< Type(s), such as 'j' */
+	char *matchby; /**< Matching method, such as 'n!u@h' */
+	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
+
+	/* ~flood:type:n!u@h   for direct matching
+	 * ~flood:type:~x:.... when calling another bantype
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return NULL;
+	*matchby++ = '\0';
+
+	/* don't verify type(s), already done in is_ok for local clients */
+	//if (!flood_type_ok(type))
+	//	return NULL;
+	/* ... but limit them to a reasonable value :D */
+	if (strlen(type) > 16)
+		return NULL;
+
+	b->banstr = matchby;
+	newmask = extban_conv_param_nuh_or_extban(b, extban);
+	if (BadPtr(newmask))
+		return NULL;
+
+	snprintf(retbuf, sizeof(retbuf), "%s:%s", type, newmask);
+	return retbuf;
+}
+
+int flood_extban_syntax(Client *client, int checkt, char *reason)
+{
+	if (MyUser(client) && (checkt == EXBCHK_PARAM))
+	{
+		sendnotice(client, "Error when setting ban exception: %s", reason);
+		sendnotice(client, " Syntax: +e ~flood:floodtype(s):mask");
+		sendnotice(client, "Example: +e ~flood:*:~account:TrustedUser");
+		sendnotice(client, "Valid flood types are: c, j, k, m, n, t, r, and * for all");
+		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~account, ~certfp, etc.");
+	}
+	return 0; /* FAIL: ban rejected */
+}
+
+int flood_extban_is_ok(BanContext *b)
+{
+	static char para[MAX_FLOODBAN_LENGTH+1];
+	char *type; /**< Type(s), such as 'j' */
+	char *matchby; /**< Matching method, such as 'n!u@h' */
+	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
+
+	/* Always permit deletion */
+	if (b->what == MODE_DEL)
+		return 1;
+
+	if (b->ban_type != EXBTYPE_EXCEPT)
+	{
+		if (b->is_ok_check == EXBCHK_PARAM)
+			sendnotice(b->client, "Ban type ~flood only works with exceptions (+e) and not with bans or invex (+b/+I)");
+		return 0; /* reject */
+	}
+
+	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
+
+	/* ~flood:type:n!u@h   for direct matching
+	 * ~flood:type:~x:.... when calling another bantype
+	 */
+
+	type = para;
+	matchby = strchr(para, ':');
+	if (!matchby || !matchby[1])
+		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
+	*matchby++ = '\0';
+
+	if (!flood_type_ok(type))
+		return flood_extban_syntax(b->client, b->is_ok_check, "Unknown flood type");
+	if (strlen(type) > 16)
+		return flood_extban_syntax(b->client, b->is_ok_check, "Too many flood types specified");
+
+	b->banstr = matchby;
+	if (extban_is_ok_nuh_extban(b) == 0)
+	{
+		/* This could be anything ranging from:
+		 * invalid n!u@h syntax, unknown (sub)extbantype,
+		 * disabled extban type in conf, too much recursion, etc.
+		 */
+		return flood_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
+	}
+
+	return 1; /* OK */
+}
diff --git a/src/modules/extbans/timedban.c b/src/modules/extbans/timedban.c
@@ -112,8 +112,6 @@ MOD_UNLOAD()
 const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
 {
 	char *cp, *x;
-	char *user;
-	char *host;
 	static char maskbuf[512];
 	char *mask;
 
@@ -162,23 +160,7 @@ const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
 		return mask;
 	}
 
-	if ((*mask == '~') && !strchr(mask, '@'))
-		return NULL; /* not an extended ban and not a ~user@host ban either. */
-
-	if ((user = strchr((cp = mask), '!')))
-		*user++ = '\0';
-	if ((host = strrchr(user ? user : cp, '@')))
-	{
-		*host++ = '\0';
-
-		if (!user)
-			return make_nick_user_host(NULL, trim_str(cp,USERLEN), 
-				trim_str(host,HOSTLEN));
-	}
-	else if (!user && strchr(cp, '.'))
-		return make_nick_user_host(NULL, NULL, trim_str(cp,HOSTLEN));
-	return make_nick_user_host(trim_str(cp,NICKLEN), trim_str(user,USERLEN), 
-		trim_str(host,HOSTLEN));
+	return convert_regular_ban(mask, NULL, 0);
 }
 
 /** Convert ban to an acceptable format (or return NULL to fully reject it) */
diff --git a/src/modules/geoip_base.c b/src/modules/geoip_base.c
@@ -28,7 +28,7 @@ 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_log(Client *client, int detail, json_t *j);
+int geoip_json_expand_client(Client *client, int detail, json_t *j);
 int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
 EVENT(geoip_base_set_existing_users_evt);
@@ -126,7 +126,7 @@ MOD_INIT()
 	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_WHOIS, 0, geoip_base_whois);
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_log);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_json_expand_client);
 
 	CommandAdd(modinfo->handle, "GEOIP", cmd_geoip, MAXPARA, CMD_USER);
 
@@ -256,7 +256,7 @@ int geoip_connect_extinfo(Client *client, NameValuePrioList **list)
 	return 0;
 }
 
-int geoip_log(Client *client, int detail, json_t *j)
+int geoip_json_expand_client(Client *client, int detail, json_t *j)
 {
 	GeoIPResult *geo = GEOIPDATA(client);
 	json_t *geoip;
diff --git a/src/modules/hideserver.c b/src/modules/hideserver.c
@@ -386,7 +386,7 @@ CMD_OVERRIDE_FUNC(override_map)
 	
 	if (IsOper(client))
 	{
-		CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+		CALL_NEXT_COMMAND_OVERRIDE();
 		return;
 	}
 
@@ -435,7 +435,7 @@ CMD_OVERRIDE_FUNC(override_links)
 
 	if (IsOper(client))
 	{
-		CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+		CALL_NEXT_COMMAND_OVERRIDE();
 		return;
 	}
 
diff --git a/src/modules/history_backend_mem.c b/src/modules/history_backend_mem.c
@@ -132,7 +132,7 @@ MOD_INIT()
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 	/* We must unload early, when all channel modes and such are still in place: */
-	ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -99999999);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -99999999);
 
 	setcfg(&cfg);
 
diff --git a/src/modules/issued-by-tag.c b/src/modules/issued-by-tag.c
@@ -0,0 +1,155 @@
+/*
+ * unrealircd.org/issued-by message tag (server only)
+ * Shows who or what actually issued the command.
+ * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"issued-by-tag",
+	"6.0",
+	"unrealircd.org/issued-by message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+int issued_by_mtag_is_ok(Client *client, const char *name, const char *value);
+int issued_by_mtag_should_send_to_client(Client *target);
+void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_MTAG_GENERATE_ISSUED_BY_IRC, _mtag_add_issued_by);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/issued-by";
+	mtag.is_ok = issued_by_mtag_is_ok;
+	mtag.should_send_to_client = issued_by_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_issued_by);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending 'unrealircd.org/issued-by'
+ * is permitted to do so and uses a permitted syntax.
+ * We simply allow unrealircd.org/issued-by ONLY from servers and with any syntax.
+ */
+int issued_by_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_inherit_issued_by(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int issued_by_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target) || IsOper(target))
+		return 1;
+
+	return 0;
+}
+
+/** Add "unrealircd.org/issued-by" tag, if applicable.
+ * @param mtags		Pointer to the message tags linked list head
+ * @param client	The client issuing the command, or NULL for none.
+ * @param recv_mtags	The mtags to inherit from, or NULL for none.
+ * @notes If specifying both 'client' and 'recv_mtags' then
+ * if inheritance through 'recv_mtags' takes precedence (if it exists).
+ *
+ * Typical usage is:
+ * For locally generated:
+ *   mtag_add_issued_by(&mtags, client, NULL);
+ * For inheriting from remote requests:
+ *   mtag_add_issued_by(&mtags, NULL, recv_mtags);
+ * For both, such as if the command is used from RPC:
+ *   mtag_add_issued_by(&mtags, client, recv_mtags);
+ */
+void _mtag_add_issued_by(MessageTag **mtags, Client *client, MessageTag *recv_mtags)
+{
+	MessageTag *m;
+	char buf[512];
+
+	m = find_mtag(recv_mtags, "unrealircd.org/issued-by");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtags);
+		return;
+	}
+
+	if (client == NULL)
+		return;
+
+	if (IsRPC(client) && client->rpc)
+	{
+		// TODO: test with all of: local rpc through unix socket, local rpc web, RRPC
+		if (client->rpc->issuer)
+		{
+			snprintf(buf, sizeof(buf), "RPC:%s@%s:%s", client->rpc->rpc_user, client->uplink->name, client->rpc->issuer);
+		} else {
+			snprintf(buf, sizeof(buf), "RPC:%s@%s", client->rpc->rpc_user, client->uplink->name);
+		}
+	} else
+	if (IsULine(client))
+	{
+		if (IsUser(client))
+			snprintf(buf, sizeof(buf), "SERVICES:%s@%s", client->name, client->uplink->name);
+		else
+			snprintf(buf, sizeof(buf), "SERVICES:%s", client->name);
+	} else
+	if (IsOper(client))
+	{
+		const char *operlogin = moddata_client_get(client, "operlogin");
+		if (operlogin)
+			snprintf(buf, sizeof(buf), "OPER:%s@%s:%s", client->name, client->uplink->name, operlogin);
+		else
+			snprintf(buf, sizeof(buf), "OPER:%s@%s", client->name, client->uplink->name);
+	} else
+	{
+		return;
+	}
+
+	m = safe_alloc(sizeof(MessageTag));
+	safe_strdup(m->name, "unrealircd.org/issued-by");
+	safe_strdup(m->value, buf);
+	AddListItem(m, *mtags);
+}
diff --git a/src/modules/join.c b/src/modules/join.c
@@ -207,7 +207,8 @@ void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mta
 }
 
 /* Routine that actually makes a user join the channel
- * this does no actual checking (banned, etc.) it just adds the user
+ * this does no actual checking (banned, etc.) it just adds the user.
+ * Note: this is called for local JOIN and remote JOIN, but not for SJOIN.
  */
 void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes)
 {
@@ -491,7 +492,7 @@ void _do_join(Client *client, int parc, const char *parv[])
 					}
 				}
 			}
-			if (ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold)))
+			if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold)))
 			{
 				sendnumeric(client, ERR_FORBIDDENCHANNEL, name, tklban->ptr.nameban->reason);
 				continue;
diff --git a/src/modules/json-log-tag.c b/src/modules/json-log-tag.c
@@ -38,6 +38,12 @@ long CAP_JSON_LOG = 0L;
 int json_log_mtag_is_ok(Client *client, const char *name, const char *value);
 int json_log_mtag_should_send_to_client(Client *target);
 
+MOD_TEST()
+{
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -1000000001); /* load very early */
+	return MOD_SUCCESS;
+}
+
 MOD_INIT()
 {
 	ClientCapabilityInfo cap;
@@ -57,6 +63,7 @@ MOD_INIT()
 	mtag.clicap_handler = c;
 	MessageTagHandlerAdd(modinfo->handle, &mtag);
 
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, 1000000001); /* unload very late */
 	return MOD_SUCCESS;
 }
 
diff --git a/src/modules/kick.c b/src/modules/kick.c
@@ -228,7 +228,7 @@ CMD_FUNC(cmd_kick)
 			continue;
 		}
 
-		if (IsULine(client) || IsServer(client))
+		if (IsULine(client) || IsServer(client) || IsMe(client))
 			goto attack;
 
 		/* Note for coders regarding oper override:
diff --git a/src/modules/labeled-response.c b/src/modules/labeled-response.c
@@ -39,7 +39,7 @@ struct LabeledResponseContext {
 	char batch[BATCHLEN+1]; /**< The generated batch id */
 	int responses; /**< Number of lines sent back to client */
 	int sent_remote; /**< Command has been sent to remote server */
-	char firstbuf[4096]; /**< First buffered response */
+	char firstbuf[MAXLINELENGTH]; /**< First buffered response */
 };
 
 /* Forward declarations */
@@ -59,7 +59,7 @@ void _labeled_response_force_end(void);
 static LabeledResponseContext currentcmd;
 static long CAP_LABELED_RESPONSE = 0L;
 
-static char packet[8192];
+static char packet[MAXLINELENGTH*2];
 
 int labeled_response_mtag_is_ok(Client *client, const char *name, const char *value);
 
diff --git a/src/modules/list.c b/src/modules/list.c
@@ -107,7 +107,7 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-/* Originally from bahamut, modified a bit for Unreal by codemastr
+/* Originally from bahamut, modified a bit for UnrealIRCd by codemastr
  * also Opers can now see +s channels -- codemastr */
 
 /*
@@ -316,7 +316,7 @@ CMD_FUNC(cmd_list)
  * Operates by stepping through the hashtable, sending the entries back if
  * they match the criteria.
  * client = Local client to send the output back to.
- * Taken from bahamut, modified for Unreal by codemastr.
+ * Taken from bahamut, modified for UnrealIRCd by codemastr.
  */
 int send_list(Client *client)
 {
diff --git a/src/modules/max-unknown-connections-per-ip.c b/src/modules/max-unknown-connections-per-ip.c
@@ -0,0 +1,96 @@
+/*
+ * Connection throttling (set::max-unknown-connections-per-ip)
+ * (C) Copyright 2022- Bram Matthys and the UnrealIRCd team.
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"max-unknown-connections-per-ip",
+	"6.0.0",
+	"set::max-unknown-connections-per-ip",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* Forward declaration */
+int max_unknown_connections_accept(Client *client);
+int max_unknown_connections_ip_change(Client *client, const char *oldip);
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -2000, max_unknown_connections_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, -2000, max_unknown_connections_ip_change);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This checks set::max-unknown-connections-per-ip,
+ * which is an important safety feature.
+ */
+static int check_too_many_unknown_connections(Client *client)
+{
+	int cnt = 1;
+	Client *c;
+
+	if (!find_tkl_exception(TKL_CONNECT_FLOOD, client))
+	{
+		list_for_each_entry(c, &unknown_list, lclient_node)
+		{
+			if (client->local && client->local->listener &&
+			    (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD))
+			{
+				continue;
+			}
+			if (!strcmp(client->ip,GetIP(c)))
+			{
+				cnt++;
+				if (cnt > iConf.max_unknown_connections_per_ip)
+					return 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+int max_unknown_connections_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_NO_CHECK_CONNECT_FLOOD)
+		return 0;
+
+	/* Check set::max-unknown-connections-per-ip */
+	if (check_too_many_unknown_connections(client))
+	{
+		send_raw_direct(client, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
+		return HOOK_DENY;
+	}
+
+	return 0;
+}
+
+int max_unknown_connections_ip_change(Client *client, const char *oldip)
+{
+	/* Check set::max-unknown-connections-per-ip */
+	if (check_too_many_unknown_connections(client))
+	{
+		sendto_one(client, NULL, "ERROR :Closing Link: [%s] (Too many unknown connections from your IP)", client->ip);
+		return HOOK_DENY;
+	}
+
+	return 0;
+}
diff --git a/src/modules/message-tags.c b/src/modules/message-tags.c
@@ -170,18 +170,27 @@ void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
 	MessageTag *m;
 
 	remainder = strchr(*str, ' ');
+	if (remainder)
+		*remainder = '\0';
+
+	if (!IsServer(client) && (strlen(*str) > 4094))
+	{
+		sendnumeric(client, ERR_INPUTTOOLONG);
+		remainder = NULL; /* stop parsing */
+	}
+
 	if (!remainder)
 	{
 		/* A message with only message tags (or starting with @ anyway).
 		 * This is useless. So we make it point to the NUL byte,
 		 * aka: empty message.
+		 * This is also used by a line-length-check above to force the
+		 * same error condition ("don't parse this").
 		 */
 		for (; **str; *str += 1);
 		return;
 	}
 
-	*remainder = '\0';
-
 	/* Now actually parse the tags: */
 	for (element = strtoken(&p, *str+1, ";"); element; element = strtoken(&p, NULL, ";"))
 	{
diff --git a/src/modules/message.c b/src/modules/message.c
@@ -537,11 +537,15 @@ const char *_StripColors(const char *text)
 
 	while (len > 0) 
 	{
-		if ((col && isdigit(*text) && nc < 2) || (col && *text == ',' && nc < 3)) 
+		if ((col && isdigit(*text) && nc < 2) ||
+		    ((col == 1) && (*text == ',') && isdigit(text[1]) && (nc > 0) && (nc < 3)))
 		{
 			nc++;
 			if (*text == ',')
+			{
 				nc = 0;
+				col++;
+			}
 		}
 		/* Syntax for RGB is ^DHHHHHH where H is a hex digit.
 		 * If < 6 hex digits are specified, the code is displayed
diff --git a/src/modules/mode.c b/src/modules/mode.c
@@ -38,7 +38,7 @@ CMD_FUNC(cmd_mlock);
 void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc, const char *parv[], time_t sendts, int samode);
 MultiLineMode *_set_mode(Channel *channel, Client *client, int parc, const char *parv[], u_int *pcount,
                        char pvar[MAXMODEPARAMS][MODEBUFLEN + 3]);
-void _set_channel_mode(Channel *channel, char *modes, char *parameters);
+void _set_channel_mode(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters);
 CMD_FUNC(_cmd_umode);
 
 /* local: */
@@ -107,12 +107,12 @@ CMD_FUNC(cmd_mode)
 			channel = find_channel(parv[1]);
 			if (!channel)
 			{
-				cmd_umode(client, recv_mtags, parc, parv);
+				CALL_CMD_FUNC(cmd_umode);
 				return;
 			}
 		} else
 		{
-			cmd_umode(client, recv_mtags, parc, parv);
+			CALL_CMD_FUNC(cmd_umode);
 			return;
 		}
 	} else
@@ -312,6 +312,22 @@ void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc
 		MessageTag *mtags = NULL;
 		int should_destroy = 0;
 
+		if (IsUser(orig_client) && samode && MyUser(orig_client))
+		{
+			if (!sajoinmode)
+			{
+				char buf[512];
+				snprintf(buf, sizeof(buf), "%s%s%s", modebuf, *parabuf ? " " : "", parabuf);
+				unreal_log(ULOG_INFO, "samode", "SAMODE_COMMAND", orig_client,
+					   "Client $client used SAMODE $channel ($mode)",
+					   log_data_channel("channel", channel),
+					   log_data_string("mode", buf));
+			}
+
+			client = &me;
+			sendts = 0;
+		}
+
 		if (m->numlines == 1)
 		{
 			/* Single mode lines are easy: retain original msgid etc */
@@ -350,22 +366,6 @@ void _do_mode(Channel *channel, Client *client, MessageTag *recv_mtags, int parc
 		}
 #endif
 
-		if (IsUser(orig_client) && samode && MyUser(orig_client))
-		{
-			if (!sajoinmode)
-			{
-				char buf[512];
-				snprintf(buf, sizeof(buf), "%s%s%s", modebuf, *parabuf ? " " : "", parabuf);
-				unreal_log(ULOG_INFO, "samode", "SAMODE_COMMAND", orig_client,
-					   "Client $client used SAMODE $channel ($mode)",
-					   log_data_channel("channel", channel),
-					   log_data_string("mode", buf));
-			}
-
-			client = &me;
-			sendts = 0;
-		}
-
 		sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags,
 			       ":%s MODE %s %s %s",
 			       client->name, channel->name, modebuf, parabuf);
@@ -1545,7 +1545,7 @@ int list_mode_request(Client *client, Channel *channel, const char *req)
 	return 1; /* handled */
 }
 
-void _set_channel_mode(Channel *channel, char *modes, char *parameters)
+void _set_channel_mode(Channel *channel, MessageTag *mtags, const char *modes, const char *parameters)
 {
 	char buf[512];
 	char *p, *param;
@@ -1561,7 +1561,7 @@ void _set_channel_mode(Channel *channel, char *modes, char *parameters)
 	myparv[myparc] = NULL;
 
 	SetULine(&me); // hack for crash.. set ulined so no access checks.
-	do_mode(channel, &me, NULL, myparc, (const char **)myparv, 0, 0);
+	do_mode(channel, &me, mtags, myparc, (const char **)myparv, 0, 0);
 	ClearULine(&me); // and clear it again..
 
 	for (i = 0; i < myparc; i++)
diff --git a/src/modules/motd.c b/src/modules/motd.c
@@ -58,7 +58,7 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_motd)
 {
-	ConfigItem_tld *ptr;
+	ConfigItem_tld *tld;
 	MOTDFile *themotd;
 	MOTDLine *motdline;
 	int  svsnofile = 0;
@@ -73,10 +73,10 @@ CMD_FUNC(cmd_motd)
 		return;
 	}
 
-	ptr = find_tld(client);
+	tld = find_tld(client);
 
-	if (ptr)
-		themotd = &ptr->motd;
+	if (tld && tld->motd.lines)
+		themotd = &tld->motd;
 	else
 		themotd = &motd;
 
diff --git a/src/modules/nick.c b/src/modules/nick.c
@@ -122,9 +122,9 @@ CMD_FUNC(cmd_nick_remote)
 	{
 		ircstats.is_kill++;
 		unreal_log(ULOG_ERROR, "nick", "BAD_NICK_REMOTE", client,
-		           "Server link $server tried to introduce bad nick '$nick' -- rejected.",
+		           "Server link $server tried to change '$client' to bad nick '$nick' -- rejected.",
 		           log_data_string("nick", parv[1]),
-		           log_data_client("server", client->direction));
+		           log_data_client("server", client->uplink));
 		mtags = NULL;
 		new_message(client, NULL, &mtags);
 		sendto_one(client, mtags, ":%s KILL %s :Illegal nick name", me.id, client->id);
@@ -205,7 +205,7 @@ CMD_FUNC(cmd_nick_remote)
 	new_message(client, recv_mtags, &mtags);
 	RunHook(HOOKTYPE_REMOTE_NICKCHANGE, client, mtags, nick);
 	client->lastnick = lastnick ? lastnick : TStime();
-	add_history(client, 1);
+	add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
 	sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
 	    client->id, nick, (long long)client->lastnick);
 	sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
@@ -433,8 +433,8 @@ CMD_FUNC(cmd_nick_local)
 
 		new_message(client, recv_mtags, &mtags);
 		RunHook(HOOKTYPE_LOCAL_NICKCHANGE, client, mtags, nick);
-		client->lastnick = TStime();
-		add_history(client, 1);
+		add_history(client, 1, WHOWAS_EVENT_NICK_CHANGE);
+		client->lastnick = TStime(); /* needs to be done AFTER add_history() */
 		sendto_server(client, 0, 0, mtags, ":%s NICK %s %lld",
 		    client->id, nick, (long long)client->lastnick);
 		sendto_local_common_channels(client, client, 0, mtags, ":%s NICK :%s", client->name, nick);
@@ -740,7 +740,7 @@ CMD_FUNC(cmd_nick)
 
 	if (MyConnect(client) && !IsServer(client))
 	{
-		cmd_nick_local(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(cmd_nick_local);
 	} else
 	if (!IsUser(client))
 	{
@@ -754,7 +754,7 @@ CMD_FUNC(cmd_nick)
 		return;
 	} else
 	{
-		cmd_nick_remote(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(cmd_nick_remote);
 	}
 }
 
@@ -764,7 +764,7 @@ CMD_FUNC(cmd_nick)
 void welcome_user(Client *client, TKL *viruschan_tkl)
 {
 	int i;
-	ConfigItem_tld *tlds;
+	ConfigItem_tld *tld;
 	char buf[BUFSIZE];
 
 	/* Make creation time the real 'online since' time, excluding registration time.
@@ -871,11 +871,11 @@ void welcome_user(Client *client, TKL *viruschan_tkl)
 	}
 
 	/* Force the user to join the given chans -- codemastr */
-	tlds = find_tld(client);
+	tld = find_tld(client);
 
-	if (tlds && !BadPtr(tlds->channel))
+	if (tld && !BadPtr(tld->channel))
 	{
-		char *chans = strdup(tlds->channel);
+		char *chans = strdup(tld->channel);
 		const char *args[3] = {
 			NULL,
 			chans,
@@ -901,14 +901,15 @@ void welcome_user(Client *client, TKL *viruschan_tkl)
 	}
 }
 
-/** Validate client->user->username.
+/** Make a valid client->user->username, or try to anyway.
  * @param client	The client to check
  * @param noident	Whether we should ignore the first ~ or not
  * @returns 1 if the username is acceptable, 0 if not.
  * @note This function will modify client->user->username to make it valid.
  *       Only if there are zero valid characters it will return 0.
+ * @note There is also valid_username() in src/misc.c
  */
-int valid_username(Client *client, int noident)
+int make_valid_username(Client *client, int noident)
 {
 	static char stripuser[USERLEN + 1];
 	char *i;
@@ -1015,7 +1016,7 @@ int _register_user(Client *client)
 	/* Now validate the username. This may alter client->user->username
 	 * or reject it completely.
 	 */
-	if (!valid_username(client, noident))
+	if (!make_valid_username(client, noident))
 	{
 		exit_client(client, NULL, "Hostile username. Please use only 0-9 a-z A-Z _ - and . in your username.");
 		return 0;
@@ -1081,7 +1082,10 @@ int _register_user(Client *client)
 	SetUser(client);
 
 	make_cloakedhost(client, client->user->realhost, client->user->cloakedhost, sizeof(client->user->cloakedhost));
-	safe_strdup(client->user->virthost, client->user->cloakedhost);
+
+	/* client->user->virthost should never be empty */
+	if (!IsSetHost(client) || !client->user->virthost)
+		safe_strdup(client->user->virthost, client->user->cloakedhost);
 
 	snprintf(descbuf, sizeof descbuf, "Client: %s", client->name);
 	fd_desc(client->local->fd, descbuf);
diff --git a/src/modules/oper.c b/src/modules/oper.c
@@ -63,18 +63,21 @@ MOD_UNLOAD()
 
 void set_oper_host(Client *client, const char *host)
 {
-        char uhost[HOSTLEN + USERLEN + 1];
-        char *p;
-        
-        strlcpy(uhost, host, sizeof(uhost));
-        
+	char uhost[HOSTLEN + USERLEN + 1];
+	char *p;
+
+	if (!valid_vhost(host))
+		return;
+
+	strlcpy(uhost, host, sizeof(uhost));
+
 	if ((p = strchr(uhost, '@')))
 	{
-	        *p++ = '\0';
+		*p++ = '\0';
 		strlcpy(client->user->username, uhost, sizeof(client->user->username));
 		sendto_server(NULL, 0, 0, NULL, ":%s SETIDENT %s",
-		    client->id, client->user->username);
-	        host = p;
+		              client->id, client->user->username);
+		host = p;
 	}
 	safe_strdup(client->user->virthost, host);
 	if (MyConnect(client))
diff --git a/src/modules/opermotd.c b/src/modules/opermotd.c
@@ -67,11 +67,9 @@ CMD_FUNC(cmd_opermotd)
 	}
 
 	tld = find_tld(client);
-
-	motdline = NULL;
-	if (tld)
+	if (tld && tld->opermotd.lines)
 		motdline = tld->opermotd.lines;
-	if (!motdline)
+	else
 		motdline = opermotd.lines;
 
 	if (!motdline)
diff --git a/src/modules/pass.c b/src/modules/pass.c
@@ -35,18 +35,6 @@ ModuleHeader MOD_HEADER
 	"unrealircd-6",
     };
 
-/* Forward declarations */
-int _check_banned(Client *client, int exitflags);
-
-MOD_TEST()
-{
-	MARK_AS_OFFICIAL_MODULE(modinfo);
-
-	EfunctionAdd(modinfo->handle, EFUNC_CHECK_BANNED, _check_banned);
-	
-	return MOD_SUCCESS;
-}
-
 MOD_INIT()
 {
 	CommandAdd(modinfo->handle, MSG_PASS, cmd_pass, 1, CMD_UNREGISTERED|CMD_USER|CMD_SERVER);
@@ -65,51 +53,6 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-/** Handles zlines/gzlines/throttling/unknown connections
- * @param client     Client to be checked
- * @param exitflags  Special flag (NO_EXIT_CLIENT) -- only used in very early stages of the connection
- * @returns 1 if user is banned and is or should be killed, 0 if not.
- */
-int _check_banned(Client *client, int exitflags)
-{
-	TKL *tk;
-
-	if ((tk = find_tkline_match_zap(client)))
-	{
-		banned_client(client, "Z-Lined", tk->ptr.serverban->reason, (tk->type & TKL_GLOBAL)?1:0, exitflags);
-		return 1;
-	}
-	else
-	{
-		int val;
-		char zlinebuf[512];
-
-		if (!(val = throttle_can_connect(client)))
-		{
-			if (exitflags & NO_EXIT_CLIENT)
-			{
-				ircsnprintf(zlinebuf, sizeof(zlinebuf),
-					"ERROR :Closing Link: [%s] (Throttled: Reconnecting too fast) - "
-					"Email %s for more information.\r\n",
-					client->ip, KLINE_ADDRESS);
-				(void)send(client->local->fd, zlinebuf, strlen(zlinebuf), 0);
-				return 1;
-			} else {
-				ircsnprintf(zlinebuf, sizeof(zlinebuf),
-				            "Throttled: Reconnecting too fast - "
-				            "Email %s for more information.",
-				            KLINE_ADDRESS);
-				exit_client(client, NULL, zlinebuf);
-				return 1;
-			}
-		}
-		else if (val == 1)
-			add_throttling_bucket(client);
-	}
-
-	return 0;
-}
-
 /***************************************************************************
  * cmd_pass() - Added Sat, 4 March 1989
  ***************************************************************************/
diff --git a/src/modules/pingpong.c b/src/modules/pingpong.c
@@ -162,7 +162,7 @@ CMD_FUNC(cmd_pong)
 
 	if (!IsRegistered(client))
 	{
-		cmd_nospoof(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(cmd_nospoof);
 		return;
 	}
 
diff --git a/src/modules/protoctl.c b/src/modules/protoctl.c
@@ -243,7 +243,7 @@ CMD_FUNC(cmd_protoctl)
 				p = NULL;
 			}
 			
-			servername = strtoken(&p, buf, ",");
+			servername = strtoken_noskip(&p, buf, ",");
 			if (!servername || !valid_server_name(servername))
 			{
 				exit_client(client, NULL, "Bogus server name");
@@ -251,14 +251,12 @@ CMD_FUNC(cmd_protoctl)
 			}
 			
 			
-			protocol = strtoken(&p, NULL, ",");
+			protocol = strtoken_noskip(&p, NULL, ",");
 			if (protocol)
 			{
-				flags = strtoken(&p, NULL, ",");
+				flags = strtoken_noskip(&p, NULL, ",");
 				if (flags)
-				{
-					software = strtoken(&p, NULL, ",");
-				}
+					software = strtoken_noskip(&p, NULL, ",");
 			}
 			
 			/* Set client->name but don't add to hash list, this gives better
@@ -279,6 +277,11 @@ CMD_FUNC(cmd_protoctl)
 				client->server->features.protocol = atoi(protocol);
 			if (software)
 				safe_strdup(client->server->features.software, software);
+			if (is_services_but_not_ulined(client))
+			{
+				exit_client_fmt(client, NULL, "Services detected but no ulines { } for server name %s", client->name);
+				return;
+			}
 			if (!IsHandshake(client) && aconf) /* Send PASS early... */
 				sendto_one(client, NULL, "PASS :%s", (aconf->auth->type == AUTHTYPE_PLAINTEXT) ? aconf->auth->data : "*");
 		}
diff --git a/src/modules/real-quit-reason.c b/src/modules/real-quit-reason.c
@@ -0,0 +1,81 @@
+/*
+ * unrealircd.org/real-quit-reason message tag (server only)
+ * This is really server-only, it does not traverse to any clients.
+ * (C) Copyright 2023-.. Syzop and The UnrealIRCd Team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"real-quit-reason",
+	"6.0",
+	"unrealircd.org/real-quit-reason message tag",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Forward declarations */
+int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value);
+int real_quit_reason_mtag_should_send_to_client(Client *target);
+void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature);
+
+MOD_INIT()
+{
+	MessageTagHandlerInfo mtag;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mtag, 0, sizeof(mtag));
+	mtag.name = "unrealircd.org/real-quit-reason";
+	mtag.is_ok = real_quit_reason_mtag_is_ok;
+	mtag.should_send_to_client = real_quit_reason_mtag_should_send_to_client;
+	mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
+	MessageTagHandlerAdd(modinfo->handle, &mtag);
+
+	HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_inherit_real_quit_reason);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** This function verifies if the client sending 'unrealircd.org/real-quit-reason'
+ * is permitted to do so and uses a permitted syntax.
+ * We simply allow unrealircd.org/real-quit-reason ONLY from servers and with any syntax.
+ */
+int real_quit_reason_mtag_is_ok(Client *client, const char *name, const char *value)
+{
+	if (IsServer(client))
+		return 1;
+
+	return 0;
+}
+
+void mtag_inherit_real_quit_reason(Client *client, MessageTag *recv_mtags, MessageTag **mtag_list, const char *signature)
+{
+	MessageTag *m = find_mtag(recv_mtags, "unrealircd.org/real-quit-reason");
+	if (m)
+	{
+		m = duplicate_mtag(m);
+		AddListItem(m, *mtag_list);
+	}
+}
+
+/** Outgoing filter for this message tag */
+int real_quit_reason_mtag_should_send_to_client(Client *target)
+{
+	if (IsServer(target))
+		return 1;
+
+	return 0;
+}
diff --git a/src/modules/reputation.c b/src/modules/reputation.c
@@ -1330,9 +1330,9 @@ CMD_FUNC(reputation_server_cmd)
 CMD_FUNC(reputation_cmd)
 {
 	if (MyUser(client))
-		reputation_user_cmd(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(reputation_user_cmd);
 	else if (IsServer(client) || IsMe(client))
-		reputation_server_cmd(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(reputation_server_cmd);
 }
 
 int reputation_whois(Client *client, Client *target, NameValuePrioList **list)
diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c
@@ -384,7 +384,7 @@ CMD_OVERRIDE_FUNC(rcmd_override)
 
 	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
 	{
-		CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+		CALL_NEXT_COMMAND_OVERRIDE();
 		return;
 	}
 
@@ -405,5 +405,5 @@ CMD_OVERRIDE_FUNC(rcmd_override)
 	}
 
 	// No restrictions apply, process command as normal =]
-	CallCommandOverride(ovr, client, recv_mtags, parc, parv);
+	CALL_NEXT_COMMAND_OVERRIDE();
 }
diff --git a/src/modules/rpc/Makefile.in b/src/modules/rpc/Makefile.in
@@ -0,0 +1,55 @@
+#************************************************************************
+#*   IRC - Internet Relay Chat, src/modules/rpc/Makefile
+#*   Copyright (C) Carsten V. Munk 2001 & The UnrealIRCd Team
+#*
+#*   This program is free software; you can redistribute it and/or modify
+#*   it under the terms of the GNU General Public License as published by
+#*   the Free Software Foundation; either version 1, or (at your option)
+#*   any later version.
+#*
+#*   This program is distributed in the hope that it will be useful,
+#*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#*   GNU General Public License for more details.
+#*
+#*   You should have received a copy of the GNU General Public License
+#*   along with this program; if not, write to the Free Software
+#*   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#*/
+
+CC = "==== DO NOT RUN MAKE FROM THIS DIRECTORY ===="
+
+INCLUDES = ../../include/channel.h \
+	../../include/common.h ../../include/config.h ../../include/dbuf.h \
+	../../include/dynconf.h ../../include/fdlist.h ../../include/h.h \
+	../../include/ircsprintf.h \
+	../../include/license.h \
+	../../include/modules.h ../../include/modversion.h ../../include/msg.h \
+	../../include/numeric.h ../../include/dns.h \
+	../../include/resource.h ../../include/setup.h \
+	../../include/struct.h ../../include/sys.h \
+	../../include/types.h \
+	../../include/version.h ../../include/whowas.h
+
+R_MODULES= \
+	rpc.so stats.so user.so server.so channel.so server_ban.so \
+	server_ban_exception.so name_ban.so spamfilter.so \
+	log.so whowas.so
+
+MODULES=$(R_MODULES)
+MODULEFLAGS=@MODULEFLAGS@
+RM=@RM@
+
+.SUFFIXES:
+.SUFFIXES: .c .h .so
+
+all: build
+
+build: $(MODULES)
+
+clean:
+	$(RM) -f *.o *.so *~ core
+
+%.so: %.c $(INCLUDES)
+	$(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \
+		-o $@ $<
diff --git a/src/modules/rpc/channel.c b/src/modules/rpc/channel.c
@@ -0,0 +1,230 @@
+/* channel.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/channel",
+	"1.0.5",
+	"channel.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_channel_list(Client *client, json_t *request, json_t *params);
+void rpc_channel_get(Client *client, json_t *request, json_t *params);
+void rpc_channel_set_mode(Client *client, json_t *request, json_t *params);
+void rpc_channel_set_topic(Client *client, json_t *request, json_t *params);
+void rpc_channel_kick(Client *client, json_t *request, json_t *params);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_channel_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_channel_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.set_mode";
+	r.call = rpc_channel_set_mode;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.set_topic";
+	r.call = rpc_channel_set_topic;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "channel.kick";
+	r.call = rpc_channel_kick;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/channel] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void rpc_channel_list(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *list, *item;
+	Channel *channel;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
+	if (details >= 5)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of >=5 is not allowed in this call");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (channel = channels; channel; channel=channel->nextch)
+	{
+		item = json_object();
+		json_expand_channel(item, NULL, channel, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_get(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname;
+	Channel *channel;
+	int details;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 3);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_channel(result, "channel", channel, details);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_set_mode(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *modes, *parameters;
+	MessageTag *mtags = NULL;
+	Channel *channel;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("modes", modes);
+	REQUIRE_PARAM_STRING("parameters", parameters);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	set_channel_mode(channel, mtags, modes, parameters);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_set_topic(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *topic, *set_by=NULL, *str;
+	Channel *channel;
+	time_t set_at = 0;
+	MessageTag *mtags = NULL;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("topic", topic);
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	OPTIONAL_PARAM_STRING("set_at", str);
+	if (str)
+		set_at = server_time_to_unix_time(str);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	set_channel_topic(&me, channel, mtags, topic, set_by, set_at);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void rpc_channel_kick(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *channelname, *nick, *reason;
+	MessageTag *mtags = NULL;
+	Channel *channel;
+	Client *acptr;
+	time_t set_at = 0;
+
+	REQUIRE_PARAM_STRING("channel", channelname);
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(channel = find_channel(channelname)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Channel not found");
+		return;
+	}
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+	kick_user(mtags, channel, &me, acptr, reason);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success
+	 * TODO: actually we can do a find_member() check and such to see if the user is kicked!
+	 * that is, assuming single channel ;)
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/log.c b/src/modules/rpc/log.c
@@ -0,0 +1,139 @@
+/* log.* RPC calls
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/log",
+	"1.0.0",
+	"log.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params);
+void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params);
+int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "log.subscribe";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_log_hook_subscribe;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/log] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "log.unsubscribe";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_log_hook_unsubscribe;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/log] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_LOG, 0, rpc_log_hook);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void rpc_log_hook_subscribe(Client *client, json_t *request, json_t *params)
+{
+	json_t *result;
+	json_t *sources;
+	size_t index;
+	json_t *value;
+	const char *str;
+	LogSource *s;
+
+	sources = json_object_get(params, "sources");
+	if (!sources || !json_is_array(sources))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "sources");
+		return;
+	}
+
+	/* Erase the old subscriptions first */
+	free_log_sources(client->rpc->log_sources);
+	client->rpc->log_sources = NULL;
+
+	/* Add subscriptions... */
+	json_array_foreach(sources, index, value)
+	{
+		str = json_get_value(value);
+		if (!str)
+			continue;
+
+		s = add_log_source(str);
+		AddListItem(s, client->rpc->log_sources);
+	}
+
+	result = json_boolean(1);
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** log.unsubscribe: unsubscribe from all log messages */
+void rpc_log_hook_unsubscribe(Client *client, json_t *request, json_t *params)
+{
+	json_t *result;
+
+	free_log_sources(client->rpc->log_sources);
+	client->rpc->log_sources = NULL;
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+int rpc_log_hook(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, json_t *json, const char *json_serialized, const char *timebuf)
+{
+	Client *client;
+	json_t *request = NULL;
+
+	if (!strcmp(subsystem, "rawtraffic"))
+		return 0;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (IsRPC(client) && client->rpc->log_sources &&
+		    log_sources_match(client->rpc->log_sources, loglevel, subsystem, event_id, 0))
+		{
+			if (request == NULL)
+			{
+				/* Lazy initalization */
+				request = json_object();
+				json_object_set_new(request, "method", json_string_unreal("log.event"));
+			}
+			rpc_response(client, request, json);
+		}
+	}
+
+	if (request)
+		json_decref(request);
+
+	return 0;
+}
diff --git a/src/modules/rpc/name_ban.c b/src/modules/rpc/name_ban.c
@@ -0,0 +1,241 @@
+/* name_ban.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/name_ban",
+	"1.0.1",
+	"name_ban.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_name_ban_list);
+RPC_CALL_FUNC(rpc_name_ban_get);
+RPC_CALL_FUNC(rpc_name_ban_del);
+RPC_CALL_FUNC(rpc_name_ban_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "name_ban.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_name_ban_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_name_ban_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.del";
+	r.call = rpc_name_ban_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "name_ban.add";
+	r.call = rpc_name_ban_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/name_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_name_ban_list)
+{
+	json_t *result, *list, *item;
+	int index;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsNameBan(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+TKL *my_find_tkl_nameban(const char *name)
+{
+	TKL *tkl;
+
+	for (tkl = tklines[tkl_hash('Q')]; tkl; tkl = tkl->next)
+	{
+		if (!TKLIsNameBan(tkl))
+			continue;
+		if (!strcasecmp(name, tkl->ptr.nameban->name))
+			return tkl;
+	}
+	return NULL;
+}
+
+RPC_CALL_FUNC(rpc_name_ban_get)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	TKL *tkl;
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	if (!(tkl = my_find_tkl_nameban(name)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_name_ban_del)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	const char *set_by;
+	TKL *tkl;
+	const char *tkllayer[7];
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if (!(tkl = my_find_tkl_nameban(name)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[0] = NULL;
+	tkllayer[1] = "-";
+	tkllayer[2] = "Q";
+	tkllayer[3] = "*";
+	tkllayer[4] = name;
+	tkllayer[5] = set_by;
+	tkllayer[6] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!my_find_tkl_nameban(name))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_name_ban_add)
+{
+	json_t *result, *list, *item;
+	const char *name;
+	const char *str;
+	const char *reason;
+	const char *set_by;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+	TKL *tkl;
+
+	REQUIRE_PARAM_STRING("name", name);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (my_find_tkl_nameban(name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban already exists");
+		return;
+	}
+
+	tkl = tkl_add_nameban(TKL_NAME|TKL_GLOBAL, name, 0, reason,
+	                      set_by, tkl_expire_at, tkl_set_at,
+	                      0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/rpc.c b/src/modules/rpc/rpc.c
@@ -0,0 +1,1922 @@
+/*
+ * RPC module - for remote management of UnrealIRCd
+ * (C)Copyright 2022 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+   
+#include "unrealircd.h"
+#include "dns.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"rpc/rpc",
+	"1.0.4",
+	"RPC module for remote management",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/** Maximum length of an rpc-user THIS { }.
+ * As we use the "RPC:" prefix it is nicklen minus that.
+ */
+#define RPCUSERLEN (NICKLEN-4)
+
+/** Timers can be minimum every <this> msec */
+#define RPC_MINIMUM_TIMER_MSEC 250
+
+/* Structs */
+typedef struct RPCUser RPCUser;
+struct RPCUser {
+	RPCUser *prev, *next;
+	SecurityGroup *match;
+	char *name;
+	AuthConfig *auth;
+};
+
+typedef struct RRPC RRPC;
+struct RRPC {
+	RRPC *prev, *next;
+	int request;
+	char source[IDLEN+1];
+	char destination[IDLEN+1];
+	char *requestid;
+	dbuf data;
+};
+
+typedef struct OutstandingRRPC OutstandingRRPC;
+struct OutstandingRRPC {
+	OutstandingRRPC *prev, *next;
+	time_t sent;
+	char source[IDLEN+1];
+	char destination[IDLEN+1];
+	char *requestid;
+};
+
+typedef struct RPCTimer RPCTimer;
+struct RPCTimer {
+	RPCTimer *prev, *next;
+	long every_msec;
+	Client *client;
+	char *timer_id;
+	json_t *request;
+	struct timeval last_run;
+};
+
+/* Forward declarations */
+int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
+int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type);
+int rpc_client_accept(Client *client);
+int rpc_pre_local_handshake_timeout(Client *client, const char **comment);
+void rpc_client_handshake_unix_socket(Client *client);
+void rpc_client_handshake_web(Client *client);
+int rpc_handle_webrequest(Client *client, WebRequest *web);
+int rpc_handle_webrequest_websocket(Client *client, WebRequest *web);
+int rpc_websocket_handshake_send_response(Client *client);
+int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len);
+int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
+int rpc_packet_in_websocket(Client *client, char *readbuf, int length);
+int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length);
+void rpc_call_text(Client *client, const char *buf, int len);
+void rpc_call(Client *client, json_t *request);
+void _rpc_response(Client *client, json_t *request, json_t *result);
+void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message);
+void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5)));
+void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request);
+void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response);
+int _rrpc_supported_simple(Client *target, char **problem_server);
+int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server);
+int rpc_handle_auth(Client *client, WebRequest *web);
+int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password);
+int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password);
+RPC_CALL_FUNC(rpc_rpc_info);
+RPC_CALL_FUNC(rpc_rpc_set_issuer);
+RPC_CALL_FUNC(rpc_rpc_add_timer);
+RPC_CALL_FUNC(rpc_rpc_del_timer);
+CMD_FUNC(cmd_rrpc);
+EVENT(rpc_remote_timeout);
+EVENT(rpc_do_timers);
+json_t *rrpc_data(RRPC *r);
+void free_rrpc_list(ModData *m);
+void free_outstanding_rrpc_list(ModData *m);
+void free_rpc_timer(RPCTimer *r);
+void free_rpc_timer_list(ModData *m);
+void rpc_call_remote(RRPC *r);
+void rpc_response_remote(RRPC *r);
+int rpc_handle_free_client(Client *client);
+int rpc_handle_server_quit(Client *client, MessageTag *mtags);
+int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child);
+const char *rrpc_md_serialize(ModData *m);
+void rrpc_md_unserialize(const char *str, ModData *m);
+void rrpc_md_free(ModData *m);
+
+/* Macros */
+#define RPC_PORT(client)  ((client->local && client->local->listener) ? client->local->listener->rpc_options : 0)
+#define WSU(client)     ((WebSocketUser *)moddata_client(client, websocket_md).ptr)
+
+/* Global variables */
+ModDataInfo *websocket_md = NULL; /* (imported) */
+RPCUser *rpcusers = NULL;
+RRPC *rrpc_list = NULL;
+OutstandingRRPC *outstanding_rrpc_list = NULL;
+RPCTimer *rpc_timer_list = NULL;
+ModDataInfo *rrpc_md;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_listen);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test_rpc_user);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_RESPONSE, _rpc_response);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR, _rpc_error);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR_FMT, TO_VOIDFUNC(_rpc_error_fmt));
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_REQUEST_TO_REMOTE, _rpc_send_request_to_remote);
+	EfunctionAddVoid(modinfo->handle, EFUNC_RPC_SEND_RESPONSE_TO_REMOTE, _rpc_send_response_to_remote);
+	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED, _rrpc_supported);
+	EfunctionAdd(modinfo->handle, EFUNC_RRPC_SUPPORTED_SIMPLE, _rrpc_supported_simple);
+
+	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT); /* can be NULL */
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, rpc_config_run_ex_listen);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rpc_config_run_rpc_user);
+	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, -5000, rpc_client_accept);
+	HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_HANDSHAKE_TIMEOUT, 0, rpc_pre_local_handshake_timeout);
+	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, rpc_packet_in_unix_socket);
+	HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, rpc_handle_server_quit);
+	HookAdd(modinfo->handle, HOOKTYPE_FREE_CLIENT, 0, rpc_handle_free_client);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, 0, rpc_json_expand_client_server);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.info";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_info;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.info] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.set_issuer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_set_issuer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.set_issuer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.add_timer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_add_timer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.add_timer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&r, 0, sizeof(r));
+	r.method = "rpc.del_timer";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_rpc_del_timer;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc.del_timer] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "rrpc";
+	mreq.type = MODDATATYPE_CLIENT;
+	mreq.serialize = rrpc_md_serialize;
+	mreq.unserialize = rrpc_md_unserialize;
+	mreq.free = rrpc_md_free;
+	mreq.sync = 1;
+	mreq.self_write = 1;
+	rrpc_md = ModDataAdd(modinfo->handle, mreq);
+	if (!rrpc_md)
+	{
+		config_error("[rpc/rpc] Unable to ModDataAdd() -- too many 3rd party modules loaded perhaps?");
+		abort();
+	}
+
+	LoadPersistentPointer(modinfo, rrpc_list, free_rrpc_list);
+	LoadPersistentPointer(modinfo, outstanding_rrpc_list, free_outstanding_rrpc_list);
+	LoadPersistentPointer(modinfo, rpc_timer_list, free_rpc_timer_list);
+
+	CommandAdd(modinfo->handle, "RRPC", cmd_rrpc, MAXPARA, CMD_SERVER);
+
+	EventAdd(modinfo->handle, "rpc_remote_timeout", rpc_remote_timeout, NULL, 1000, 0);
+	EventAdd(modinfo->handle, "rpc_do_timers", rpc_do_timers, NULL, RPC_MINIMUM_TIMER_MSEC, 0);
+
+	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
+
+	return MOD_SUCCESS;
+}
+
+#define MYRRPCMODULES		me.moddata[rrpc_md->slot].ptr
+#define RRPCMODULES(client)	((NameValuePrioList *)moddata_client(client, rrpc_md).ptr)
+
+void rpc_do_moddata(void)
+{
+	Module *m;
+
+	free_nvplist(MYRRPCMODULES);
+	MYRRPCMODULES = NULL;
+
+	for (m = Modules; m; m = m->next)
+		if (!strncmp(m->header->name, "rpc/", 4))
+			add_nvplist((NameValuePrioList **)&MYRRPCMODULES, 0, m->header->name + 4, m->header->version);
+}
+
+MOD_LOAD()
+{
+	rpc_do_moddata();
+	return MOD_SUCCESS;
+}
+
+void free_config(void)
+{
+	RPCUser *e, *e_next;
+	for (e = rpcusers; e; e = e_next)
+	{
+		e_next = e->next;
+		safe_free(e->name);
+		free_security_group(e->match);
+		Auth_FreeAuthConfig(e->auth);
+		safe_free(e);
+	}
+	rpcusers = NULL;
+}
+
+MOD_UNLOAD()
+{
+	free_config();
+	SavePersistentPointer(modinfo, rrpc_list);
+	SavePersistentPointer(modinfo, outstanding_rrpc_list);
+	SavePersistentPointer(modinfo, rpc_timer_list);
+	return MOD_SUCCESS;
+}
+
+int rpc_config_test_listen(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	int ext = 0;
+	ConfigEntry *cep;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interested in listen::options::rpc.. */
+	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
+		return 0;
+
+	/* No options atm */
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int rpc_config_run_ex_listen(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
+{
+	ConfigEntry *cep, *cepp;
+	ConfigItem_listen *l;
+
+	if (type != CONFIG_LISTEN_OPTIONS)
+		return 0;
+
+	/* We are only interrested in listen::options::rpc.. */
+	if (!ce || !ce->name || strcmp(ce->name, "rpc"))
+		return 0;
+
+	l = (ConfigItem_listen *)ptr;
+	l->options |= LISTENER_NO_CHECK_CONNECT_FLOOD;
+	if (l->socket_type == SOCKET_TYPE_UNIX)
+	{
+		l->start_handshake = rpc_client_handshake_unix_socket;
+	} else {
+		l->options |= LISTENER_TLS;
+		l->start_handshake = rpc_client_handshake_web;
+		l->webserver = safe_alloc(sizeof(WebServer));
+		l->webserver->handle_request = rpc_handle_webrequest;
+		l->webserver->handle_body = rpc_handle_webrequest_data;
+	}
+	l->rpc_options = 1;
+
+	return 1;
+}
+
+/** Valid name for rpc-user THISNAME { } ? */
+static int valid_rpc_user_name(const char *str)
+{
+	const char *p;
+
+	if (strlen(str) > RPCUSERLEN)
+		return 0;
+
+	for (p = str; *p; p++)
+		if (!isalnum(*p) && !strchr("_-", *p))
+			return 0;
+
+	return 1;
+}
+
+int rpc_config_test_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	char has_match = 1, has_password = 1;
+	ConfigEntry *cep;
+
+	/* We are only interested in rpc-user { } */
+	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
+		return 0;
+
+	if (!ce->value)
+	{
+		config_error("%s:%d: rpc-user block needs to have a name, eg: rpc-user apiuser { }",
+		             ce->file->filename, ce->line_number);
+		*errs = 1;
+		return -1; /* quick return */
+	}
+
+	if (!valid_rpc_user_name(ce->value))
+	{
+		config_error("%s:%d: rpc-user block has invalid name '%s'. "
+		             "Can be max %d long and may only contain a-z, A-Z, 0-9, - and _.",
+		             ce->file->filename, ce->line_number,
+		             ce->value, RPCUSERLEN);
+		errors++;
+	}
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "match"))
+		{
+			has_match = 1;
+			test_match_block(cf, cep, &errors);
+		} else
+		if (!strcmp(cep->name, "password"))
+		{
+			has_password = 1;
+			if (Auth_CheckError(cep) < 0)
+				errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int rpc_config_run_rpc_user(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+	RPCUser *e;
+
+	/* We are only interested in rpc-user { } */
+	if ((type != CONFIG_MAIN) || !ce || !ce->name || strcmp(ce->name, "rpc-user"))
+		return 0;
+
+	e = safe_alloc(sizeof(RPCUser));
+	safe_strdup(e->name, ce->value);
+	AddListItem(e, rpcusers);
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "match"))
+		{
+			conf_match_block(cf, cep, &e->match);
+		} else
+		if (!strcmp(cep->name, "password"))
+		{
+			e->auth = AuthBlockToAuthConfig(cep);
+		}
+	}
+	return 1;
+}
+
+/** Incoming HTTP request: delegate it to websocket handler or HTTP POST */
+int rpc_handle_webrequest(Client *client, WebRequest *web)
+{
+	if (!rpc_handle_auth(client, web))
+		return 0; /* rejected */
+
+	if (get_nvplist(web->headers, "Sec-WebSocket-Key"))
+		return rpc_handle_webrequest_websocket(client, web);
+
+	if (!strcmp(web->uri, "/api"))
+	{
+		if (web->method != HTTP_METHOD_POST)
+		{
+			webserver_send_response(client, 200, "To use the UnrealIRCd RPC API you need to make a POST request. See https://www.unrealircd.org/docs/RPC\n");
+			return 0;
+		}
+		webserver_send_response(client, 200, NULL); /* continue.. */
+		return 1; /* accept */
+	}
+
+	webserver_send_response(client, 404, "Page not found.\n");
+	return 0;
+}
+
+/** Handle HTTP request - websockets handshake.
+ */
+int rpc_handle_webrequest_websocket(Client *client, WebRequest *web)
+{
+	NameValuePrioList *r;
+	const char *value;
+
+	if (!websocket_md)
+	{
+		webserver_send_response(client, 405, "Websockets are disabled on this server (module 'websocket_common' not loaded).\n");
+		return 0;
+	}
+
+	/* Allocate a new WebSocketUser struct for this session */
+	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
+	/* ...and set the default protocol (text or binary) */
+	WSU(client)->type = WEBSOCKET_TYPE_TEXT;
+
+	value = get_nvplist(web->headers, "Sec-WebSocket-Key");
+	if (strchr(value, ':'))
+	{
+		/* This would cause unserialization issues. Should be base64 anyway */
+		webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
+		return 0; // FIXME: 0 here, -1 in the other, what is it ???
+	}
+	safe_strdup(WSU(client)->handshake_key, value);
+
+	rpc_websocket_handshake_send_response(client);
+	return 1; /* ACCEPT */
+}
+
+/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
+int rpc_websocket_handshake_send_response(Client *client)
+{
+	char buf[512], hashbuf[64];
+	char sha1out[20]; /* 160 bits */
+
+	WSU(client)->handshake_completed = 1;
+
+	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
+	sha1hash_binary(sha1out, buf, strlen(buf));
+	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
+
+	snprintf(buf, sizeof(buf),
+	         "HTTP/1.1 101 Switching Protocols\r\n"
+	         "Upgrade: websocket\r\n"
+	         "Connection: Upgrade\r\n"
+	         "Sec-WebSocket-Accept: %s\r\n\r\n",
+	         hashbuf);
+
+	/* Caution: we bypass sendQ flood checking by doing it this way.
+	 * Risk is minimal, though, as we only permit limited text only
+	 * once per session.
+	 */
+	dbuf_put(&client->local->sendQ, buf, strlen(buf));
+	send_queued(client);
+
+	return 0;
+}
+
+int rpc_handle_webrequest_data(Client *client, WebRequest *web, const char *buf, int len)
+{
+	if (WSU(client))
+	{
+		/* Websocket user */
+		return rpc_handle_body_websocket(client, web, buf, len);
+	}
+
+	/* We only handle POST to /api -- reject all the rest */
+	if (strcmp(web->uri, "/api") || (web->method != HTTP_METHOD_POST))
+	{
+		webserver_send_response(client, 404, "Page not found\n");
+		return 0;
+	}
+
+	// NB: content_length
+	// NB: chunked transfers?
+	if (!webserver_handle_body(client, web, buf, len))
+	{
+		webserver_send_response(client, 400, "Error handling POST body data\n");
+		return 0;
+	}
+
+
+	if (web->request_body_complete)
+	{
+		if (!web->request_buffer)
+		{
+			webserver_send_response(client, 500, "Error while processing POST body data\n");
+			return 0;
+		}
+		//config_status("GOT: '%s'", buf);
+		rpc_call_text(client, web->request_buffer, web->request_buffer_size);
+		send_queued(client);
+		webserver_close_client(client);
+	}
+
+	return 0;
+}
+
+int rpc_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
+{
+	return websocket_handle_websocket(client, web, readbuf2, length2, rpc_packet_in_websocket);
+}
+
+int rpc_packet_in_websocket(Client *client, char *readbuf, int length)
+{
+	rpc_call_text(client, readbuf, length);
+	return 0; /* and if dead?? */
+}
+
+int rpc_packet_in_unix_socket(Client *client, const char *readbuf, int *length)
+{
+	char buf[READBUFSIZE];
+
+	if (!RPC_PORT(client) || !(client->local->listener->socket_type == SOCKET_TYPE_UNIX) || (*length <= 0))
+		return 1; /* Not for us */
+
+	dbuf_put(&client->local->recvQ, readbuf, *length);
+
+	while (DBufLength(&client->local->recvQ))
+	{
+		int len = dbuf_getmsg(&client->local->recvQ, buf);
+		if (len <= 0)
+			break;
+		rpc_call_text(client, buf, len);
+		if (IsDead(client))
+			break;
+	}
+
+	return 0;
+}
+
+void rpc_close(Client *client)
+{
+	send_queued(client);
+
+	/* May not be a web request actually, but this works: */
+	webserver_close_client(client);
+}
+
+/** Handle the RPC request: input is a buffer with a certain length.
+ * This calls rpc_call()
+ */
+void rpc_call_text(Client *client, const char *readbuf, int len)
+{
+	json_t *request = NULL;
+	json_error_t jerr;
+#if JANSSON_VERSION_HEX >= 0x020100
+	const char *buf = readbuf;
+	request = json_loadb(buf, len, JSON_REJECT_DUPLICATES, &jerr);
+#else
+	char buf[2048];
+
+	*buf = '\0';
+	strlncpy(buf, readbuf, sizeof(buf), len);
+
+	request = json_loads(buf, JSON_REJECT_DUPLICATES, &jerr);
+#endif
+	if (!request)
+	{
+		unreal_log(ULOG_INFO, "rpc", "RPC_INVALID_JSON", client,
+		           "Received unparsable JSON request from $client",
+		           log_data_string("json_incoming", buf));
+		rpc_error(client, NULL, JSON_RPC_ERROR_PARSE_ERROR, "Unparsable JSON data");
+		/* This is a fatal error */
+		rpc_close(client);
+		return;
+	}
+	rpc_call(client, request);
+	json_decref(request);
+}
+
+void rpc_sendto(Client *client, const char *buf, int len)
+{
+	if (IsDead(client))
+		return;
+	if (MyConnect(client) && IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
+	{
+		/* Websocket */
+		int utf8bufsize = len*2 + 16;
+		char *utf8buf = safe_alloc(utf8bufsize);
+		char *newbuf = unrl_utf8_make_valid(buf, utf8buf, utf8bufsize, 1);
+		int newlen = strlen(newbuf);
+		int ws_sendbufsize = newlen + 64 + ((newlen / 1024) * 64); // some random magic
+		char *ws_sendbuf = safe_alloc(ws_sendbufsize);
+		websocket_create_packet_ex(WSOP_TEXT, &newbuf, &newlen, ws_sendbuf, ws_sendbufsize);
+		dbuf_put(&client->local->sendQ, newbuf, newlen);
+		safe_free(ws_sendbuf);
+		safe_free(utf8buf);
+	} else {
+		/* Unix domain socket or HTTP */
+		dbuf_put(&client->local->sendQ, buf, len);
+		dbuf_put(&client->local->sendQ, "\n", 1);
+	}
+	mark_data_to_send(client);
+}
+
+void _rpc_error(Client *client, json_t *request, JsonRpcError error_code, const char *error_message)
+{
+	/* Careful, we are in the "error" routine, so everything can be NULL */
+	const char *method = NULL;
+	json_t *id = NULL;
+	char *json_serialized;
+	json_t *error;
+
+	/* Start a new object for the error response */
+	json_t *j = json_object();
+
+	if (request)
+	{
+		method = json_object_get_string(request, "method");
+		id = json_object_get(request, "id");
+	}
+
+	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
+	if (method)
+		json_object_set_new(j, "method", json_string_unreal(method));
+	if (id)
+		json_object_set(j, "id", id);
+
+	error = json_object();
+	json_object_set_new(j, "error", error);
+	json_object_set_new(error, "code", json_integer(error_code));
+	json_object_set_new(error, "message", json_string_unreal(error_message));
+
+	unreal_log(ULOG_INFO, "rpc", "RPC_CALL_ERROR", client,
+	           "[rpc] Client $client: RPC call $method",
+	           log_data_string("method", method ? method : "<invalid>"));
+
+
+	json_serialized = json_dumps(j, 0);
+	if (!json_serialized)
+	{
+		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_ERROR_SERIALIZE_FAILED", NULL,
+		           "[BUG] rpc_error() failed to serialize response "
+		           "for request from $client ($method)",
+		           log_data_string("method", method));
+		json_decref(j);
+		return;
+	}
+
+	if (MyConnect(client))
+		rpc_sendto(client, json_serialized, strlen(json_serialized));
+	else
+		rpc_send_response_to_remote(&me, client, j);
+
+#ifdef DEBUGMODE
+	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+		   "[rpc] Client $client: RPC result error: $response",
+		   log_data_string("response", json_serialized));
+#endif
+	json_decref(j);
+	safe_free(json_serialized);
+}
+
+void _rpc_error_fmt(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...)
+{
+	char buf[512];
+
+	va_list vl;
+	va_start(vl, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, vl);
+	va_end(vl);
+	rpc_error(client, request, error_code, buf);
+}
+
+void _rpc_response(Client *client, json_t *request, json_t *result)
+{
+	const char *method = json_object_get_string(request, "method");
+	json_t *id = json_object_get(request, "id");
+	char *json_serialized;
+	json_t *j = json_object();
+
+	json_object_set_new(j, "jsonrpc", json_string_unreal("2.0"));
+	json_object_set_new(j, "method", json_string_unreal(method));
+	if (id)
+		json_object_set(j, "id", id); /* 'id' is optional */
+	json_object_set(j, "result", result);
+
+	json_serialized = json_dumps(j, 0);
+	if (!json_serialized)
+	{
+		unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_RESPONSE_SERIALIZE_FAILED", NULL,
+		           "[BUG] rpc_response() failed to serialize response "
+		           "for request from $client ($method)",
+		           log_data_string("method", method));
+		json_decref(j);
+		return;
+	}
+
+	if (MyConnect(client))
+		rpc_sendto(client, json_serialized, strlen(json_serialized));
+	else
+		rpc_send_response_to_remote(&me, client, j);
+
+#ifdef DEBUGMODE
+	unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+		   "[rpc] Client $client: RPC response result: $response",
+		   log_data_string("response", json_serialized));
+#endif
+	json_decref(j);
+	safe_free(json_serialized);
+}
+
+int sanitize_params_actual(Client *client, json_t *request, const char *str)
+{
+	if (!str)
+		return 1;
+
+	if (strlen(str) > 510)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings cannot be longer than 510 characters in the request");
+		return 0;
+	}
+
+	if (strchr(str, '\n') || strchr(str, '\r'))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Strings may not contain \n or \r in the request");
+		return 0;
+	}
+
+	return 1;
+}
+
+int sanitize_params(Client *client, json_t *request, json_t *j)
+{
+	/* Check the current object itself */
+	const char *str = json_string_value(j);
+	if (str && !sanitize_params_actual(client, request, str))
+		return 0;
+
+	/* Now walk through the object, if needed */
+
+	if (json_is_array(j))
+	{
+		size_t index;
+		json_t *value;
+		json_array_foreach(j, index, value)
+		{
+			if (!sanitize_params(client, request, value))
+				return 0;
+		}
+	} else
+	if (json_is_object(j))
+	{
+		const char *key;
+		json_t *value;
+		json_object_foreach(j, key, value)
+		{
+			if (!sanitize_params_actual(client, request, key))
+				return 0;
+			if (!sanitize_params(client, request, value))
+				return 0;
+		}
+	}
+
+	return 1;
+}
+
+/** Log the RPC request */
+void rpc_call_log(Client *client, RPCHandler *handler, json_t *request, const char *method, json_t *params)
+{
+	const char *key;
+	json_t *value_object;
+	char params_string[512], tbuf[256];
+
+	*params_string = '\0';
+	json_object_foreach(params, key, value_object)
+	{
+		const char *value = json_get_value(value_object);
+		if (value)
+		{
+			snprintf(tbuf, sizeof(tbuf), "%s='%s', ", key, value);
+			strlcat(params_string, tbuf, sizeof(params_string));
+		}
+	}
+	if (*params_string)
+		params_string[strlen(params_string)-2] = '\0'; /* cut off last comma */
+
+	// TODO: pass log_data_json() or something, pass the entire 'request' ? For JSON logging
+
+	if (client->rpc && client->rpc->issuer)
+	{
+		if (*params_string)
+		{
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client ($issuer): $params_string",
+				   log_data_string("issuer", client->rpc->issuer),
+				   log_data_string("method", method),
+				   log_data_string("params_string", params_string));
+		} else {
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client ($issuer)",
+				   log_data_string("issuer", client->rpc->issuer),
+				   log_data_string("method", method));
+		}
+	} else {
+		if (*params_string)
+		{
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client: $params_string",
+				   log_data_string("method", method),
+				   log_data_string("params_string", params_string));
+		} else {
+			unreal_log(handler->loglevel, "rpc", "RPC_CALL", client,
+				   "[rpc] RPC call $method by $client",
+				   log_data_string("method", method));
+		}
+	}
+}
+
+/** Parse an RPC request, except that it does not validate 'params'.
+ * @param client	The client issuing the request
+ * @param mainrequest	The underlying request that we should send errors to (usually same as 'request')
+ * @param request	The request that needs parsing
+ * @param method	This will be filled in if successfully parsed
+ * @param handler	This will be filled in if the handler is found
+ * @retval 0		An error occured while parsing or the method was not found.
+ * @retval 1		All good. You still need to validate 'params', though.
+ */
+int parse_rpc_call(Client *client, json_t *mainrequest, json_t *request, const char **method, RPCHandler **handler)
+{
+	const char *jsonrpc;
+	json_t *id;
+	const char *str;
+
+	*method = NULL;
+	*handler = NULL;
+
+	jsonrpc = json_object_get_string(request, "jsonrpc");
+	if (!jsonrpc || strcasecmp(jsonrpc, "2.0"))
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Only JSON-RPC version 2.0 is supported");
+		return 0;
+	}
+
+	id = json_object_get(request, "id");
+	if (!id)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'id'");
+		return 0;
+	}
+
+	if ((str = json_string_value(id)))
+	{
+		if (strlen(str) > 32)
+		{
+			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' cannot be longer than 32 characters in UnrealIRCd JSON-RPC");
+			return 0;
+		}
+		if (strchr(str, '\n') || strchr(str, '\r'))
+		{
+			rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' may not contain \n or \r in UnrealIRCd JSON-RPC");
+			return 0;
+		}
+	} else if (!json_is_integer(id))
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
+		return 0;
+	}
+
+	*method = json_object_get_string(request, "method");
+	if (!*method)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'method' to call");
+		return 0;
+	}
+
+	*handler = RPCHandlerFind(*method);
+	if (!*handler)
+	{
+		rpc_error(client, mainrequest, JSON_RPC_ERROR_METHOD_NOT_FOUND, "Unsupported method");
+		return 0;
+	}
+
+	return 1;
+}
+
+/** Handle the RPC request: request is in JSON */
+void rpc_call(Client *client, json_t *request)
+{
+	const char *method;
+	json_t *params;
+	RPCHandler *handler;
+
+	if (!parse_rpc_call(client, request, request, &method, &handler))
+		return; /* Error already returned to caller */
+
+	params = json_object_get(request, "params");
+	if (params)
+	{
+		if (!(handler->flags & RPC_HANDLER_FLAGS_UNFILTERED) &&
+		    !sanitize_params(client, request, params))
+		{
+			return;
+		}
+	} else
+	{
+		/* Params is optional, so create an empty params object instead
+		 * to make life easier of the RPC handlers (no need to check NULL).
+		 */
+		params = json_object();
+		json_object_set_new(request, "params", params);
+	}
+
+	rpc_call_log(client, handler, request, method, params);
+
+#ifdef DEBUGMODE
+	{
+		char *call = json_dumps(request, 0);
+		if (call)
+		{
+			unreal_log(ULOG_DEBUG, "rpc", "RPC_CALL_DEBUG", client,
+				   "[rpc] Client $client: RPC call: $call",
+				   log_data_string("call", call));
+			safe_free(call);
+		}
+	}
+#endif
+	handler->call(client, request, params);
+}
+
+/** Called very early on accept() of the socket, before TLS is ready */
+int rpc_client_accept(Client *client)
+{
+	if (RPC_PORT(client))
+	{
+		SetRPC(client);
+		client->rpc = safe_alloc(sizeof(RPCClient));
+	}
+	return 0;
+}
+
+/** Called upon handshake of unix socket (direct JSON usage, no auth) */
+void rpc_client_handshake_unix_socket(Client *client)
+{
+	if (client->local->listener->socket_type != SOCKET_TYPE_UNIX)
+		abort(); /* impossible */
+
+	strlcpy(client->name, "RPC:local", sizeof(client->name));
+	SetRPC(client);
+	client->rpc = safe_alloc(sizeof(RPCClient));
+	safe_strdup(client->rpc->rpc_user, "<local>");
+
+	/* Allow incoming data to be read from now on.. */
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+/** Called upon handshake, after TLS is ready (before any HTTP header parsing) */
+void rpc_client_handshake_web(Client *client)
+{
+	RPCUser *r;
+	char found = 0;
+
+	/* Explicitly mark as RPC, since the TLS layer may
+	 * have set us to SetUnknown() after the TLS handshake.
+	 */
+	SetRPC(client);
+	if (!client->rpc)
+		client->rpc = safe_alloc(sizeof(RPCClient));
+
+	/* Is the client allowed by any rpc-user { } block?
+	 * If not, reject the client immediately, before
+	 * processing any HTTP data.
+	 */
+	for (r = rpcusers; r; r = r->next)
+	{
+		if (user_allowed_by_security_group(client, r->match))
+		{
+			found = 1;
+			break;
+		}
+	}
+	if (!found)
+	{
+		webserver_send_response(client, 401, "Access denied");
+		return;
+	}
+
+	/* Allow incoming data to be read from now on.. */
+	fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
+}
+
+#define RPC_WEBSOCKET_PING_TIME 120
+
+int rpc_pre_local_handshake_timeout(Client *client, const char **comment)
+{
+	/* Don't hang up websocket connections */
+	if (IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
+	{
+		long t = TStime() - client->local->last_msg_received;
+		if ((t > RPC_WEBSOCKET_PING_TIME*2) && IsPingSent(client))
+		{
+			*comment = "No websocket PONG received in time.";
+			return HOOK_CONTINUE;
+		} else
+		if ((t > RPC_WEBSOCKET_PING_TIME) && !IsPingSent(client) && !IsDead(client))
+		{
+			char pingbuf[4];
+			const char *pkt = pingbuf;
+			int pktlen = sizeof(pingbuf);
+			pingbuf[0] = 0x11;
+			pingbuf[1] = 0x22;
+			pingbuf[2] = 0x33;
+			pingbuf[3] = 0x44;
+			websocket_create_packet_simple(WSOP_PING, &pkt, &pktlen);
+			dbuf_put(&client->local->sendQ, pkt, pktlen);
+			send_queued(client);
+			SetPingSent(client);
+		}
+		return HOOK_ALLOW; /* prevent closing the connection due to timeout */
+	}
+
+	return HOOK_CONTINUE;
+}
+
+RPCUser *find_rpc_user(const char *username)
+{
+	RPCUser *r;
+	for (r = rpcusers; r; r = r->next)
+		if (!strcmp(r->name, username))
+			return r;
+	return NULL;
+}
+
+/** This function deals with authentication after the HTTP request was received.
+ * It is called for both ordinary HTTP(S) requests and Websockets.
+ * Note that there has also been some pre-filtering done in rpc_client_handshake()
+ * to see if the IP address was allowed to connect at all (::match),
+ * but here we actually check the 'correct' rpc-user { } block.
+ * @param client	The client to authenticate
+ * @param web		The webrequest (containing the headers)
+ * @return 1 on success, 0 on failure
+ */
+int rpc_handle_auth(Client *client, WebRequest *web)
+{
+	char *username = NULL, *password = NULL;
+	RPCUser *r;
+
+	if (!rpc_parse_auth_basic_auth(client, web, &username, &password) &&
+	    !rpc_parse_auth_uri(client, web, &username, &password))
+	{
+		webserver_send_response(client, 401, "Authentication required");
+		return 0;
+	}
+
+	if (username && password && ((r = find_rpc_user(username))))
+	{
+		if (user_allowed_by_security_group(client, r->match) &&
+		    Auth_Check(client, r->auth, password))
+		{
+			/* Authenticated! */
+			snprintf(client->name, sizeof(client->name), "RPC:%s", r->name);
+			safe_strdup(client->rpc->rpc_user, r->name);
+			return 1;
+		}
+	}
+
+	/* Authentication failed */
+	webserver_send_response(client, 401, "Authentication required");
+	return 0;
+}
+
+int rpc_parse_auth_basic_auth(Client *client, WebRequest *web, char **username, char **password)
+{
+	const char *auth_header = get_nvplist(web->headers, "Authorization");
+	static char buf[512];
+	char *p;
+	int n;
+
+	if (!auth_header)
+		return 0;
+
+	/* We only support basic auth */
+	if (strncasecmp(auth_header, "Basic ", 6))
+		return 0;
+
+	p = strchr(auth_header, ' ');
+	skip_whitespace(&p);
+	n = b64_decode(p, buf, sizeof(buf)-1);
+	if (n <= 1)
+		return 0;
+	buf[n] = '\0';
+
+	p = strchr(buf, ':');
+	if (!p)
+		return 0;
+	*p++ = '\0';
+
+	*username = buf;
+	*password = p;
+	return 1;
+}
+
+// TODO: the ?a=b&c=d stuff should be urldecoded by 'webserver'
+int rpc_parse_auth_uri(Client *client, WebRequest *web, char **username, char **password)
+{
+	static char buf[2048];
+	char *str, *p;
+
+	if (!web->uri)
+		return 0;
+
+	strlcpy(buf, web->uri, sizeof(buf));
+	str = strstr(buf, "username=");
+	if (!str)
+		return 0;
+	str += 9;
+	*username = str;
+	p = strchr(str, '&');
+	if (p)
+	{
+		*p++ = '\0';
+		p = strstr(p, "password=");
+		if (p)
+		{
+			p += 9;
+			*password = p;
+			p = strchr(str, '&');
+			if (p)
+				*p = '\0';
+		}
+	}
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_rpc_info)
+{
+	json_t *result, *methods, *item;
+	RPCHandler *r;
+
+	result = json_object();
+	methods = json_object();
+	json_object_set_new(result, "methods", methods);
+
+	for (r = rpchandlers; r; r = r->next)
+	{
+		item = json_object();
+		json_object_set_new(item, "name", json_string_unreal(r->method));
+		if (r->owner)
+		{
+			json_object_set_new(item, "module", json_string_unreal(r->owner->header->name));
+			json_object_set_new(item, "version", json_string_unreal(r->owner->header->version));
+		}
+		json_object_set_new(methods, r->method, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_rpc_set_issuer)
+{
+	json_t *result;
+	const char *name;
+	char buf[512];
+
+	REQUIRE_PARAM_STRING("name", name);
+
+	/* Do some validation on the name */
+	strlcpy(buf, name, sizeof(buf));
+	if (!do_remote_nick_name(buf) || strcmp(buf, name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME,
+		          "The 'name' contains illegal characters or is too long. "
+		          "The same rules as for nick names apply.");
+		return;
+	}
+
+	safe_strdup(client->rpc->issuer, name);
+
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void free_rrpc(RRPC *r)
+{
+	safe_free(r->requestid);
+	DBufClear(&r->data);
+	DelListItem(r, rrpc_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rrpc_list(ModData *m)
+{
+	RRPC *r, *r_next;
+
+	for (r = rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_rrpc(r);
+	}
+}
+
+void free_outstanding_rrpc(OutstandingRRPC *r)
+{
+	safe_free(r->requestid);
+	DelListItem(r, outstanding_rrpc_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_outstanding_rrpc_list(ModData *m)
+{
+	OutstandingRRPC *r, *r_next;
+
+	for (r = outstanding_rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_outstanding_rrpc(r);
+	}
+}
+
+/** Remove timer from rpc_timer_list and free it */
+void free_rpc_timer(RPCTimer *r)
+{
+	safe_free(r->timer_id);
+	json_decref(r->request);
+	DelListItem(r, rpc_timer_list);
+	safe_free(r);
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rpc_timer_list(ModData *m)
+{
+	RPCTimer *r, *r_next;
+
+	for (r = rpc_timer_list; r; r = r_next)
+	{
+		r_next = r->next;
+		free_rpc_timer(r);
+	}
+}
+
+/* Admin unloading the RPC module for good (not called on rehash) */
+void free_rpc_timers_for_user(Client *client)
+{
+	RPCTimer *r, *r_next;
+
+	for (r = rpc_timer_list; r; r = r_next)
+	{
+		r_next = r->next;
+		if (r->client == client)
+			free_rpc_timer(r);
+	}
+}
+
+RPCTimer *find_rpc_timer(Client *client, const char *timer_id)
+{
+	RPCTimer *r;
+
+	for (r = rpc_timer_list; r; r = r->next)
+	{
+		if ((r->client == client) && !strcmp(timer_id, r->timer_id))
+			return r;
+	}
+	return NULL;
+}
+
+/** When a server quits, cancel all the RPC requests to/from those clients */
+int rpc_handle_server_quit(Client *client, MessageTag *mtags)
+{
+	RRPC *r, *r_next;
+	OutstandingRRPC *or, *or_next;
+
+	for (r = rrpc_list; r; r = r_next)
+	{
+		r_next = r->next;
+		if (!strncmp(client->id, r->source, SIDLEN) ||
+		    !strncmp(client->id, r->destination, SIDLEN))
+		{
+			free_rrpc(r);
+		}
+	}
+
+	for (or = outstanding_rrpc_list; or; or = or_next)
+	{
+		or_next = or->next;
+		if (!strcmp(client->id, or->destination))
+		{
+			Client *client = find_client(or->source, NULL);
+			if (client)
+			{
+				json_t *j = json_object();
+				json_object_set_new(j, "id", json_string_unreal(or->requestid));
+				rpc_error(client, NULL, JSON_RPC_ERROR_SERVER_GONE, "Remote server disconnected while processing the request");
+				json_decref(j);
+			}
+			free_outstanding_rrpc(or);
+		}
+	}
+
+	return 0;
+}
+
+EVENT(rpc_remote_timeout)
+{
+	OutstandingRRPC *or, *or_next;
+	time_t deadline = TStime() - 15;
+
+	for (or = outstanding_rrpc_list; or; or = or_next)
+	{
+		or_next = or->next;
+		if (or->sent < deadline)
+		{
+			Client *client = find_client(or->source, NULL);
+			if (client)
+			{
+				json_t *request = json_object();
+				json_object_set_new(request, "id", json_string_unreal(or->requestid));
+				rpc_error(client, request, JSON_RPC_ERROR_TIMEOUT, "Request timed out");
+				json_decref(request);
+			}
+			free_outstanding_rrpc(or);
+		}
+	}
+}
+
+RRPC *find_rrpc(const char *source, const char *destination, const char *requestid)
+{
+	RRPC *r;
+	for (r = rrpc_list; r; r = r->next)
+	{
+		if (!strcmp(r->source, source) &&
+		    !strcmp(r->destination, destination) &&
+		    !strcmp(r->requestid, requestid))
+		{
+			return r;
+		}
+	}
+	return NULL;
+}
+
+OutstandingRRPC *find_outstandingrrpc(const char *source, const char *requestid)
+{
+	OutstandingRRPC *r;
+	for (r = outstanding_rrpc_list; r; r = r->next)
+	{
+		if (!strcmp(r->source, source) &&
+		    !strcmp(r->requestid, requestid))
+		{
+			return r;
+		}
+	}
+	return NULL;
+}
+
+/* Remote RPC call over the network (RRPC)
+ * :<server> RRPC <REQ|RES> <source> <destination> <requestid> [S|C|F] :<request data>
+ * S = Start
+ * C = Continuation
+ * F = Finish
+ */
+CMD_FUNC(cmd_rrpc)
+{
+	int request;
+	const char *source, *destination, *requestid, *type, *data;
+	RRPC *r;
+	Client *dest;
+	char sid[SIDLEN+1];
+	char binarydata[BUFSIZE+1];
+	int binarydatalen;
+
+	if ((parc < 7) || BadPtr(parv[6]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, "KNOCK");
+		return;
+	}
+
+	if (!strcmp(parv[1], "REQ"))
+	{
+		request = 1;
+	} else if (!strcmp(parv[1], "RES"))
+	{
+		request = 0;
+	} else {
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Invalid parameter");
+		return;
+	}
+
+	source = parv[2];
+	destination = parv[3];
+	requestid = parv[4];
+	type = parv[5];
+	data = parv[6];
+
+	/* Search by SID (first 3 characters of destination)
+	 * so we can always deliver, even forn unknown UID destinations
+	 * in case this is a response.
+	 */
+	strlcpy(sid, destination, sizeof(sid));
+	dest = find_server_quick(sid);
+	if (!dest)
+	{
+		sendnumeric(client, ERR_NOSUCHSERVER, sid);
+		return;
+	}
+
+	if (dest != &me)
+	{
+		/* Just pass it along... */
+		sendto_one(dest, recv_mtags, ":%s RRPC %s %s %s %s %s :%s",
+		           client->id, parv[1], parv[2], parv[3], parv[4], parv[5], parv[6]);
+		return;
+	}
+
+	/* It's for us! So handle it ;) */
+
+	if (strchr(type, 'S'))
+	{
+		r = find_rrpc(source, destination, requestid);
+		if (r)
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Duplicate request found");
+			/* We actually terminate the existing RRPC as well,
+			 * because there's a big risk of the the two different ones
+			 * merging in subsequent RRPC... C ... commands. Bad!
+			 * (and yeah this does not handle the case where you have
+			 *  like 3 or more duplicate request id requests... so be it..)
+			 */
+			free_rrpc(r);
+			return;
+		}
+		/* A new request */
+		r = safe_alloc(sizeof(RRPC));
+		strlcpy(r->source, source, sizeof(r->source));
+		strlcpy(r->destination, destination, sizeof(r->destination));
+		safe_strdup(r->requestid, requestid);
+		r->request = request;
+		dbuf_queue_init(&r->data);
+		AddListItem(r, rrpc_list);
+	} else
+	if (strchr(type, 'C') || strchr(type, 'F'))
+	{
+		r = find_rrpc(source, destination, requestid);
+		if (!r)
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Request not found");
+			return;
+		}
+	} else
+	{
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "RRPC", "Only actions S/C/F are supported");
+		return;
+	}
+
+	/* Append the data */
+	dbuf_put(&r->data, data, strlen(data));
+
+	/* Now check if the request happens to be terminated */
+	if (strchr(type, 'F'))
+	{
+		if (r->request)
+			rpc_call_remote(r);
+		else
+			rpc_response_remote(r);
+		free_rrpc(r);
+		return;
+	}
+}
+
+/** Convert the RRPC data to actual workable JSON output */
+json_t *rrpc_data(RRPC *r)
+{
+	int datalen;
+	char *data;
+	json_t *j;
+	json_error_t jerr;
+
+	datalen = dbuf_get(&r->data, &data);
+	j = json_loads(data, JSON_REJECT_DUPLICATES, &jerr);
+	safe_free(data);
+
+	return j;
+}
+
+/** Received a remote RPC request (from a client on another server) */
+void rpc_call_remote(RRPC *r)
+{
+	json_t *request = NULL;
+	Client *server;
+	Client *client;
+	char sid[SIDLEN+1];
+
+	request = rrpc_data(r);
+	if (!request)
+	{
+		// TODO: handle invalid JSON
+		return;
+	}
+
+	/* Create a (fake) client structure */
+	strlcpy(sid, r->source, sizeof(sid));
+	server = find_server_quick(sid);
+	if (!server)
+	{
+		return;
+	}
+	client = make_client(server->direction, server);
+	strlcpy(client->id, r->source, sizeof(client->id));
+	client->rpc = safe_alloc(sizeof(RPCClient));
+	strlcpy(client->name, "RPC:remote", sizeof(client->name));
+	safe_strdup(client->rpc->rpc_user, "<remote>");
+	// Note: NOT added to hash table or id table etc.
+	list_add(&client->client_node, &rpc_remote_list);
+	rpc_call(client, request);
+	json_decref(request);
+
+	/* And free the temporary client, unless it is async... */
+	if (!IsAsyncRPC(client))
+		free_client(client);
+}
+
+/** Received a remote RPC response (from another server) to our local RPC client */
+void rpc_response_remote(RRPC *r)
+{
+	OutstandingRRPC *or;
+	Client *client = find_client(r->destination, NULL);
+	json_t *json, *j;
+
+	if (!client)
+		return;
+
+	or = find_outstandingrrpc(client->id, r->requestid);
+	if (!or)
+		return; /* Not a known outstanding request, maybe the client left already */
+
+	json = rrpc_data(r);
+	if (!json)
+		return;
+
+	if ((j = json_object_get(json, "result")))
+	{
+		rpc_response(client, json, j);
+	} else if ((j = json_object_get(json, "error")))
+	{
+		json_t *x;
+		int errorcode = 0;
+		const char *error_message = json_object_get_string(j, "message");
+		if ((x = json_object_get(j, "errorcode")))
+			errorcode = json_integer_value(x);
+		if (errorcode == 0)
+			errorcode = JSON_RPC_ERROR_INTERNAL_ERROR;
+		rpc_error(client, json, errorcode, error_message ? error_message : "");
+	}
+
+	json_decref(json);
+
+	free_outstanding_rrpc(or);
+}
+
+const char *rpc_id(json_t *request)
+{
+	static char rid[128];
+	const char *requestid;
+	json_t *j;
+
+	j = json_object_get(request, "id");
+	if (!j)
+		return NULL;
+
+	requestid = json_string_value(j);
+	if (!requestid)
+	{
+		json_int_t v = json_integer_value(j);
+		if (v == 0)
+			return NULL;
+		snprintf(rid, sizeof(rid), "%lld", (long long)v);
+		requestid = rid;
+	}
+
+	return requestid;
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void rpc_send_generic_to_remote(Client *source, Client *target, const char *requesttype, json_t *json)
+{
+	char *json_serialized;
+	json_t *j;
+	const char *type;
+	const char *requestid;
+	char *str;
+	int bytes; /* bytes in this frame */
+	int bytes_remaining; /* bytes remaining overall */
+	int start_frame = 1; /* set to 1 if this is the start frame */
+	char data[451];
+
+	requestid = rpc_id(json);
+	if (!requestid)
+		return;
+
+	json_serialized = json_dumps(json, 0);
+	if (!json_serialized)
+		return;
+
+	/* :<server> RRPC REQ <source> <destination> <requestid> [S|C|F] :<request data>
+	 * S = Start
+	 * C = Continuation
+	 * F = Finish
+	 */
+
+	bytes_remaining = strlen(json_serialized);
+	for (str = json_serialized, bytes = MIN(bytes_remaining, 450);
+	     str && *str && bytes_remaining;
+	     str += bytes, bytes = MIN(bytes_remaining, 450))
+	{
+		bytes_remaining -= bytes;
+		if (start_frame == 1)
+		{
+			start_frame = 0;
+			if (bytes_remaining > 0)
+				type = "S"; /* start (with later continuation frames) */
+			else
+				type = "SF"; /* start and finish */
+		} else
+		if (bytes_remaining > 0)
+		{
+			type = "C"; /* continuation frame (with later a finish frame) */
+		} else {
+			type = "F"; /* finish frame (the last frame) */
+		}
+
+		strlncpy(data, str, sizeof(data), bytes);
+
+		sendto_one(target, NULL, ":%s RRPC %s %s %s %s %s :%s",
+		           me.id,
+		           requesttype,
+		           source->id,
+		           target->id,
+		           requestid,
+		           type,
+		           data);
+	}
+
+	safe_free(json_serialized);
+}
+
+int _rrpc_supported_simple(Client *target, char **problem_server)
+{
+	if (!moddata_client_get(target, "rrpc"))
+	{
+		if (problem_server)
+			*problem_server = target->name;
+		return 0;
+	}
+	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
+		return 0;
+	return 1;
+}
+
+int _rrpc_supported(Client *target, const char *module, const char *minimum_version, char **problem_server)
+{
+	if (!moddata_client_get(target, "rrpc"))
+	{
+		if (problem_server)
+			*problem_server = target->name;
+		return 0;
+	}
+	if ((target != target->direction) && !rrpc_supported_simple(target->direction, problem_server))
+		return 0;
+	return 1;
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void _rpc_send_request_to_remote(Client *source, Client *target, json_t *request)
+{
+	OutstandingRRPC *r;
+	const char *requestid = rpc_id(request);
+	char *problem_server = NULL;
+
+	if (!requestid)
+	{
+		/* should never happen, since already covered upstream, but just to be sure... */
+		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "The 'id' must be a string or an integer in UnrealIRCd JSON-RPC");
+		return;
+	}
+
+	if (find_outstandingrrpc(source->id, requestid))
+	{
+		rpc_error(source, NULL, JSON_RPC_ERROR_INVALID_REQUEST, "A request with that id is already in progress. Use unique id's!");
+		return;
+	}
+
+	/* If we already detect that next server cannot satisfy the request then stop it right now */
+	if (!rrpc_supported_simple(target, &problem_server))
+	{
+		rpc_error_fmt(source, request, JSON_RPC_ERROR_REMOTE_SERVER_NO_RPC, "Server %s does not support remote JSON-RPC", problem_server);
+		return;
+	}
+
+	/* Add the request to the "Outstanding RRPC list" */
+	r = safe_alloc(sizeof(OutstandingRRPC));
+	r->sent = TStime();
+	strlcpy(r->source, source->id, sizeof(r->source));
+	strlcpy(r->destination, target->id, sizeof(r->destination));
+	safe_strdup(r->requestid, requestid);
+	AddListItem(r, outstanding_rrpc_list);
+
+	/* And send it! */
+	rpc_send_generic_to_remote(source, target, "REQ", request);
+}
+
+/** Send a remote RPC (RRPC) request 'request' to server 'target'. */
+void _rpc_send_response_to_remote(Client *source, Client *target, json_t *response)
+{
+	rpc_send_generic_to_remote(source, target, "RES", response);
+}
+
+const char *rrpc_md_serialize(ModData *m)
+{
+	static char buf[512];
+	char tmp[128];
+	NameValuePrioList *nv;
+
+	if (m->ptr == NULL)
+		return NULL;
+
+	*buf = '\0';
+	for (nv = m->ptr; nv; nv = nv->next)
+	{
+		snprintf(tmp, sizeof(tmp), "%s:%s,", nv->name, nv->value);
+		strlcat(buf, tmp, sizeof(buf));
+	}
+	if (*buf)
+		buf[strlen(buf)-1] = '\0'; // strip last comma
+
+	return buf;
+}
+
+void rrpc_md_unserialize(const char *str, ModData *m)
+{
+	char buf[1024], *p, *name, *value;
+
+	/* First free everything if needed */
+	if (m->ptr)
+	{
+		free_nvplist(m->ptr);
+		m->ptr = NULL;
+	}
+
+	if (BadPtr(str))
+		return; /* Done already */
+
+	strlcpy(buf, str, sizeof(buf));
+	for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
+	{
+		value = strchr(name, ':');
+		if (!value)
+			continue;
+		*value++ = '\0';
+		add_nvplist((NameValuePrioList **)&m->ptr, 0, name, value);
+	}
+}
+
+void rrpc_md_free(ModData *m)
+{
+	if (m->ptr)
+	{
+		free_nvplist(m->ptr);
+		m->ptr = NULL;
+	}
+}
+
+int rpc_json_expand_client_server(Client *client, int detail, json_t *j, json_t *child)
+{
+	NameValuePrioList *nv = RRPCMODULES(client);
+	json_t *rpc_modules;
+
+	if (!nv || (detail < 2))
+		return 0;
+
+	/* All this belongs under 'features' */
+	child = json_object_get(child, "features");
+	if (!child)
+		return 0;
+
+	rpc_modules = json_array();
+	json_object_set_new(child, "rpc_modules", rpc_modules);
+	for (; nv; nv = nv->next)
+	{
+		json_t *e = json_object();
+		json_object_set_new(e, "name", json_string_unreal(nv->name));
+		json_object_set_new(e, "version", json_string_unreal(nv->value));
+		json_array_append_new(rpc_modules, e);
+	}
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_rpc_add_timer)
+{
+	json_t *result;
+	json_t *subrequest;
+	long every_msec;
+	const char *timer_id;
+	const char *method;
+	RPCHandler *handler;
+	RPCTimer *timer;
+
+	REQUIRE_PARAM_INTEGER("every_msec", every_msec);
+	REQUIRE_PARAM_STRING("timer_id", timer_id);
+
+	subrequest = json_object_get(params, "request");
+	if (!subrequest)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: '%s'", "request");
+		return;
+	}
+
+	if (every_msec < RPC_MINIMUM_TIMER_MSEC)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS,
+		              "Value for every_msec may not be less than %d",
+		              (int)RPC_MINIMUM_TIMER_MSEC);
+		return;
+	}
+
+	/* Do some validation on the name */
+	if (!parse_rpc_call(client, request, subrequest, &method, &handler))
+		return; /* Error already returned to caller */
+
+	/* We don't validate 'params' here, but do so at runtime */
+
+	/* Check if a timer with that same name already exists FOR THIS CLIENT */
+	if (find_rpc_timer(client, timer_id))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Timer already exists with timer_id '%s'", timer_id);
+		return;
+	}
+
+	timer = safe_alloc(sizeof(RPCTimer));
+	timer->every_msec = every_msec;
+	timer->client = client;
+	safe_strdup(timer->timer_id, timer_id);
+	json_incref(subrequest);
+	timer->request = subrequest;
+	AddListItem(timer, rpc_timer_list);
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+EVENT(rpc_do_timers)
+{
+	RPCTimer *e, *e_next;
+
+	for (e = rpc_timer_list; e; e = e_next)
+	{
+		e_next = e->next;
+		if (minimum_msec_since_last_run(&e->last_run, e->every_msec))
+		{
+			rpc_call(e->client, e->request);
+		}
+		// TODO: maybe do counts as well?
+	}
+}
+
+/** Client being freed? If RPC then cancel timers, if any */
+int rpc_handle_free_client(Client *client)
+{
+	if (IsRPC(client))
+		free_rpc_timers_for_user(client);
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_rpc_del_timer)
+{
+	const char *timer_id;
+	RPCTimer *r;
+	json_t *result;
+
+	REQUIRE_PARAM_STRING("timer_id", timer_id);
+
+	r = find_rpc_timer(client, timer_id);
+	if (!r)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_NOT_FOUND, "Timer not found with timer_id '%s'", timer_id);
+		return;
+	}
+	free_rpc_timer(r);
+
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/server.c b/src/modules/rpc/server.c
@@ -0,0 +1,416 @@
+/* server.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server",
+	"1.0.0",
+	"server.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_list);
+RPC_CALL_FUNC(rpc_server_get);
+RPC_CALL_FUNC(rpc_server_rehash);
+RPC_CALL_FUNC(rpc_server_connect);
+RPC_CALL_FUNC(rpc_server_disconnect);
+RPC_CALL_FUNC(rpc_server_module_list);
+
+int rpc_server_rehash_log(int failure, json_t *rehash_log);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.rehash";
+	r.call = rpc_server_rehash;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.connect";
+	r.call = rpc_server_connect;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.disconnect";
+	r.call = rpc_server_disconnect;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "server.module_list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_module_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	HookAdd(modinfo->handle, HOOKTYPE_REHASH_LOG, 0, rpc_server_rehash_log);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_list)
+{
+	json_t *result, *list, *item;
+	Client *acptr;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	list_for_each_entry(acptr, &global_server_list, client_node)
+	{
+		if (!IsServer(acptr) && !IsMe(acptr))
+			continue;
+
+		item = json_object();
+		json_expand_client(item, NULL, acptr, 99);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_get)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	result = json_object();
+	json_expand_client(result, "server", acptr, 99);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_rehash)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	if (acptr != &me)
+	{
+		/* Forward to remote */
+		if (rrpc_supported_simple(acptr, NULL))
+		{
+			/* Server supports RRPC and will handle the response */
+			rpc_send_request_to_remote(client, acptr, request);
+		} else {
+			/* Server does not support RRPC, so we can only do best effort: */
+			sendto_one(acptr, NULL, ":%s REHASH %s", me.id, acptr->name);
+			result = json_boolean(1);
+			rpc_response(client, request, result);
+			json_decref(result);
+		}
+		return;
+	}
+
+	if (client->rpc->rehash_request)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "A rehash initiated by you is already in progress");
+		return;
+	}
+
+	/* It's for me... */
+
+	SetMonitorRehash(client);
+	SetAsyncRPC(client);
+	client->rpc->rehash_request = json_copy(request); // or json_deep_copy ??
+
+	if (!loop.rehashing)
+	{
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
+		request_rehash(client);
+	} /* else.. we simply joined the rehash request so we are notified as well ;) */
+
+	/* Do NOT send the JSON Response here, it is done by rpc_server_rehash_log()
+	 * after the REHASH completed (which may take several seconds).
+	 */
+}
+
+int rpc_server_rehash_log(int failure, json_t *rehash_log)
+{
+	Client *client, *next;
+
+	list_for_each_entry(client, &unknown_list, lclient_node)
+	{
+		if (IsRPC(client) && IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
+		{
+			rpc_response(client, client->rpc->rehash_request, rehash_log);
+			json_decref(client->rpc->rehash_request);
+			client->rpc->rehash_request = NULL;
+		}
+	}
+	list_for_each_entry_safe(client, next, &rpc_remote_list, client_node)
+	{
+		if (IsMonitorRehash(client) && client->rpc && client->rpc->rehash_request)
+		{
+			rpc_response(client, client->rpc->rehash_request, rehash_log);
+			json_decref(client->rpc->rehash_request);
+			client->rpc->rehash_request = NULL;
+			free_client(client);
+		}
+	}
+	return 0;
+}
+
+RPC_CALL_FUNC(rpc_server_connect)
+{
+	json_t *result, *list, *item;
+	const char *server, *link_name;
+	Client *acptr;
+	ConfigItem_link *link;
+	const char *err;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+	REQUIRE_PARAM_STRING("link", link_name);
+
+	if (acptr != &me)
+	{
+		/* Not supported atm */
+		result = json_boolean(0);
+		rpc_response(client, request, result);
+		json_decref(result);
+		return;
+	}
+
+	if (find_server_quick(link_name))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Server is already linked");
+		return;
+	}
+
+	link = find_link(link_name);
+	if (!link)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name does not exist in any link block");
+		return;
+	}
+	if (!link->outgoing.hostname && !link->outgoing.file)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server with that name exists but is not configured as an OUTGOING server.");
+		return;
+	}
+
+	if ((err = check_deny_link(link, 0)))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_DENIED, "Server linking is denied via a deny link { } block: %s", err);
+		return;
+	}
+
+	unreal_log(ULOG_INFO, "link", "LINK_REQUEST", client,
+		   "CONNECT: Link to $link_block requested by $client",
+		   log_data_link_block(link));
+
+	connect_server(link, client, NULL);
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_disconnect)
+{
+	json_t *result, *list, *item;
+	const char *server, *link_name, *reason;
+	Client *acptr, *target;
+	MessageTag *mtags = NULL;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+	REQUIRE_PARAM_STRING("link", link_name);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (acptr != &me)
+	{
+		/* Not supported atm */
+		result = json_boolean(0);
+		rpc_response(client, request, result);
+		json_decref(result);
+		return;
+	}
+
+	target = find_server_quick(link_name);
+	if (!target)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server link not found");
+		return;
+	}
+
+	if (target == &me)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "We cannot disconnect ourselves");
+		return;
+	}
+
+	unreal_log(ULOG_INFO, "link", "SQUIT", client,
+	           "SQUIT: Forced server disconnect of $target by $client ($reason)",
+	           log_data_client("target", target),
+	           log_data_string("reason", reason));
+
+	/* The actual SQUIT: */
+	new_message(client, NULL, &mtags);
+	exit_client_ex(target, NULL, mtags, reason);
+	free_message_tags(mtags);
+
+	/* Return true */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+void json_expand_module(json_t *j, const char *key, Module *m, int detail)
+{
+	char buf[BUFSIZE+1];
+	json_t *child;
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	json_object_set_new(child, "name", json_string_unreal(m->header->name));
+	json_object_set_new(child, "version", json_string_unreal(m->header->version));
+	json_object_set_new(child, "author", json_string_unreal(m->header->author));
+	json_object_set_new(child, "description", json_string_unreal(m->header->description));
+	json_object_set_new(child, "third_party", json_boolean(m->options & MOD_OPT_OFFICIAL ? 0 : 1));
+	json_object_set_new(child, "permanent", json_boolean(m->options & MOD_OPT_PERM ? 1 : 0));
+	json_object_set_new(child, "permanent_but_reloadable", json_boolean(m->options & MOD_OPT_PERM_RELOADABLE ? 1 : 0));
+}
+
+RPC_CALL_FUNC(rpc_server_module_list)
+{
+	json_t *result, *list, *item;
+	const char *server;
+	Client *acptr;
+	Module *m;
+
+	OPTIONAL_PARAM_STRING("server", server);
+	if (server)
+	{
+		if (!(acptr = find_server(server, NULL)))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Server not found");
+			return;
+		}
+	} else {
+		acptr = &me;
+	}
+
+	if (acptr != &me)
+	{
+		/* Forward to remote */
+		rpc_send_request_to_remote(client, acptr, request);
+		return;
+	}
+
+	/* Return true */
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (m = Modules; m; m = m->next)
+	{
+		item = json_object();
+		json_expand_module(item, NULL, m, 1);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/server_ban.c b/src/modules/rpc/server_ban.c
@@ -0,0 +1,342 @@
+/* server_ban.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server_ban",
+	"1.0.3",
+	"server_ban.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_ban_list);
+RPC_CALL_FUNC(rpc_server_ban_get);
+RPC_CALL_FUNC(rpc_server_ban_del);
+RPC_CALL_FUNC(rpc_server_ban_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server_ban.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.del";
+	r.call = rpc_server_ban_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban.add";
+	r.call = rpc_server_ban_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_list)
+{
+	json_t *result, *list, *item;
+	int index, index2;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (TKLIsServerBan(tkl))
+				{
+					item = json_object();
+					json_expand_tkl(item, NULL, tkl, 1);
+					json_array_append_new(list, item);
+				}
+			}
+		}
+	}
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsServerBan(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** Shared code for selecting a server ban, for .add/.del/.get */
+int server_ban_select_criteria(Client *client, json_t *request, json_t *params,
+                               const char **name,
+                               const char **type_name,
+                               char *tkl_type_char,
+                               int *tkl_type_int,
+                               char **usermask,
+                               char **hostmask,
+                               int *soft)
+{
+	const char *error;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	*type_name = json_object_get_string(params, "type");
+	if (!*type_name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'type'");
+		return 0;
+	}
+
+	*tkl_type_char = tkl_configtypetochar(*type_name);
+	if (!*tkl_type_char)
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s'", *type_name);
+		return 0;
+	}
+	*tkl_type_int = tkl_chartotype(*tkl_type_char);
+	if (!TKLIsServerBanType(*tkl_type_int))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s' (type exists but is not valid for in server_ban.*)", *type_name);
+		return 0;
+	}
+
+	if (!server_ban_parse_mask(client, 0, *tkl_type_char, *name, usermask, hostmask, soft, &error))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
+		return 0;
+	}
+
+	/* Hm, shouldn't this be done by server_ban_parse_mask() ? */
+	if (*soft && (*usermask[0] == '%'))
+		*usermask = *usermask + 1;
+
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_get)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_del)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	const char *set_by;
+	char *usermask, *hostmask;
+	char usermask_mess[256];
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+	const char *tkllayer[10];
+	char tkl_type_str[2];
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	tkl_type_str[0] = tkl_type_char;
+	tkl_type_str[1] = '\0';
+
+	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
+		return;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[1] = "-";
+	tkllayer[2] = tkl_type_str;
+	if (soft)
+	{
+		/* I don't like this fiddling.
+		 * It will be gone when we move from cmd_tkl() to a real function though.
+		 */
+		snprintf(usermask_mess, sizeof(usermask_mess), "%%%s", usermask);
+		tkllayer[3] = usermask_mess;
+	} else {
+		tkllayer[3] = usermask;
+	}
+	tkllayer[4] = hostmask;
+	tkllayer[5] = set_by;
+	tkllayer[6] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_add)
+{
+	json_t *result, *list, *item;
+	const char *name, *type_name;
+	const char *set_by;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	char tkl_type_char;
+	int tkl_type_int;
+	char tkl_type_str[2];
+	const char *reason;
+	const char *str;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+
+	if (!server_ban_select_criteria(client, request, params,
+	                                &name, &type_name,
+	                                &tkl_type_char, &tkl_type_int,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	tkl_type_str[0] = tkl_type_char;
+	tkl_type_str[1] = '\0';
+
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban with that mask already exists");
+		return;
+	}
+
+	tkl = tkl_add_serverban(tkl_type_int, usermask, hostmask, reason,
+	                        set_by, tkl_expire_at, tkl_set_at,
+	                        soft, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/server_ban_exception.c b/src/modules/rpc/server_ban_exception.c
@@ -0,0 +1,314 @@
+/* server_ban_exception.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/server_ban_exception",
+	"1.0.1",
+	"server_ban_exception.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_server_ban_exception_list);
+RPC_CALL_FUNC(rpc_server_ban_exception_get);
+RPC_CALL_FUNC(rpc_server_ban_exception_del);
+RPC_CALL_FUNC(rpc_server_ban_exception_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "server_ban_exception.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_exception_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_server_ban_exception_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.del";
+	r.call = rpc_server_ban_exception_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "server_ban_exception.add";
+	r.call = rpc_server_ban_exception_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/server_ban_exception] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_list)
+{
+	json_t *result, *list, *item;
+	int index, index2;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				if (TKLIsBanException(tkl))
+				{
+					item = json_object();
+					json_expand_tkl(item, NULL, tkl, 1);
+					json_array_append_new(list, item);
+				}
+			}
+		}
+	}
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsBanException(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/** Shared code for selecting a server ban, for .add/.del/.get */
+int server_ban_exception_select_criteria(Client *client, json_t *request, json_t *params, int add,
+                               const char **name,
+                               const char **exception_types,
+                               char **usermask,
+                               char **hostmask,
+                               int *soft)
+{
+	const char *error;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	if (add)
+	{
+		*exception_types = json_object_get_string(params, "exception_types");
+		if (!*exception_types)
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'exception_types'");
+			return 0;
+		}
+	} else {
+		*exception_types = NULL;
+	}
+
+	if (!server_ban_exception_parse_mask(client, add, *exception_types, *name, usermask, hostmask, soft, &error))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
+		return 0;
+	}
+
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_get)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+
+	if (!server_ban_exception_select_criteria(client, request, params, 0,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
+	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_del)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	const char *set_by;
+	const char *error;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	const char *tkllayer[11];
+
+	if (!server_ban_exception_select_criteria(client, request, params, 0,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	if (!(tkl = find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft)) &&
+	    !(tkl = find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban exception not found");
+		return;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	tkllayer[1] = "-";
+	tkllayer[2] = "E";
+	tkllayer[3] = usermask;
+	tkllayer[4] = hostmask;
+	tkllayer[5] = set_by;
+	tkllayer[6] = "0";
+	tkllayer[7] = "-";
+	tkllayer[8] = "-";
+	tkllayer[9] = "-";
+	tkllayer[10] = NULL;
+	cmd_tkl(&me, NULL, 6, tkllayer);
+
+	if (!find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) &&
+	    !find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based ban.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+	}
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_server_ban_exception_add)
+{
+	json_t *result, *list, *item;
+	const char *name, *exception_types;
+	const char *set_by;
+	const char *error;
+	char *usermask, *hostmask;
+	int soft;
+	TKL *tkl;
+	const char *reason;
+	const char *str;
+	time_t tkl_expire_at;
+	time_t tkl_set_at = TStime();
+
+	if (!server_ban_exception_select_criteria(client, request, params, 1,
+	                                &name, &exception_types,
+	                                &usermask, &hostmask, &soft))
+	{
+		return;
+	}
+
+	// FIXME: where is set_by? is this missing in others as well? :p -> OPTIONAL!
+
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	/* Duration / expiry time */
+	if ((str = json_object_get_string(params, "duration_string")))
+	{
+		tkl_expire_at = config_checkval(str, CFG_TIME);
+		if (tkl_expire_at > 0)
+			tkl_expire_at = TStime() + tkl_expire_at;
+	} else
+	if ((str = json_object_get_string(params, "expire_at")))
+	{
+		tkl_expire_at = server_time_to_unix_time(str);
+	} else
+	{
+		/* Never expire */
+		tkl_expire_at = 0;
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
+	{
+		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
+		return;
+	}
+
+	if (find_tkl_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask, soft) ||
+	    find_tkl_banexception(TKL_EXCEPTION, usermask, hostmask, soft))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban exception with that mask already exists");
+		return;
+	}
+
+	tkl = tkl_add_banexception(TKL_EXCEPTION|TKL_GLOBAL, usermask, hostmask,
+	                           NULL, reason,
+	                           set_by, tkl_expire_at, tkl_set_at,
+	                           soft, exception_types, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/spamfilter.c b/src/modules/rpc/spamfilter.c
@@ -0,0 +1,326 @@
+/* spamfilter.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/spamfilter",
+	"1.0.3",
+	"spamfilter.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_spamfilter_list);
+RPC_CALL_FUNC(rpc_spamfilter_get);
+RPC_CALL_FUNC(rpc_spamfilter_del);
+RPC_CALL_FUNC(rpc_spamfilter_add);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "spamfilter.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_spamfilter_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_spamfilter_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.del";
+	r.call = rpc_spamfilter_del;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	r.method = "spamfilter.add";
+	r.call = rpc_spamfilter_add;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/spamfilter] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_list)
+{
+	json_t *result, *list, *item;
+	int index;
+	TKL *tkl;
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			if (TKLIsSpamfilter(tkl))
+			{
+				item = json_object();
+				json_expand_tkl(item, NULL, tkl, 1);
+				json_array_append_new(list, item);
+			}
+		}
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+/* Shared code for selecting a spamfilter, for .add/.del/get */
+int spamfilter_select_criteria(Client *client, json_t *request, json_t *params, const char **name, int *match_type,
+                               int *targets, char *targetbuf, size_t targetbuflen, BanAction *action, char *actionbuf)
+{
+	const char *str;
+
+	*name = json_object_get_string(params, "name");
+	if (!*name)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
+		return 0;
+	}
+
+	str = json_object_get_string(params, "match_type");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'match_type'");
+		return 0;
+	}
+	*match_type = unreal_match_method_strtoval(str);
+	if (!*match_type)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'match_type'");
+		return 0;
+	}
+
+	str = json_object_get_string(params, "spamfilter_targets");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'spamfilter_targets'");
+		return 0;
+	}
+	*targets = spamfilter_gettargets(str, NULL);
+	if (!*targets)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value(s) for parameter 'spamfilter_targets'");
+		return 0;
+	}
+	strlcpy(targetbuf, spamfilter_target_inttostring(*targets), targetbuflen);
+
+	str = json_object_get_string(params, "ban_action");
+	if (!str)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'ban_action'");
+		return 0;
+	}
+	*action = banact_stringtoval(str);
+	if (!*action)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_action'");
+		return 0;
+	}
+	actionbuf[0] = banact_valtochar(*action);
+	actionbuf[1] = '\0';
+	return 1;
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_get)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *name;
+	TKL *tkl;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_add)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *str;
+	const char *name, *reason;
+	const char *set_by;
+	time_t ban_duration = 0;
+	TKL *tkl;
+	Match *m;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+	char reasonbuf[512];
+	char *err = NULL;
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	/* Reason */
+	reason = json_object_get_string(params, "reason");
+	if (!reason)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'reason'");
+		return;
+	}
+
+	/* Ban duration */
+	if ((str = json_object_get_string(params, "ban_duration")))
+	{
+		ban_duration = config_checkval(str, CFG_TIME);
+		if (ban_duration < 0)
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_duration'");
+			return;
+		}
+	}
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	if (find_tkl_spamfilter(type, name, action, targets))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A spamfilter with that regex+action+target already exists");
+		return;
+	}
+
+	/* Convert reason to use internal storage and wire format */
+	reason = unreal_encodespace(reason);
+	strlcpy(reasonbuf, reason, sizeof(reasonbuf));
+	reason = reasonbuf;
+
+	/* now check the regex / match field (only when adding) */
+	m = unreal_create_match(match_type, name, &err);
+	if (!m)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid regex or match string specified");
+		return;
+	}
+
+	tkl = tkl_add_spamfilter(type, targets, action, m, set_by, 0, TStime(),
+	                         ban_duration, reason, 0);
+
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
+		return;
+	}
+
+	tkl_added(client, tkl);
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_spamfilter_del)
+{
+	json_t *result;
+	int type = TKL_SPAMF|TKL_GLOBAL;
+	const char *name;
+	const char *set_by;
+	TKL *tkl;
+	BanAction action;
+	int match_type = 0;
+	int targets = 0;
+	char targetbuf[64];
+	char actionbuf[2];
+	const char *tkllayer[13];
+
+	if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
+		return; /* Error already communicated to client */
+
+	OPTIONAL_PARAM_STRING("set_by", set_by);
+	if (!set_by)
+		set_by = client->name;
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_tkl(result, "tkl", tkl, 1);
+
+	/* Wait.. this is a bit dull? */
+	tkllayer[1] = "-";
+	tkllayer[2] = "F";
+	tkllayer[3] = targetbuf;
+	tkllayer[4] = actionbuf;
+	tkllayer[5] = set_by;
+	tkllayer[6] = "-";
+	tkllayer[7] = "0";
+	tkllayer[8] = "0";
+	tkllayer[9] = "-";
+	tkllayer[10] = unreal_match_method_valtostr(match_type);
+	tkllayer[11] = name;
+	tkllayer[12] = NULL;
+
+	cmd_tkl(&me, NULL, 12, tkllayer);
+
+	tkl = find_tkl_spamfilter(type, name, action, targets);
+	if (!tkl)
+	{
+		rpc_response(client, request, result);
+	} else {
+		/* Spamfilter still exists so failure to remove.
+		 * Actually this may not be an internal error, it could be an
+		 * incorrect request, such as asking to remove a config-based spamfilter.
+		 */
+		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
+		return;
+	}
+	json_decref(result);
+}
diff --git a/src/modules/rpc/stats.c b/src/modules/rpc/stats.c
@@ -0,0 +1,214 @@
+/* stats.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/stats",
+	"1.0.2",
+	"stats.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+void rpc_stats_get(Client *client, json_t *request, json_t *params);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "stats.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_stats_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/stats] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+void json_expand_countries(json_t *main, const char *name, NameValuePrioList *geo)
+{
+	json_t *list = json_array();
+	json_t *item;
+
+	json_object_set_new(main, name, list);
+
+	for (; geo; geo = geo->next)
+	{
+		item = json_object();
+		json_object_set_new(item, "country", json_string_unreal(geo->name));
+		json_object_set_new(item, "count", json_integer(0 - geo->priority));
+		json_array_append_new(list, item);
+	}
+}
+
+void rpc_stats_user(json_t *main, int detail)
+{
+	Client *client;
+	int total = 0, ulined = 0, oper = 0;
+	json_t *child;
+	GeoIPResult *geo;
+	NameValuePrioList *countries = NULL;
+
+	child = json_object();
+	json_object_set_new(main, "user", child);
+
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (IsUser(client))
+		{
+			total++;
+			if (IsULine(client))
+				ulined++;
+			else if (IsOper(client))
+				oper++;
+			if (detail >= 1)
+			{
+				geo = geoip_client(client);
+				if (geo && geo->country_code)
+				{
+					NameValuePrioList *e = find_nvplist(countries, geo->country_code);
+					if (e)
+					{
+						DelListItem(e, countries);
+						e->priority--;
+						AddListItemPrio(e, countries, e->priority);
+					} else {
+						add_nvplist(&countries, -1, geo->country_code, NULL);
+					}
+				}
+			}
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "ulined", json_integer(ulined));
+	json_object_set_new(child, "oper", json_integer(oper));
+	json_object_set_new(child, "record", json_integer(irccounts.global_max));
+	if (detail >= 1)
+		json_expand_countries(child, "countries", countries);
+}
+
+void rpc_stats_channel(json_t *main)
+{
+	json_t *child = json_object();
+	json_object_set_new(main, "channel", child);
+	json_object_set_new(child, "total", json_integer(irccounts.channels));
+}
+
+void rpc_stats_server(json_t *main)
+{
+	Client *client;
+	int total = 0, ulined = 0, oper = 0;
+	json_t *child = json_object();
+	json_object_set_new(main, "server", child);
+
+	total++; /* ourselves */
+	list_for_each_entry(client, &global_server_list, client_node)
+	{
+		if (IsServer(client))
+		{
+			total++;
+			if (IsULine(client))
+				ulined++;
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "ulined", json_integer(ulined));
+}
+
+void rpc_stats_server_ban(json_t *main)
+{
+	Client *client;
+	int index, index2;
+	TKL *tkl;
+	int total = 0;
+	int server_ban = 0;
+	int server_ban_exception = 0;
+	int spamfilter = 0;
+	int name_ban = 0;
+	json_t *child = json_object();
+	json_object_set_new(main, "server_ban", child);
+
+	/* First, hashed entries.. */
+	for (index = 0; index < TKLIPHASHLEN1; index++)
+	{
+		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
+		{
+			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
+			{
+				total++;
+				if (TKLIsServerBan(tkl))
+					server_ban++;
+				else if (TKLIsBanException(tkl))
+					server_ban_exception++;
+				else if (TKLIsNameBan(tkl))
+					name_ban++;
+				else if (TKLIsSpamfilter(tkl))
+					spamfilter++;
+			}
+		}
+	}
+
+	/* Now normal entries.. */
+	for (index = 0; index < TKLISTLEN; index++)
+	{
+		for (tkl = tklines[index]; tkl; tkl = tkl->next)
+		{
+			total++;
+			if (TKLIsServerBan(tkl))
+				server_ban++;
+			else if (TKLIsBanException(tkl))
+				server_ban_exception++;
+			else if (TKLIsNameBan(tkl))
+				name_ban++;
+			else if (TKLIsSpamfilter(tkl))
+				spamfilter++;
+		}
+	}
+
+	json_object_set_new(child, "total", json_integer(total));
+	json_object_set_new(child, "server_ban", json_integer(server_ban));
+	json_object_set_new(child, "spamfilter", json_integer(spamfilter));
+	json_object_set_new(child, "name_ban", json_integer(name_ban));
+	json_object_set_new(child, "server_ban_exception", json_integer(server_ban_exception));
+}
+
+void rpc_stats_get(Client *client, json_t *request, json_t *params)
+{
+	json_t *result, *item;
+	const char *statsname;
+	Channel *stats;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 1);
+
+	result = json_object();
+	rpc_stats_server(result);
+	rpc_stats_user(result, details);
+	rpc_stats_channel(result);
+	rpc_stats_server_ban(result);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/user.c b/src/modules/rpc/user.c
@@ -0,0 +1,697 @@
+/* user.* RPC calls
+ * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/user",
+	"1.0.7",
+	"user.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_user_list);
+RPC_CALL_FUNC(rpc_user_get);
+RPC_CALL_FUNC(rpc_user_set_nick);
+RPC_CALL_FUNC(rpc_user_set_username);
+RPC_CALL_FUNC(rpc_user_set_realname);
+RPC_CALL_FUNC(rpc_user_set_vhost);
+RPC_CALL_FUNC(rpc_user_set_mode);
+RPC_CALL_FUNC(rpc_user_set_snomask);
+RPC_CALL_FUNC(rpc_user_set_oper);
+RPC_CALL_FUNC(rpc_user_kill);
+RPC_CALL_FUNC(rpc_user_quit);
+RPC_CALL_FUNC(rpc_user_join);
+RPC_CALL_FUNC(rpc_user_part);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "user.list";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_user_list;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_user_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_nick";
+	r.call = rpc_user_set_nick;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_username";
+	r.call = rpc_user_set_username;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_realname";
+	r.call = rpc_user_set_realname;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_vhost";
+	r.call = rpc_user_set_vhost;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_mode";
+	r.call = rpc_user_set_mode;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_snomask";
+	r.call = rpc_user_set_snomask;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.set_oper";
+	r.call = rpc_user_set_oper;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.kill";
+	r.call = rpc_user_kill;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.quit";
+	r.call = rpc_user_quit;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.join";
+	r.call = rpc_user_join;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+	memset(&r, 0, sizeof(r));
+	r.method = "user.part";
+	r.call = rpc_user_part;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/user] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+RPC_CALL_FUNC(rpc_user_list)
+{
+	json_t *result, *list, *item;
+	Client *acptr;
+	int details;
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (!IsUser(acptr))
+			continue;
+
+		item = json_object();
+		json_expand_client(item, NULL, acptr, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_get)
+{
+	json_t *result, *list, *item;
+	const char *nick;
+	Client *acptr;
+	int details;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 4);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	result = json_object();
+	json_expand_client(result, "client", acptr, details);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_nick)
+{
+	json_t *result, *list, *item;
+	MessageTag *mtags = NULL;
+	const char *args[5];
+	const char *nick, *newnick_requested, *str;
+	int force = 0;
+	char newnick[NICKLEN+1];
+	char tsbuf[32];
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("newnick", newnick_requested);
+	strlcpy(newnick, newnick_requested, iConf.nick_length + 1);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (!do_nick_name(newnick) || strcmp(newnick, newnick_requested) ||
+	    !strcasecmp(newnick, "IRC") || !strcasecmp(newnick, "IRCd"))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(nick, newnick))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old nickname and new nickname are identical");
+		return;
+	}
+
+	if (!force)
+	{
+		/* Check other restrictions */
+		Client *check = find_user(newnick, NULL);
+		int ishold = 0;
+
+		/* Check if in use by someone else (do allow case-changing) */
+		if (check && (acptr != check))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "New nickname is already taken by another user");
+			return;
+		}
+
+		// Can't really check for spamfilter here, since it assumes user is local
+
+		// But we can check q-lines...
+		if (find_qline(acptr, newnick, &ishold))
+		{
+			rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New nickname is forbidden by q-line");
+			return;
+		}
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = newnick;
+	snprintf(tsbuf, sizeof(tsbuf), "%lld", (long long)TStime());
+	args[3] = tsbuf;
+	args[4] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSNICK", 4, args);
+	safe_free_message_tags(mtags);
+
+	/* Simply return success */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_username)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *username, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("username", username);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (!valid_username(username))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New username contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(acptr->user->username, username))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new user name are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = username;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGIDENT", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(acptr->user->username, username))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_realname)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *realname, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("realname", realname);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (strlen(realname) > REALLEN)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New real name is too long");
+		return;
+	}
+
+	if (!strcmp(acptr->info, realname))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new real name are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = realname;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGNAME", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(acptr->info, realname))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_vhost)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *vhost, *str;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("vhost", vhost);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if ((strlen(vhost) > HOSTLEN) || !valid_host(vhost, 0))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_NAME, "New vhost contains forbidden character(s) or is too long");
+		return;
+	}
+
+	if (!strcmp(GetHost(acptr), vhost))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "Old and new vhost are identical");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = vhost;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "CHGHOST", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if (!strcmp(GetHost(acptr), vhost))
+		result = json_boolean(1);
+	else
+		result = json_boolean(0);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_mode)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *modes, *str;
+	int hidden;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("modes", modes);
+	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = modes;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, hidden ? "SVSMODE" : "SVS2MODE", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_snomask)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *snomask, *str;
+	int hidden;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("snomask", snomask);
+	OPTIONAL_PARAM_BOOLEAN("hidden", hidden, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = snomask;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, hidden ? "SVSSNO" : "SVS2SNO", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_set_oper)
+{
+	json_t *result, *list, *item;
+	const char *args[9];
+	const char *nick, *oper_account, *oper_class;
+	const char *class=NULL, *modes=NULL, *snomask=NULL, *vhost=NULL;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+	char default_modes[64];
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("oper_account", oper_account);
+	REQUIRE_PARAM_STRING("oper_class", oper_class);
+	OPTIONAL_PARAM_STRING("class", class);
+	OPTIONAL_PARAM_STRING("modes", modes);
+	OPTIONAL_PARAM_STRING("snomask", snomask);
+	OPTIONAL_PARAM_STRING("vhost", vhost);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	if (modes == NULL)
+	{
+		strlcpy(default_modes, get_usermode_string_raw(OPER_MODES), sizeof(default_modes));
+		modes = default_modes;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = oper_account;
+	args[3] = oper_class;
+	args[4] = class ? class : "opers";
+	args[5] = modes;
+	args[6] = snomask ? snomask : iConf.oper_snomask;
+	args[7] = vhost ? vhost : "-";
+	args[8] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSO", 8, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result (always true) */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_kill)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *reason;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = reason;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "KILL", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
+		result = json_boolean(0);
+	else
+		result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_quit)
+{
+	json_t *result, *list, *item;
+	const char *args[4];
+	const char *nick, *reason;
+	MessageTag *mtags = NULL;
+	Client *acptr;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("reason", reason);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = reason;
+	args[3] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, mtags, "SVSKILL", 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result */
+	if ((acptr = find_user(nick, NULL)) && !IsDead(acptr))
+		result = json_boolean(0);
+	else
+		result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_join)
+{
+	json_t *result, *list, *item;
+	const char *args[5];
+	const char *nick, *channel, *key=NULL;
+	Client *acptr;
+	MessageTag *mtags = NULL;
+	int force = 0;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("channel", channel);
+	OPTIONAL_PARAM_STRING("key", key);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	mtag_add_issued_by(&mtags, client, NULL);
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = channel;
+
+	if (force == 0)
+	{
+		args[3] = key;
+		args[4] = NULL;
+		do_cmd(&me, mtags, "SVSJOIN", key ? 4 : 3, args);
+	} else {
+		args[3] = NULL;
+		do_cmd(&me, mtags, "SAJOIN", 3, args);
+	}
+
+	safe_free_message_tags(mtags);
+
+	/* Return result -- yeah this is always true, not so good.. :D
+	 * It is that way because we (this server) may not actually
+	 * do the SVSJOIN at all, we may be just relaying it to some
+	 * other server.
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
+
+RPC_CALL_FUNC(rpc_user_part)
+{
+	json_t *result, *list, *item;
+	const char *args[5];
+	const char *nick, *channel, *reason=NULL;
+	Client *acptr;
+	MessageTag *mtags = NULL;
+	int force = 0;
+
+	REQUIRE_PARAM_STRING("nick", nick);
+	REQUIRE_PARAM_STRING("channel", channel);
+	OPTIONAL_PARAM_STRING("reason", reason);
+	OPTIONAL_PARAM_BOOLEAN("force", force, 0);
+
+	if (!(acptr = find_user(nick, NULL)))
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found");
+		return;
+	}
+
+	args[0] = NULL;
+	args[1] = acptr->name;
+	args[2] = channel;
+	args[3] = reason;
+	args[4] = NULL;
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_cmd(&me, NULL, force ? "SAPART" : "SVSPART", reason ? 4 : 3, args);
+	safe_free_message_tags(mtags);
+
+	/* Return result. Always 'true' at the moment.
+	 * Technically we could check if the user is in all of these channels.
+	 * But then again, do we really want to return failure if one specified
+	 * channel does not exist out of X channels to be parted? Not worth it.
+	 */
+	result = json_boolean(1);
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rpc/whowas.c b/src/modules/rpc/whowas.c
@@ -0,0 +1,148 @@
+/* whowas.* RPC calls
+ * (C) Copyright 2023-.. Bram Matthys (Syzop) and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+= {
+	"rpc/whowas",
+	"1.0.0",
+	"whowas.* RPC calls",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Externals */
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+
+/* Forward declarations */
+RPC_CALL_FUNC(rpc_whowas_get);
+
+MOD_INIT()
+{
+	RPCHandlerInfo r;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&r, 0, sizeof(r));
+	r.method = "whowas.get";
+	r.loglevel = ULOG_DEBUG;
+	r.call = rpc_whowas_get;
+	if (!RPCHandlerAdd(modinfo->handle, &r))
+	{
+		config_error("[rpc/whowas] Could not register RPC handler");
+		return MOD_FAILED;
+	}
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+const char *whowas_event_to_string(WhoWasEvent event)
+{
+	if (event == WHOWAS_EVENT_QUIT)
+		return "quit";
+	if (event == WHOWAS_EVENT_NICK_CHANGE)
+		return "nick-change";
+	if (event == WHOWAS_EVENT_SERVER_TERMINATING)
+		return "server-terminating";
+	return "unknown";
+}
+
+void json_expand_whowas(json_t *j, const char *key, WhoWas *e, int detail)
+{
+	json_t *child;
+	json_t *user = NULL;
+	char buf[BUFSIZE+1];
+
+	if (key)
+	{
+		child = json_object();
+		json_object_set_new(j, key, child);
+	} else {
+		child = j;
+	}
+
+	json_object_set_new(child, "name", json_string_unreal(e->name));
+	json_object_set_new(child, "event", json_string_unreal(whowas_event_to_string(e->event)));
+	json_object_set_new(child, "logon_time", json_timestamp(e->logon));
+	json_object_set_new(child, "logoff_time", json_timestamp(e->logoff));
+
+	if (detail == 0)
+		return;
+
+	json_object_set_new(child, "hostname", json_string_unreal(e->hostname));
+	json_object_set_new(child, "ip", json_string_unreal(e->ip));
+
+	snprintf(buf, sizeof(buf), "%s!%s@%s", e->name, e->username, e->hostname);
+	json_object_set_new(child, "details", json_string_unreal(buf));
+
+	if (detail < 2)
+		return;
+
+	if (e->connected_since)
+		json_object_set_new(child, "connected_since", json_timestamp(e->connected_since));
+
+	/* client.user */
+	user = json_object();
+	json_object_set_new(child, "user", user);
+
+	json_object_set_new(user, "username", json_string_unreal(e->username));
+	if (!BadPtr(e->realname))
+		json_object_set_new(user, "realname", json_string_unreal(e->realname));
+	if (!BadPtr(e->virthost))
+		json_object_set_new(user, "vhost", json_string_unreal(e->virthost));
+	json_object_set_new(user, "servername", json_string_unreal(e->servername));
+	if (!BadPtr(e->account))
+		json_object_set_new(user, "account", json_string_unreal(e->account));
+}
+
+RPC_CALL_FUNC(rpc_whowas_get)
+{
+	json_t *result, *list, *item;
+	int details;
+	int i;
+	const char *nick;
+	const char *ip;
+
+	OPTIONAL_PARAM_STRING("nick", nick);
+	OPTIONAL_PARAM_STRING("ip", ip);
+	OPTIONAL_PARAM_INTEGER("object_detail_level", details, 2);
+	if (details == 3)
+	{
+		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Using an 'object_detail_level' of 3 is not allowed in user.* calls, use 0, 1, 2 or 4.");
+		return;
+	}
+
+	result = json_object();
+	list = json_array();
+	json_object_set_new(result, "list", list);
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (!e->name)
+			continue;
+		if (nick && !match_simple(nick, e->name))
+			continue;
+		if (ip && !match_simple(ip, e->ip))
+			continue;
+		item = json_object();
+		json_expand_whowas(item, NULL, e, details);
+		json_array_append_new(list, item);
+	}
+
+	rpc_response(client, request, result);
+	json_decref(result);
+}
diff --git a/src/modules/rules.c b/src/modules/rules.c
@@ -58,19 +58,16 @@ MOD_UNLOAD()
  */
 CMD_FUNC(cmd_rules)
 {
-	ConfigItem_tld *ptr;
+	ConfigItem_tld *tld;
 	MOTDLine *temp;
 
-	temp = NULL;
-
 	if (hunt_server(client, recv_mtags, "RULES", 1, parc, parv) != HUNTED_ISME)
 		return;
 
-	ptr = find_tld(client);
-
-	if (ptr)
-		temp = ptr->rules.lines;
-	if (!temp)
+	tld = find_tld(client);
+	if (tld && tld->rules.lines)
+		temp = tld->rules.lines;
+	else
 		temp = rules.lines;
 
 	if (temp == NULL)
diff --git a/src/modules/sajoin.c b/src/modules/sajoin.c
@@ -52,11 +52,20 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static void log_sajoin(Client *client, Client *target, const char *channels)
+static void log_sajoin(Client *client, MessageTag *mtags, Client *target, const char *channels)
 {
-	unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $client used SAJOIN to make $target join $channels",
-		   log_data_client("target", target),
-		   log_data_string("channels", channels));
+	const char *issuer = command_issued_by_rpc(mtags);
+	if (issuer)
+	{
+		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $issuer used SAJOIN to make $target join $channels",
+			   log_data_string("issuer", issuer),
+			   log_data_client("target", target),
+			   log_data_string("channels", channels));
+	} else {
+		unreal_log(ULOG_INFO, "sacmds", "SAJOIN_COMMAND", client, "SAJOIN: $client used SAJOIN to make $target join $channels",
+			   log_data_client("target", target),
+			   log_data_string("channels", channels));
+	}
 }
 
 /* cmd_sajoin() - Lamego - Wed Jul 21 20:04:48 1999
@@ -97,10 +106,10 @@ CMD_FUNC(cmd_sajoin)
 	/* Broadcast so other servers can log it appropriately as an SAJOIN */
 	sendto_server(client, 0, 0, recv_mtags, ":%s SAJOIN %s %s", client->id, target->id, parv[2]);
 
-	/* If it's not for our client, then simply pass on the message... */
+	/* If it's not for our client, then only log... */
 	if (!MyUser(target))
 	{
-		log_sajoin(client, target, parv[2]);
+		log_sajoin(client, recv_mtags, target, parv[2]);
 		return;
 	}
 
@@ -212,6 +221,7 @@ CMD_FUNC(cmd_sajoin)
 					channel = lp->channel;
 
 					new_message(target, NULL, &mtags);
+					mtag_add_issued_by(&mtags, client, recv_mtags);
 					sendto_channel(channel, target, NULL, 0, 0, SEND_LOCAL, mtags,
 					               ":%s PART %s :%s",
 					               target->name, channel->name, "Left all channels");
@@ -246,6 +256,7 @@ CMD_FUNC(cmd_sajoin)
 			 * Each with their own unique msgid.
 			 */
 			new_message(target, NULL, &mtags);
+			mtag_add_issued_by(&mtags, client, recv_mtags);
 			join_channel(channel, target, mtags, member_modes);
 			if (prefix)
 			{
@@ -273,11 +284,11 @@ CMD_FUNC(cmd_sajoin)
 				strlcat(jbuf, ",", sizeof jbuf);
 			strlcat(jbuf, name, sizeof jbuf);
 		}
-		
-		if (did_anything)
-		{
-			//sendnotice(target, "*** You were forced to join %s", jbuf);
-			log_sajoin(client, target, jbuf);
-		}
+
+		//if (did_anything)
+		//{
+		//	sendnotice(target, "*** You were forced to join %s", jbuf);
+		//	log_sajoin(client, recv_mtags, target, jbuf);
+		//}
 	}
 }
diff --git a/src/modules/samode.c b/src/modules/samode.c
@@ -61,6 +61,7 @@ MOD_UNLOAD()
 CMD_FUNC(cmd_samode)
 {
 	Channel *channel;
+	MessageTag *mtags = NULL;
 
 	if (parc <= 2)
 	{
@@ -82,5 +83,7 @@ CMD_FUNC(cmd_samode)
 	}
 
 	opermode = 0;
-	do_mode(channel, client, NULL, parc - 2, parv + 2, 0, 1);
+	mtag_add_issued_by(&mtags, client, NULL);
+	do_mode(channel, client, mtags, parc - 2, parv + 2, 0, 1);
+	safe_free_message_tags(mtags);
 }
diff --git a/src/modules/sapart.c b/src/modules/sapart.c
@@ -52,20 +52,41 @@ MOD_UNLOAD()
 	return MOD_SUCCESS;
 }
 
-static void log_sapart(Client *client, Client *target, const char *channels, const char *comment)
+static void log_sapart(Client *client, MessageTag *mtags, Client *target, const char *channels, const char *comment)
 {
-	if (comment)
-	{
-		unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels ($reason)",
-			   log_data_client("target", target),
-			   log_data_string("channels", channels),
-			   log_data_string("reason", comment));
-	}
-	else
+	const char *issuer = command_issued_by_rpc(mtags);
+
+	if (issuer)
 	{
-		unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels",
-			   log_data_client("target", target),
-			   log_data_string("channels", channels));
+		if (comment)
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels ($reason)",
+				   log_data_string("issuer", issuer),
+				   log_data_client("target", target),
+				   log_data_string("channels", channels),
+				   log_data_string("reason", comment));
+		}
+		else
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $issuer used SAPART to make $target part $channels",
+				   log_data_string("issuer", issuer),
+				   log_data_client("target", target),
+				   log_data_string("channels", channels));
+		}
+	} else {
+		if (comment)
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels ($reason)",
+				   log_data_client("target", target),
+				   log_data_string("channels", channels),
+				   log_data_string("reason", comment));
+		}
+		else
+		{
+			unreal_log(ULOG_INFO, "sacmds", "SAPART_COMMAND", client, "SAPART: $client used SAPART to make $target part $channels",
+				   log_data_client("target", target),
+				   log_data_string("channels", channels));
+		}
 	}
 }
 
@@ -83,6 +104,7 @@ CMD_FUNC(cmd_sapart)
 {
 	Client *target;
 	Channel *channel;
+	MessageTag *mtags = NULL;
 	Membership *lp;
 	char *name, *p = NULL;
 	int i;
@@ -120,7 +142,7 @@ CMD_FUNC(cmd_sapart)
 
 	if (!MyUser(target))
 	{
-		log_sapart(client, target, parv[2], comment);
+		log_sapart(client, recv_mtags, target, parv[2], comment);
 		return;
 	}
 
@@ -165,24 +187,24 @@ CMD_FUNC(cmd_sapart)
 
 	strlcpy(request, jbuf, sizeof(request));
 
-	log_sapart(client, target, request, comment);
+	log_sapart(client, recv_mtags, target, request, comment);
 
-/***
-	if (comment)
-	{
-		snprintf(commentx, sizeof(commentx), "SAPart: %s", comment);
-		sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
-	} else {
-		sendnotice(target, "*** You were forced to part %s", request);
-	}
-***/
+	//if (comment)
+	//{
+	//	snprintf(commentx, sizeof(commentx), "SAPart: %s", comment);
+	//	sendnotice(target, "*** You were forced to part %s (%s)", request, commentx);
+	//} else {
+	//	sendnotice(target, "*** You were forced to part %s", request);
+	//}
 
 	parv[0] = target->name; // nick
 	parv[1] = request; // chan
 	parv[2] = comment ? commentx : NULL; // comment
 
 	/* Now, do the actual parting: */
-	do_cmd(target, NULL, "PART", comment ? 3 : 2, parv);
+	mtag_add_issued_by(&mtags, client, recv_mtags);
+	do_cmd(target, mtags, "PART", comment ? 3 : 2, parv);
+	safe_free_message_tags(mtags);
 
 	/* NOTE: target may be killed now due to the part reason @ spamfilter */
 }
diff --git a/src/modules/sasl.c b/src/modules/sasl.c
@@ -43,7 +43,6 @@ EVENT(sasl_timeout);
 /* Macros */
 #define MSG_AUTHENTICATE "AUTHENTICATE"
 #define MSG_SASL "SASL"
-#define MSG_SVSLOGIN "SVSLOGIN"
 #define AGENT_SID(agent_p)	(agent_p->user != NULL ? agent_p->user->server : agent_p->name)
 
 /* Variables */
@@ -89,53 +88,6 @@ int sasl_account_login(Client *client, MessageTag *mtags)
 	return 0;
 }
 
-/*
- * SVSLOGIN message
- *
- * parv[1]: propagation mask
- * parv[2]: target
- * parv[3]: account name (SVID)
- */
-CMD_FUNC(cmd_svslogin)
-{
-	Client *target;
-
-	if (MyUser(client) || (parc < 3) || !parv[3])
-		return;
-
-	/* We actually ignore parv[1] since this is a broadcast message.
-	 * It is a broadcast message because we want ALL servers to know
-	 * that the user is now logged in under account xyz.
-	 */
-
-	target = find_client(parv[2], NULL);
-	if (target)
-	{
-		if (IsServer(target))
-			return;
-
-		if (target->user == NULL)
-			make_user(target);
-
-		strlcpy(target->user->account, parv[3], sizeof(target->user->account));
-		user_account_login(recv_mtags, target);
-		if (MyConnect(target) && IsDead(target))
-			return; /* was killed due to *LINE on ~a probably */
-	} else {
-		/* It is perfectly normal for target to be NULL as this
-		 * happens during registration phase (pre-connect).
-		 * It just means we cannot set any properties for this user,
-		 * which is fine in that case, since it will be synced via
-		 * the UID message instead.
-		 * We still have to broadcast the message, which is why
-		 * we do not return here.
-		 */
-	}
-
-	/* Propagate to the rest of the network */
-	sendto_server(client, 0, 0, NULL, ":%s SVSLOGIN %s %s %s",
-	              client->name, parv[1], parv[2], parv[3]);
-}
 
 /*
  * SASL message
@@ -227,6 +179,9 @@ CMD_FUNC(cmd_authenticate)
 		return;
 	}
 
+	if (client->user == NULL)
+		make_user(client);
+
 	if (*client->local->sasl_agent)
 		agent_p = find_client(client->local->sasl_agent, NULL);
 
@@ -370,7 +325,6 @@ MOD_INIT()
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
 	CommandAdd(modinfo->handle, MSG_SASL, cmd_sasl, MAXPARA, CMD_USER|CMD_SERVER);
-	CommandAdd(modinfo->handle, MSG_SVSLOGIN, cmd_svslogin, MAXPARA, CMD_USER|CMD_SERVER);
 	CommandAdd(modinfo->handle, MSG_AUTHENTICATE, cmd_authenticate, MAXPARA, CMD_UNREGISTERED|CMD_USER);
 
 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, sasl_connect);
diff --git a/src/modules/server-time.c b/src/modules/server-time.c
@@ -91,26 +91,9 @@ void mtag_add_or_inherit_time(Client *sender, MessageTag *recv_mtags, MessageTag
 		m = duplicate_mtag(m);
 	} else
 	{
-		struct timeval t;
-		struct tm *tm;
-		time_t sec;
-		char buf[64];
-
-		gettimeofday(&t, NULL);
-		sec = t.tv_sec;
-		tm = gmtime(&sec);
-		snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
-			tm->tm_year + 1900,
-			tm->tm_mon + 1,
-			tm->tm_mday,
-			tm->tm_hour,
-			tm->tm_min,
-			tm->tm_sec,
-			(int)(t.tv_usec / 1000));
-
 		m = safe_alloc(sizeof(MessageTag));
 		safe_strdup(m->name, "time");
-		safe_strdup(m->value, buf);
+		safe_strdup(m->value, timestamp_iso8601_now());
 	}
 	AddListItem(m, *mtag_list);
 }
diff --git a/src/modules/server.c b/src/modules/server.c
@@ -36,8 +36,19 @@ struct cfgstruct {
 	long handshake_timeout;
 };
 
+typedef struct ConfigItem_deny_link ConfigItem_deny_link;
+struct ConfigItem_deny_link {
+	ConfigItem_deny_link *prev, *next;
+	ConfigFlag_except flag;
+	ConfigItem_mask  *mask;
+	CRuleNode *rule; /**< parsed crule */
+	char *prettyrule; /**< human printable version */
+	char *reason; /**< Reason for the deny link */
+};
+
 /* Forward declarations */
 void server_config_setdefaults(cfgstruct *cfg);
+void server_config_free();
 int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
 int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
 EVENT(server_autoconnect);
@@ -57,10 +68,15 @@ void server_generic_free(ModData *m);
 int server_post_connect(Client *client);
 void _connect_server(ConfigItem_link *aconf, Client *by, struct hostent *hp);
 static int connect_server_helper(ConfigItem_link *, Client *);
+int _is_services_but_not_ulined(Client *client);
+const char *_check_deny_link(ConfigItem_link *link, int auto_connect);
+int server_stats_denylink_all(Client *client, const char *para);
+int server_stats_denylink_auto(Client *client, const char *para);
 
 /* Global variables */
 static cfgstruct cfg;
 static char *last_autoconnect_server = NULL;
+static ConfigItem_deny_link *conf_deny_link = NULL;
 
 ModuleHeader MOD_HEADER
   = {
@@ -81,6 +97,8 @@ MOD_TEST()
 	EfunctionAdd(modinfo->handle, EFUNC_CHECK_DENY_VERSION, _check_deny_version);
 	EfunctionAddVoid(modinfo->handle, EFUNC_BROADCAST_SINFO, _broadcast_sinfo);
 	EfunctionAddVoid(modinfo->handle, EFUNC_CONNECT_SERVER, _connect_server);
+	EfunctionAdd(modinfo->handle, EFUNC_IS_SERVICES_BUT_NOT_ULINED, _is_services_but_not_ulined);
+	EfunctionAddConstString(modinfo->handle, EFUNC_CHECK_DENY_LINK, _check_deny_link);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, server_config_test);
 	return MOD_SUCCESS;
 }
@@ -92,6 +110,8 @@ MOD_INIT()
 	server_config_setdefaults(&cfg);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, server_config_run);
 	HookAdd(modinfo->handle, HOOKTYPE_POST_SERVER_CONNECT, 0, server_post_connect);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_all);
+	HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, server_stats_denylink_auto);
 	CommandAdd(modinfo->handle, "SERVER", cmd_server, MAXPARA, CMD_UNREGISTERED|CMD_SERVER);
 	CommandAdd(modinfo->handle, "SID", cmd_sid, MAXPARA, CMD_SERVER);
 
@@ -107,6 +127,7 @@ MOD_LOAD()
 
 MOD_UNLOAD()
 {
+	server_config_free();
 	SavePersistentPointer(modinfo, last_autoconnect_server);
 	return MOD_SUCCESS;
 }
@@ -152,17 +173,27 @@ void server_config_setdefaults(cfgstruct *cfg)
 	cfg->handshake_timeout = 20;
 }
 
-int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+void server_config_free(void)
 {
-	int errors = 0;
-	ConfigEntry *cep;
+	ConfigItem_deny_link *d, *d_next;
 
-	if (type != CONFIG_SET)
-		return 0;
+	for (d = conf_deny_link; d; d = d_next)
+	{
+		d_next = d->next;
+		unreal_delete_masks(d->mask);
+		crule_free(&d->rule);
+		safe_free(d->prettyrule);
+		safe_free(d->reason);
+		DelListItem(d, conf_deny_link);
+		safe_free(d);
+	}
+	conf_deny_link = NULL;
+}
 
-	/* We are only interrested in set::server-linking.. */
-	if (!ce || strcmp(ce->name, "server-linking"))
-		return 0;
+int server_config_test_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
@@ -218,17 +249,10 @@ int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 	return errors ? -1 : 1;
 }
 
-int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+int server_config_run_set_server_linking(ConfigFile *cf, ConfigEntry *ce, int type)
 {
 	ConfigEntry *cep;
 
-	if (type != CONFIG_SET)
-		return 0;
-
-	/* We are only interrested in set::server-linking.. */
-	if (!ce || strcmp(ce->name, "server-linking"))
-		return 0;
-
 	for (cep = ce->items; cep; cep = cep->next)
 	{
 		if (!strcmp(cep->name, "autoconnect-strategy"))
@@ -244,12 +268,177 @@ int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 			cfg.handshake_timeout = config_checkval(cep->value, CFG_TIME);
 		}
 	}
+
 	return 1;
 }
 
-int server_needs_linking(ConfigItem_link *aconf)
+int server_config_test_deny_link(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 {
+        int errors = 0;
+        ConfigEntry *cep;
+	char has_mask = 0, has_rule = 0, has_type = 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->items)
+		{
+			if (config_is_blankorempty(cep, "deny link"))
+			{
+				errors++;
+				continue;
+			}
+			else if (!strcmp(cep->name, "mask"))
+			{
+				has_mask = 1;
+			} else if (!strcmp(cep->name, "rule"))
+			{
+				int val = 0;
+				if (has_rule)
+				{
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny link::rule");
+					continue;
+				}
+				has_rule = 1;
+				if ((val = crule_test(cep->value)))
+				{
+					config_error("%s:%i: deny link::rule contains an invalid expression: %s",
+						cep->file->filename,
+						cep->line_number,
+						crule_errstring(val));
+					errors++;
+				}
+			}
+			else if (!strcmp(cep->name, "type"))
+			{
+				if (has_type)
+				{
+					config_warn_duplicate(cep->file->filename,
+						cep->line_number, "deny link::type");
+					continue;
+				}
+				has_type = 1;
+				if (!strcmp(cep->value, "auto"))
+				;
+				else if (!strcmp(cep->value, "all"))
+				;
+				else {
+					config_status("%s:%i: unknown deny link type",
+					cep->file->filename, cep->line_number);
+					errors++;
+				}
+			} else if (!strcmp(cep->name, "reason"))
+			{
+			}
+			else
+			{
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny link", cep->name);
+				errors++;
+			}
+		}
+		else
+		{
+			// Sections
+			if (!strcmp(cep->name, "mask"))
+			{
+				if (cep->value || cep->items)
+					has_mask = 1;
+			}
+			else
+			{
+				config_error_unknown(cep->file->filename,
+					cep->line_number, "deny link", cep->name);
+				errors++;
+				continue;
+			}
+		}
+	}
+	if (!has_mask)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::mask");
+		errors++;
+	}
+	if (!has_rule)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::rule");
+		errors++;
+	}
+	if (!has_type)
+	{
+		config_error_missing(ce->file->filename, ce->line_number,
+			"deny link::type");
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int server_config_run_deny_link(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
 	ConfigItem_deny_link *deny;
+
+	deny = safe_alloc(sizeof(ConfigItem_deny_link));
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "mask"))
+		{
+			unreal_add_masks(&deny->mask, cep);
+		}
+		else if (!strcmp(cep->name, "rule"))
+		{
+			deny->rule = crule_parse(cep->value);
+			safe_strdup(deny->prettyrule, cep->value);
+		}
+		else if (!strcmp(cep->name, "reason"))
+		{
+			safe_strdup(deny->reason, cep->value);
+		}
+		else if (!strcmp(cep->name, "type")) {
+			if (!strcmp(cep->value, "all"))
+				deny->flag.type = CRULE_ALL;
+			else if (!strcmp(cep->value, "auto"))
+				deny->flag.type = CRULE_AUTO;
+		}
+	}
+
+	/* Set a default reason, if needed */
+	if (!deny->reason)
+		safe_strdup(deny->reason, "Denied");
+
+	AddListItem(deny, conf_deny_link);
+	return 1;
+}
+
+int server_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	if ((type == CONFIG_SET) && !strcmp(ce->name, "server-linking"))
+		return server_config_test_set_server_linking(cf, ce, type, errs);
+
+	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
+		return server_config_test_deny_link(cf, ce, type, errs);
+
+	return 0; /* not for us */
+}
+
+int server_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	if ((type == CONFIG_SET) && ce && !strcmp(ce->name, "server-linking"))
+		return server_config_run_set_server_linking(cf, ce, type);
+
+	if ((type == CONFIG_DENY) && !strcmp(ce->value, "link"))
+		return server_config_run_deny_link(cf, ce, type);
+
+	return 0; /* not for us */
+}
+
+int server_needs_linking(ConfigItem_link *aconf)
+{
 	Client *client;
 	ConfigItem_class *class;
 
@@ -278,9 +467,8 @@ int server_needs_linking(ConfigItem_link *aconf)
 		return 0; /* Class is full */
 
 	/* Check connect rules to see if we're allowed to try the link */
-	for (deny = conf_deny_link; deny; deny = deny->next)
-		if (unreal_mask_match_string(aconf->servername, deny->mask) && crule_eval(deny->rule))
-			return 0;
+	if (check_deny_link(aconf, 1))
+		return 0;
 
 	/* Yes, this server is a linking candidate */
 	return 1;
@@ -590,8 +778,8 @@ void _send_protoctl_servers(Client *client, int response)
 	Client *acptr;
 	int sendit = 1;
 
-	sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,%s",
-		me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", version);
+	sendto_one(client, NULL, "PROTOCTL EAUTH=%s,%d,%s%s,UnrealIRCd-%s",
+		me.name, UnrealProtocol, serveropts, extraflags ? extraflags : "", buildid);
 		
 	ircsnprintf(buf, sizeof(buf), "PROTOCTL SERVERS=%s", response ? "*" : "");
 
@@ -684,9 +872,7 @@ ConfigItem_link *_verify_link(Client *client)
 		goto skip_host_check;
 	} else {
 		/* Hunt the linkblock down ;) */
-		for(link = conf_link; link; link = link->next)
-			if (match_simple(link->servername, client->name))
-				break;
+		link = find_link(client->name);
 	}
 	
 	if (!link)
@@ -707,9 +893,7 @@ ConfigItem_link *_verify_link(Client *client)
 	}
 
 	orig_link = link;
-	link = find_link(client->name, client);
-
-	if (!link)
+	if (!user_allowed_by_security_group(client, link->incoming.match))
 	{
 		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_INCOMING_MASK_MISMATCH", client,
 		           "Link with server $client.details denied: Server is in link block but link::incoming::mask didn't match",
@@ -876,9 +1060,9 @@ CMD_FUNC(cmd_server)
 	int  hop = 0;
 	char info[REALLEN + 61];
 	ConfigItem_link *aconf = NULL;
-	ConfigItem_deny_link *deny;
 	char *flags = NULL, *protocol = NULL, *inf = NULL, *num = NULL;
 	int incoming;
+	const char *err;
 
 	if (IsUser(client))
 	{
@@ -996,18 +1180,14 @@ CMD_FUNC(cmd_server)
 		strlcpy(client->info, info[0] ? info : "server", sizeof(client->info));
 	}
 
-	/* Process deny server { } restrictions */
-	for (deny = conf_deny_link; deny; deny = deny->next)
+	if ((err = check_deny_link(aconf, 0)))
 	{
-		if (deny->flag.type == CRULE_ALL && unreal_mask_match_string(servername, deny->mask)
-			&& crule_eval(deny->rule))
-		{
-			unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DENY_LINK_BLOCK", client,
-			           "Server link $servername rejected by deny link { } block.",
-			           log_data_string("servername", servername));
-			exit_client(client, NULL, "Disallowed by connection rule");
-			return;
-		}
+		unreal_log(ULOG_ERROR, "link", "LINK_DENIED_DENY_LINK_BLOCK", client,
+			   "Server link $servername rejected by deny link { } block: $reason",
+			   log_data_string("servername", servername),
+			   log_data_string("reason", err));
+		exit_client_fmt(client, NULL, "Disallowed by connection rule: %s", err);
+		return;
 	}
 
 	if (aconf->options & CONNECT_QUARANTINE)
@@ -1991,3 +2171,89 @@ static int connect_server_helper(ConfigItem_link *aconf, Client *client)
 	return 1;
 }
 
+int _is_services_but_not_ulined(Client *client)
+{
+	if (!client->server || !client->server->features.software || !*client->name)
+		return 0; /* cannot detect software version or name not available yet */
+
+	if (our_strcasestr(client->server->features.software, "anope") ||
+	    our_strcasestr(client->server->features.software, "atheme"))
+	{
+		if (!find_uline(client->name))
+		{
+			unreal_log(ULOG_ERROR, "link", "LINK_NO_ULINES", client,
+			           "Server $client is a services server ($software). "
+			           "However, server $me does not have $client in the ulines { } block, "
+			           "which is required for services servers. "
+			           "See https://www.unrealircd.org/docs/Ulines_block",
+			           log_data_client("me", &me),
+			           log_data_string("software", client->server->features.software));
+			return 1; /* Is services AND no ulines { } entry */
+		}
+	}
+	return 0;
+}
+
+/** Check if this link should be denied due to deny link { } configuration
+ * @param link		The link block
+ * @param auto_connect	Set this to 1 if this is called from auto connect code
+ *			(it will then check both CRULE_AUTO + CRULE_ALL)
+ *			set it to 0 otherwise (will not check CRULE_AUTO blocks).
+ * @returns The deny block if the server should be denied, or NULL if no deny block.
+ */
+const char *_check_deny_link(ConfigItem_link *link, int auto_connect)
+{
+	ConfigItem_deny_link *d;
+
+	for (d = conf_deny_link; d; d = d->next)
+	{
+		if ((auto_connect == 0) && (d->flag.type == CRULE_AUTO))
+			continue;
+		if (unreal_mask_match_string(link->servername, d->mask) &&
+		    crule_eval(d->rule))
+		{
+			return d->reason;
+		}
+	}
+	return NULL;
+}
+
+int server_stats_denylink_all(Client *client, const char *para)
+{
+	ConfigItem_deny_link *links;
+	ConfigItem_mask *m;
+
+	if (!para || !(!strcmp(para, "D") || !strcasecmp(para, "denylinkall")))
+		return 0;
+
+	for (links = conf_deny_link; links; links = links->next)
+	{
+		if (links->flag.type == CRULE_ALL)
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'D', m->mask, links->prettyrule);
+		}
+	}
+
+	return 1;
+}
+
+int server_stats_denylink_auto(Client *client, const char *para)
+{
+	ConfigItem_deny_link *links;
+	ConfigItem_mask *m;
+
+	if (!para || !(!strcmp(para, "d") || !strcasecmp(para, "denylinkauto")))
+		return 0;
+
+	for (links = conf_deny_link; links; links = links->next)
+	{
+		if (links->flag.type == CRULE_AUTO)
+		{
+			for (m = links->mask; m; m = m->next)
+				sendnumeric(client, RPL_STATSDLINE, 'd', m->mask, links->prettyrule);
+		}
+	}
+
+	return 1;
+}
diff --git a/src/modules/setident.c b/src/modules/setident.c
@@ -103,15 +103,10 @@ CMD_FUNC(cmd_setident)
 	}
 
 	/* Check if the new ident contains illegal characters */
-	for (s = vident; *s; s++)
+	if (!valid_username(vident))
 	{
-		if ((*s == '~') && (s == vident))
-			continue;
-		if (!isallowed(*s))
-		{
-			sendnotice(client, "*** /SETIDENT Error: A username may contain a-z, A-Z, 0-9, '-', '~' & '.'.");
-			return;
-		}
+		sendnotice(client, "*** /SETIDENT Error: A username may contain a-z, A-Z, 0-9, '-', '~' & '.'.");
+		return;
 	}
 
 	userhost_save_current(client);
diff --git a/src/modules/sinfo.c b/src/modules/sinfo.c
@@ -104,6 +104,14 @@ CMD_FUNC(sinfo_server)
 	else
 		safe_strdup(client->server->features.software, parv[parc-1]);
 
+	if (is_services_but_not_ulined(client))
+	{
+		char buf[512];
+		snprintf(buf, sizeof(buf), "Services detected but no ulines { } for server name %s", client->name);
+		exit_client_ex(client, &me, NULL, buf);
+		return;
+	}
+
 	/* Broadcast to 'the other side' of the net */
 	concat_params(buf, sizeof(buf), parc, parv);
 	sendto_server(client, 0, 0, NULL, ":%s SINFO %s", client->id, buf);
@@ -157,7 +165,7 @@ CMD_FUNC(sinfo_user)
 CMD_FUNC(cmd_sinfo)
 {
 	if (IsServer(client))
-		sinfo_server(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(sinfo_server);
 	else if (MyUser(client))
-		sinfo_user(client, recv_mtags, parc, parv);
+		CALL_CMD_FUNC(sinfo_user);
 }
diff --git a/src/modules/sjoin.c b/src/modules/sjoin.c
@@ -452,6 +452,10 @@ CMD_FUNC(cmd_sjoin)
 				MessageTag *mtags = NULL;
 
 				add_user_to_channel(channel, acptr, item_modes);
+				unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", acptr,
+					   "User $client joined $channel",
+					   log_data_channel("channel", channel),
+					   log_data_string("modes", item_modes));
 				RunHook(HOOKTYPE_REMOTE_JOIN, acptr, channel, recv_mtags);
 				new_message_special(acptr, recv_mtags, &mtags, ":%s JOIN %s", acptr->name, channel->name);
 				send_join_to_local_users(acptr, channel, mtags);
diff --git a/src/modules/slog.c b/src/modules/slog.c
@@ -157,7 +157,7 @@ CMD_FUNC(cmd_slog)
 	json_serialized = json_dumps(j, JSON_COMPACT);
 
 	if (json_serialized)
-		do_unreal_log_internal_from_remote(loglevel, subsystem, event_id, mmsg, json_serialized, client);
+		do_unreal_log_internal_from_remote(loglevel, subsystem, event_id, mmsg, j, json_serialized, client);
 
 	/* Broadcast to the other servers */
 	sendto_server(client, 0, 0, recv_mtags, ":%s SLOG %s %s %s :%s",
diff --git a/src/modules/sreply.c b/src/modules/sreply.c
@@ -0,0 +1,87 @@
+/*
+ *   Unreal Internet Relay Chat Daemon, src/modules/sreply.c
+ *   (C) 2022 Valware and the UnrealIRCd Team
+ * 
+ *   Allows services to send Standard Replies in response to non-privmsg commands:
+ *   https://ircv3.net/specs/extensions/standard-replies
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+CMD_FUNC(cmd_sreply);
+
+ModuleHeader MOD_HEADER
+  = {
+	"sreply",	/* Name of module */
+	"1.0", /* Version */
+	"Server command SREPLY", /* Short description of module */
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+/* This is called on module init, before Server Ready */
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, "SREPLY", cmd_sreply, MAXPARA, CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+/* Is first run when server is 100% ready */
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/* Called when module is unloaded */
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;	
+}
+
+/**
+ * cmd_sreply
+ * @param parv[1]		Nick|UID
+ * @param parv[2]		"F", "W" or "N" for FAIL, WARN and NOTE.
+ * @param parv[3]		The rest of the message
+*/
+CMD_FUNC(cmd_sreply)
+{
+	Client *target;
+
+	if (parc < 4)
+		return;
+
+	target = find_user(parv[1], NULL);
+	if (!target && !(target = find_server_by_uid(parv[1])))
+		return;
+
+	if (!MyUser(target))
+	{
+		/* Target is a remote user/server */
+		sendto_one(target, recv_mtags, ":%s SREPLY %s %s :%s", client->name, parv[1], parv[2], parv[3]);
+		return;
+	}
+
+	/* For a locally connected user... */
+	if (!strcmp(parv[2],"F"))
+		sendto_one(target, recv_mtags, ":%s FAIL %s", client->name, parv[3]);
+	else if (!strcmp(parv[2],"W"))
+		sendto_one(target, recv_mtags, ":%s WARN %s", client->name, parv[3]);
+	else if (!strcmp(parv[2],"N"))
+		sendto_one(target, recv_mtags, ":%s NOTE %s", client->name, parv[3]);
+}
diff --git a/src/modules/standard-replies.c b/src/modules/standard-replies.c
@@ -0,0 +1,58 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/standard-replies.c
+ *   (C) 2023 Syzop & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"standard-replies",
+	"6.0",
+	"standard-replies CAP", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+	};
+
+/* Variables */
+long CAP_STANDARD_REPLIES = 0L;
+
+MOD_INIT()
+{
+	ClientCapabilityInfo cap;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&cap, 0, sizeof(cap));
+	cap.name = "standard-replies";
+	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_STANDARD_REPLIES);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
diff --git a/src/modules/stats.c b/src/modules/stats.c
@@ -56,7 +56,6 @@ extern MODVAR int  max_connection_count;
 
 int stats_banversion(Client *, const char *);
 int stats_links(Client *, const char *);
-int stats_denylinkall(Client *, const char *);
 int stats_gline(Client *, const char *);
 int stats_except(Client *, const char *);
 int stats_allow(Client *, const char *);
@@ -67,7 +66,6 @@ int stats_bannick(Client *, const char *);
 int stats_traffic(Client *, const char *);
 int stats_uline(Client *, const char *);
 int stats_vhost(Client *, const char *);
-int stats_denylinkauto(Client *, const char *);
 int stats_kline(Client *, const char *);
 int stats_banrealname(Client *, const char *);
 int stats_sqline(Client *, const char *);
@@ -101,7 +99,6 @@ struct statstab {
 struct statstab StatsTable[] = {
 	{ 'B', "banversion",	stats_banversion,	0		},
 	{ 'C', "link", 		stats_links,		0 		},
-	{ 'D', "denylinkall",	stats_denylinkall,	0		},
 	{ 'G', "gline",		stats_gline,		FLAGS_AS_PARA	},
 	{ 'H', "link",	 	stats_links,		0 		},
 	{ 'I', "allow",		stats_allow,		0 		},
@@ -119,7 +116,6 @@ struct statstab StatsTable[] = {
 	{ 'X', "notlink",	stats_notlink,		0 		},
 	{ 'Y', "class",		stats_class,		0 		},
 	{ 'c', "link", 		stats_links,		0 		},
-	{ 'd', "denylinkauto",	stats_denylinkauto,	0 		},
 	{ 'e', "except",	stats_except,		0 		},
 	{ 'f', "spamfilter",	stats_spamfilter,	FLAGS_AS_PARA	},
 	{ 'g', "gline",		stats_gline,		FLAGS_AS_PARA	},
@@ -444,22 +440,6 @@ int stats_links(Client *client, const char *para)
 	return 0;
 }
 
-int stats_denylinkall(Client *client, const char *para)
-{
-	ConfigItem_deny_link *links;
-	ConfigItem_mask *m;
-
-	for (links = conf_deny_link; links; links = links->next)
-	{
-		if (links->flag.type == CRULE_ALL)
-		{
-			for (m = links->mask; m; m = m->next)
-				sendnumeric(client, RPL_STATSDLINE, 'D', m->mask, links->prettyrule);
-		}
-	}
-	return 0;
-}
-
 int stats_gline(Client *client, const char *para)
 {
 	int cnt = 0;
@@ -686,22 +666,6 @@ int stats_vhost(Client *client, const char *para)
 	return 0;
 }
 
-int stats_denylinkauto(Client *client, const char *para)
-{
-	ConfigItem_deny_link *links;
-	ConfigItem_mask *m;
-
-	for (links = conf_deny_link; links; links = links->next)
-	{
-		if (links->flag.type == CRULE_AUTO)
-		{
-			for (m = links->mask; m; m = m->next)
-				sendnumeric(client, RPL_STATSDLINE, 'd', m->mask, links->prettyrule);
-		}
-	}
-	return 0;
-}
-
 int stats_kline(Client *client, const char *para)
 {
 	int cnt = 0;
diff --git a/src/modules/svsjoin.c b/src/modules/svsjoin.c
@@ -63,7 +63,7 @@ CMD_FUNC(cmd_svsjoin)
 {
 	Client *target;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if ((parc < 3) || !(target = find_user(parv[1], NULL)))
diff --git a/src/modules/svskill.c b/src/modules/svskill.c
@@ -73,7 +73,7 @@ CMD_FUNC(cmd_svskill)
 	if (parc == 3)
 		comment = parv[2];
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (!(target = find_user(parv[1], NULL)))
diff --git a/src/modules/svslogin.c b/src/modules/svslogin.c
@@ -0,0 +1,101 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/svslogin.c
+ *   (C) 2022 The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+#define MSG_SVSLOGIN "SVSLOGIN"
+
+CMD_FUNC(cmd_svslogin);
+
+ModuleHeader MOD_HEADER
+  = {
+	"svslogin",
+	"6.0",
+	"command /SVSLOGIN", 
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_SVSLOGIN, cmd_svslogin, MAXPARA, CMD_USER|CMD_SERVER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+ * SVSLOGIN message
+ *
+ * parv[1]: propagation mask
+ * parv[2]: target
+ * parv[3]: account name (SVID)
+ */
+CMD_FUNC(cmd_svslogin)
+{
+	Client *target;
+
+	if (MyUser(client) || (parc < 3) || !parv[3])
+		return;
+
+	/* We actually ignore parv[1] since this is a broadcast message.
+	 * It is a broadcast message because we want ALL servers to know
+	 * that the user is now logged in under account xyz.
+	 */
+
+	target = find_client(parv[2], NULL);
+	if (target)
+	{
+		if (IsServer(target))
+			return;
+
+		if (target->user == NULL)
+			make_user(target);
+
+		strlcpy(target->user->account, parv[3], sizeof(target->user->account));
+		user_account_login(recv_mtags, target);
+		if (MyConnect(target) && IsDead(target))
+			return; /* was killed due to *LINE on ~a probably */
+	} else {
+		/* It is perfectly normal for target to be NULL as this
+		 * happens during registration phase (pre-connect).
+		 * It just means we cannot set any properties for this user,
+		 * which is fine in that case, since it will be synced via
+		 * the UID message instead.
+		 * We still have to broadcast the message, which is why
+		 * we do not return here.
+		 */
+	}
+
+	/* Propagate to the rest of the network */
+	sendto_server(client, 0, 0, NULL, ":%s SVSLOGIN %s %s %s",
+	              client->name, parv[1], parv[2], parv[3]);
+}
+\ No newline at end of file
diff --git a/src/modules/svslusers.c b/src/modules/svslusers.c
@@ -64,7 +64,7 @@ MOD_UNLOAD()
 */
 CMD_FUNC(cmd_svslusers)
 {
-        if (!IsULine(client) || parc < 4)
+        if (!IsSvsCmdOk(client) || parc < 4)
 		return;  
         if (hunt_server(client, NULL, "SVSLUSERS", 1, parc, parv) == HUNTED_ISME)
         {
diff --git a/src/modules/svsmode.c b/src/modules/svsmode.c
@@ -326,7 +326,7 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *pa
 	int  what;
 	long oldumodes = 0;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	what = MODE_ADD;
@@ -520,11 +520,11 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *pa
 		} /*switch*/
 
 	if (parc > 3)
-		sendto_server(client, 0, 0, NULL, ":%s %s %s %s %s",
+		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s %s",
 		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
 		    parv[1], parv[2], parv[3]);
 	else
-		sendto_server(client, 0, 0, NULL, ":%s %s %s %s",
+		sendto_server(client, 0, 0, recv_mtags, ":%s %s %s %s",
 		    client->id, show_change ? "SVS2MODE" : "SVSMODE",
 		    parv[1], parv[2]);
 
@@ -539,7 +539,12 @@ void do_svsmode(Client *client, MessageTag *recv_mtags, int parc, const char *pa
 		char buf[BUFSIZE];
 		build_umode_string(target, oldumodes, ALL_UMODES, buf);
 		if (MyUser(target) && *buf)
-			sendto_one(target, NULL, ":%s MODE %s :%s", client->name, target->name, buf);
+		{
+			MessageTag *mtags = NULL;
+			new_message(client, recv_mtags, &mtags);
+			sendto_one(target, mtags, ":%s MODE %s :%s", client->name, target->name, buf);
+			safe_free_message_tags(mtags);
+		}
 	}
 
 	userhost_changed(target); /* we can safely call this, even if nothing changed */
diff --git a/src/modules/svsmotd.c b/src/modules/svsmotd.c
@@ -62,7 +62,7 @@ CMD_FUNC(cmd_svsmotd)
 {
 	FILE *conf = NULL;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 	{
 		sendnumeric(client, ERR_NOPRIVILEGES);
 		return;
diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c
@@ -66,8 +66,9 @@ CMD_FUNC(cmd_svsnick)
 	MessageTag *mtags = NULL;
 	char nickname[NICKLEN+1];
 	char oldnickname[NICKLEN+1];
+	time_t ts;
 
-	if (!IsULine(client) || parc < 4 || (strlen(parv[2]) > NICKLEN))
+	if (!IsSvsCmdOk(client) || parc < 4 || (strlen(parv[2]) > NICKLEN))
 		return; /* This looks like an error anyway -Studded */
 
 	if (hunt_server(client, NULL, "SVSNICK", 1, parc, parv) != HUNTED_ISME)
@@ -83,7 +84,7 @@ CMD_FUNC(cmd_svsnick)
 	if ((ocptr = find_client(nickname, NULL)) && ocptr != acptr) /* Collision */
 	{
 		exit_client(acptr, NULL,
-		                   "Nickname collision due to Services enforced "
+		                   "Nickname collision due to forced "
 		                   "nickname change, your nick was overruled");
 		return;
 	}
@@ -96,20 +97,22 @@ CMD_FUNC(cmd_svsnick)
 
 	if (acptr != ocptr)
 		acptr->umodes &= ~UMODE_REGNICK;
-	acptr->lastnick = atol(parv[3]);
+	ts = atol(parv[3]);
 
 	/* no 'recv_mtags' here, we do not inherit from SVSNICK but generate a new NICK event */
 	new_message(acptr, NULL, &mtags);
+	mtag_add_issued_by(&mtags, client, recv_mtags);
 	RunHook(HOOKTYPE_LOCAL_NICKCHANGE, acptr, mtags, nickname);
 	sendto_local_common_channels(acptr, acptr, 0, mtags, ":%s NICK :%s", acptr->name, nickname);
 	sendto_one(acptr, mtags, ":%s NICK :%s", acptr->name, nickname);
-	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)acptr->lastnick);
+	sendto_server(NULL, 0, 0, mtags, ":%s NICK %s :%lld", acptr->id, nickname, (long long)ts);
 
-	add_history(acptr, 1);
+	add_history(acptr, 1, WHOWAS_EVENT_NICK_CHANGE);
+	acptr->lastnick = ts; /* needs to be done AFTER add_history() */
 	del_from_client_hash_table(acptr->name, acptr);
 
 	unreal_log(ULOG_INFO, "nick", "FORCED_NICK_CHANGE", acptr,
-	           "$client.details has been forced by services to change their nickname to $new_nick_name",
+	           "$client.details has been forced to change their nickname to $new_nick_name",
 	           log_data_string("new_nick_name", nickname));
 
 	strlcpy(acptr->name, nickname, sizeof acptr->name);
diff --git a/src/modules/svsnline.c b/src/modules/svsnline.c
@@ -112,14 +112,14 @@ CMD_FUNC(cmd_svsnline)
 			AddListItem(bconf, conf_ban);
 		  } 
 		 
-		  if (IsULine(client))
+		  if (IsSvsCmdOk(client))
 			sendto_server(client, 0, 0, NULL, ":%s SVSNLINE + %s :%s",
 			    client->id, parv[2], parv[3]);
 		  break;
 	  }
 	  case '-':
 	  {
-		  if (!IsULine(client))
+		  if (!IsSvsCmdOk(client))
 			  return;
 		  if (parc < 3)
 			  return;
@@ -146,7 +146,7 @@ CMD_FUNC(cmd_svsnline)
 	  }
 	  case '*':
 	  {
-		  if (!IsULine(client))
+		  if (!IsSvsCmdOk(client))
 			  return;
 	          wipe_svsnlines();
 		  sendto_server(client, 0, 0, NULL, ":%s SVSNLINE *", client->id);
diff --git a/src/modules/svsnolag.c b/src/modules/svsnolag.c
@@ -59,7 +59,7 @@ void do_svsnolag(Client *client, int parc, const char *parv[], int show_change)
 	Client *target;
 	char *cmd = show_change ? MSG_SVS2NOLAG : MSG_SVSNOLAG;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (parc < 3)
diff --git a/src/modules/svsnoop.c b/src/modules/svsnoop.c
@@ -59,7 +59,7 @@ CMD_FUNC(cmd_svsnoop)
 {
 	Client *acptr;
 
-	if (!(IsULine(client) && parc > 2))
+	if (!(IsSvsCmdOk(client) && parc > 2))
 		return;
 
 	if (hunt_server(client, NULL, "SVSNOOP", 1, parc, parv) == HUNTED_ISME)
diff --git a/src/modules/svso.c b/src/modules/svso.c
@@ -54,7 +54,7 @@ CMD_FUNC(cmd_svso)
 	const char *snomask;
 	const char *vhost;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if ((parc < 8) || BadPtr(parv[7]))
@@ -122,11 +122,14 @@ CMD_FUNC(cmd_svso)
 
 	}
 
-	/* Prefix the oper block name with "services:" if it hasn't already */
-	if (!strncmp(parv[2], "services:", 9))
+	if (vhost && !valid_vhost(vhost))
+		sendnumeric(client, ERR_CANNOTDOCOMMAND, "SVSO", "Failed to make user oper: vhost contains illegal characters or is too long");
+
+	/* Prefix the oper block name with "remote:" if it hasn't already */
+	if (!strncmp(parv[2], "remote:", 7))
 		strlcpy(oper_account, parv[2], sizeof(oper_account));
 	else
-		snprintf(oper_account, sizeof(oper_account), "services:%s", parv[2]);
+		snprintf(oper_account, sizeof(oper_account), "remote:%s", parv[2]);
 
 	/* These needs to be looked up... */
 	clientclass_c = find_class(clientclass); /* NULL is fine! */
diff --git a/src/modules/svspart.c b/src/modules/svspart.c
@@ -63,7 +63,7 @@ CMD_FUNC(cmd_svspart)
 {
 	Client *target;
 	const char *comment = (parc > 3 && parv[3] ? parv[3] : NULL);
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (parc < 3 || !(target = find_user(parv[1], NULL))) 
diff --git a/src/modules/svssilence.c b/src/modules/svssilence.c
@@ -62,7 +62,7 @@ CMD_FUNC(cmd_svssilence)
 	char *p, *cp, c;
 	char request[BUFSIZE];
 	
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
diff --git a/src/modules/svssno.c b/src/modules/svssno.c
@@ -61,13 +61,13 @@ MOD_UNLOAD()
  * parv[2] - snomasks to change
  * show_change determines whether to show the change to the user
  */
-void do_svssno(Client *client, int parc, const char *parv[], int show_change)
+void do_svssno(Client *client, MessageTag *recv_mtags, int parc, const char *parv[], int show_change)
 {
 	const char *p;
 	Client *target;
 	int what = MODE_ADD, i;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (parc < 2)
@@ -79,7 +79,7 @@ void do_svssno(Client *client, int parc, const char *parv[], int show_change)
 	if (!(target = find_user(parv[1], NULL)))
 		return;
 
-	if (hunt_server(client, NULL, show_change ? "SVS2SNO" : "SVSSNO", 1, parc, parv) != HUNTED_ISME)
+	if (hunt_server(client, recv_mtags, show_change ? "SVS2SNO" : "SVSSNO", 1, parc, parv) != HUNTED_ISME)
 		return;
 
 	if (MyUser(target))
@@ -91,15 +91,21 @@ void do_svssno(Client *client, int parc, const char *parv[], int show_change)
 	}
 
 	if (show_change && target->user->snomask)
+	{
+		MessageTag *mtags = NULL;
+		new_message(client, recv_mtags, &mtags);
+		// TODO: sendnumeric has no mtags support :D
 		sendnumeric(target, RPL_SNOMASK, target->user->snomask);
+		safe_free_message_tags(mtags);
+	}
 }
 
 CMD_FUNC(cmd_svssno)
 {
-	do_svssno(client, parc, parv, 0);
+	do_svssno(client, recv_mtags, parc, parv, 0);
 }
 
 CMD_FUNC(cmd_svs2sno)
 {
-	do_svssno(client, parc, parv, 1);
+	do_svssno(client, recv_mtags, parc, parv, 1);
 }
diff --git a/src/modules/svswatch.c b/src/modules/svswatch.c
@@ -62,7 +62,7 @@ CMD_FUNC(cmd_svswatch)
 {
 	Client *target;
 
-	if (!IsULine(client))
+	if (!IsSvsCmdOk(client))
 		return;
 
 	if (parc < 3 || BadPtr(parv[2]) || !(target = find_user(parv[1], NULL)))
diff --git a/src/modules/targetfloodprot.c b/src/modules/targetfloodprot.c
@@ -259,7 +259,10 @@ int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Member
 	if (flood->cnt[what] >= channelcfg->cnt[what])
 	{
 		/* Flood detected */
-		flood_limit_exceeded_log(client, "target-flood-channel");
+		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $channel",
+			   log_data_string("flood_type", "target-flood-channel"),
+			   log_data_channel("channel", channel));
 		snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
 		*errmsg = errbuf;
 		return HOOK_DENY;
@@ -306,7 +309,10 @@ int targetfloodprot_can_send_to_user(Client *client, Client *target, const char 
 	if (flood->cnt[what] >= privatecfg->cnt[what])
 	{
 		/* Flood detected */
-		flood_limit_exceeded_log(client, "target-flood-user");
+		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
+			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $target",
+			   log_data_string("flood_type", "target-flood-user"),
+			   log_data_client("target", target));
 		snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
 		*errmsg = errbuf;
 		return HOOK_DENY;
diff --git a/src/modules/tkl.c b/src/modules/tkl.c
@@ -42,6 +42,8 @@ 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);
+int tkl_accept(Client *client);
+void check_set_spamfilter_utf8_setting_changed(void);
 CMD_FUNC(cmd_gline);
 CMD_FUNC(cmd_shun);
 CMD_FUNC(cmd_tempshun);
@@ -54,6 +56,7 @@ void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type);
 int _tkl_hash(unsigned int c);
 char _tkl_typetochar(int type);
 int _tkl_chartotype(char c);
+char _tkl_configtypetochar(const char *name);
 int tkl_banexception_chartotype(char c);
 char *_tkl_type_string(TKL *tk);
 char *_tkl_type_config_string(TKL *tk);
@@ -102,8 +105,11 @@ TKL *_find_tkl_banexception(int type, char *usermask, char *hostmask, int softba
 TKL *_find_tkl_nameban(int type, char *name, int hold);
 TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsigned short target);
 int _find_tkl_exception(int ban_type, Client *client);
+int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
+int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
 static void add_default_exempts(void);
 int parse_extended_server_ban(const char *mask_in, Client *client, char **error, int skip_checking, char *buf1, size_t buf1len, char *buf2, size_t buf2len);
+void _tkl_added(Client *client, TKL *tkl);
 
 /* Externals (only for us :D) */
 extern int MODVAR spamf_ugly_vchanoverride;
@@ -153,8 +159,12 @@ TKLTypeTable tkl_types[] = {
 };
 #define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
 
+/* Global variables for this module */
+
 int max_stats_matches = 1000;
 int mtag_spamfilters_present = 0; /**< Are any spamfilters with type SPAMF_MTAG present? */
+long previous_spamfilter_utf8 = 0;
+static int firstboot = 0;
 
 MOD_TEST()
 {
@@ -170,6 +180,7 @@ MOD_TEST()
 #endif
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_TYPETOCHAR, TO_INTFUNC(_tkl_typetochar));
 	EfunctionAdd(modinfo->handle, EFUNC_TKL_CHARTOTYPE, TO_INTFUNC(_tkl_chartotype));
+	EfunctionAdd(modinfo->handle, EFUNC_TKL_CONFIGTYPETOCHAR, TO_INTFUNC(_tkl_configtypetochar));
 #if defined(__GNUC__)
 #pragma GCC diagnostic pop
 #endif
@@ -207,17 +218,24 @@ MOD_TEST()
 	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKL_EXCEPTION, _find_tkl_exception);
 	EfunctionAddString(modinfo->handle, EFUNC_TKL_UHOST, _tkl_uhost);
 	EfunctionAdd(modinfo->handle, EFUNC_UNREAL_MATCH_IPLIST, _unreal_match_iplist);
+	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_PARSE_MASK, TO_INTFUNC(_server_ban_parse_mask));
+	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK, TO_INTFUNC(_server_ban_exception_parse_mask));
+	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_ADDED, _tkl_added);
 	return MOD_SUCCESS;
 }
 
 MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
+	if (loop.booted == 0)
+		firstboot = 1;
+	LoadPersistentLong(modinfo, previous_spamfilter_utf8);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_set);
 	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 2000000000, tkl_ip_change);
+	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -1000, tkl_accept);
 	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);
@@ -228,19 +246,20 @@ MOD_INIT()
 	CommandAdd(modinfo->handle, "ELINE", cmd_eline, 4, CMD_OPER);
 	CommandAdd(modinfo->handle, "TKL", _cmd_tkl, MAXPARA, CMD_OPER|CMD_SERVER);
 	add_default_exempts();
-	MARK_AS_OFFICIAL_MODULE(modinfo);
 	return MOD_SUCCESS;
 }
 
 MOD_LOAD()
 {
 	check_mtag_spamfilters_present();
+	check_set_spamfilter_utf8_setting_changed();
 	EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
 	return MOD_SUCCESS;
 }
 
 MOD_UNLOAD()
 {
+	SavePersistentLong(modinfo, previous_spamfilter_utf8);
 	return MOD_SUCCESS;
 }
 
@@ -879,6 +898,56 @@ int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
 	return 0;
 }
 
+/** Recompile all spamfilters due to set::spamfilter::utf8 setting change */
+void recompile_spamfilters(void)
+{
+	TKL *tkl;
+	Match *m;
+	char *err;
+	int converted = 0;
+	int index;
+
+	index = tkl_hash('F');
+	for (tkl = tklines[index]; tkl; tkl = tkl->next)
+	{
+		if (!TKLIsSpamfilter(tkl) || (tkl->ptr.spamfilter->match->type != MATCH_PCRE_REGEX))
+			continue;
+		m = unreal_create_match(MATCH_PCRE_REGEX, tkl->ptr.spamfilter->match->str, &err);
+		if (!m)
+		{
+			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_COMPILE_ERROR", NULL,
+			           "Spamfilter no longer compiles upon utf8 change, error: $error. "
+			           "Spamfilter '$tkl' ($tkl.reason). "
+			           "Spamfilter not transformed to/from utf8.",
+			           log_data_tkl("tkl", tkl),
+			           log_data_string("error", err ? err : "Unknown"));
+			continue;
+		}
+
+		unreal_delete_match(tkl->ptr.spamfilter->match); /* unset old one */
+		tkl->ptr.spamfilter->match = m; /* set new one */
+		converted++;
+	}
+	unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_UTF8_CONVERTED", NULL,
+	           "Spamfilter: Recompiled $count spamfilters due to set::spamfilter::utf8 change.",
+	           log_data_integer("count", converted));
+}
+
+void check_set_spamfilter_utf8_setting_changed(void)
+{
+	if (firstboot)
+	{
+		/* First boot, not a rehash */
+		previous_spamfilter_utf8 = iConf.spamfilter_utf8;
+		return;
+	}
+
+	if (previous_spamfilter_utf8 != iConf.spamfilter_utf8)
+		recompile_spamfilters();
+
+	previous_spamfilter_utf8 = iConf.spamfilter_utf8;
+}
+
 /** Return unique spamfilter id for TKL */
 char *spamfilter_id(TKL *tk)
 {
@@ -890,7 +959,20 @@ char *spamfilter_id(TKL *tk)
 
 int tkl_ip_change(Client *client, const char *oldip)
 {
-	check_banned(client, 0);
+	TKL *tkl;
+	if ((tkl = find_tkline_match_zap(client)))
+		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
+	return 0;
+}
+
+int tkl_accept(Client *client)
+{
+	TKL *tkl;
+	if ((tkl = find_tkline_match_zap(client)))
+	{
+		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
+		return HOOK_DENY;
+	}
 	return 0;
 }
 
@@ -1182,7 +1264,7 @@ int ban_too_broad(char *usermask, char *hostmask)
 	 * CIDR mask will also pass this test (which is fine).
 	 */
 	for (p = hostmask; *p; p++)
-		if (*p != '*' && *p != '.' && *p != '?')
+		if (*p != '*' && *p != '.' && *p != '?' && *p != ':')
 			cnt++;
 
 	if (cnt >= 4)
@@ -1324,75 +1406,58 @@ fail_parse_extended_server_ban:
 	return 0;
 }
 
-
-/** Intermediate layer between user functions such as KLINE/GLINE
- * and the TKL layer (cmd_tkl).
- * This allows us doing some syntax checking and other helpful
- * things that are the same for many types of *LINES.
+/** Parse a server ban request such as 'blah@blah.com' or '~account:EvilUser'
+ * @param client	Client requesting the operation (can be NULL)
+ * @param add		Set to 1 for add ban, 0 for remove ban
+ * @param type		TKL type (character), see 2nd column of tkl_types[], eg 'G' for gline.
+ * @param str		The input string
+ * @param usermask_out	Will be set to the TKL usermask
+ * @param hostmask_out	Will be set to the TKL hostmask
+ * @param soft		Will be set to 1 if it's a softban, otherwise 0
+ * @param error		On failure, this will contain the error string
+ * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
+ * @retval 0	Failed: error is set appropriately
  */
-void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
+int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
 {
-	time_t secs;
-	int add = 1;
-	time_t i;
-	Client *acptr = NULL;
-	char maskbuf[BUFSIZE];
-	char *mask;
-	char mo[64], mo2[64];
-	char mask1buf[BUFSIZE];
-	char mask2buf[BUFSIZE];
-	char *p, *usermask, *hostmask;
-	const char *tkllayer[10] = {
-		me.name,		/*0  server.name */
-		NULL,			/*1  +|- */
-		NULL,			/*2  G   */
-		NULL,			/*3  user */
-		NULL,			/*4  host */
-		NULL,			/*5  set_by */
-		"0",			/*6  expire_at */
-		NULL,			/*7  set_at */
-		"no reason",	/*8  reason */
-		NULL
-	};
-	struct tm *t;
+	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE];
+	char *hostmask = NULL, *usermask = NULL;
+	char *mask, *p;
 
-	if ((parc == 1) || BadPtr(parv[1]))
-		return; /* shouldn't happen */
+	/* Set defaults */
+	*usermask_out = *hostmask_out = NULL;
+	*soft = 0;
 
-	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
+	strlcpy(maskbuf, str, sizeof(maskbuf));
 	mask = maskbuf;
-	if (*mask == '-')
-	{
-		add = 0;
-		mask++;
-	}
-	else if (*mask == '+')
-	{
-		add = 1;
-		mask++;
-	}
 
 	if ((*mask != '~') && strchr(mask, '!'))
 	{
-		sendnotice(client, "[error] Cannot have '!' in masks.");
-		return;
+		*error = "Cannot have '!' in masks.";
+		return 0;
 	}
+
 	if (*mask == ':')
 	{
-		sendnotice(client, "[error] Mask cannot start with a ':'.");
-		return;
+		*error = "Mask cannot start with a ':'.";
+		return 0;
 	}
+
 	if (strchr(mask, ' '))
-		return;
+	{
+		*error = "Mask may not contain spaces";
+		return 0;
+	}
 
 	/* Check if it's a softban */
 	if (*mask == '%')
 	{
-		if (!strchr("kGs", *type))
+		*soft = 1;
+		if (!strchr("kGs", type))
 		{
-			sendnotice(client, "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN."
-			                 "For technical reasons this will not work for (G)ZLINE.");
-			return;
+			*error = "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN. "
+			         "For technical reasons this will not work for (G)ZLINE.";
+			return 0;
 		}
 	}
 
@@ -1406,8 +1471,8 @@ void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
 			/* If adding, reject it */
 			if (add)
 			{
-				sendnotice(client, "ERROR: %s", err);
-				return;
+				*error = err;
+				return 0;
 			} else
 			{
 				/* Always allow any removal attempt... */
@@ -1423,13 +1488,13 @@ void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
 				/* fallthrough */
 			}
 		}
-		if (add && ((*type == 'z') || (*type == 'Z')))
+		if (add && ((type == 'z') || (type == 'Z')))
 		{
-			sendnotice(client, "ERROR: (g)zlines must be placed at *@\037IPMASK\037. "
-					   "Extended server bans don't work here because (g)zlines are processed"
-					   "BEFORE dns and ident lookups are done and before reading any client data. "
-					   "If you want to use extended server bans then use a KLINE/GLINE instead.");
-			return;
+			*error = "(G)Zlines must be placed at *@\037IPMASK\037. "
+			         "Extended server bans don't work here because (g)zlines are processed "
+			         "BEFORE dns and ident lookups are done and before reading any client data. "
+			         "If you want to use extended server bans then use a KLINE/GLINE instead.";
+			return 0;
 		}
 		usermask = mask1buf; /* eg ~S: */
 		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
@@ -1440,64 +1505,127 @@ void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
 		if (p) {
 			if ((p == mask) || !p[1])
 			{
-				sendnotice(client, "Error: no user@host specified");
-				return;
+				*error = "No user@host specified";
+				return 0;
 			}
 			usermask = strtok(mask, "@");
 			hostmask = strtok(NULL, "");
-			if (BadPtr(hostmask)) {
-				if (BadPtr(usermask)) {
-					return;
+			if (BadPtr(hostmask))
+			{
+				if (BadPtr(usermask))
+				{
+					*error = "Invalid mask";
+					return 0;
 				}
 				hostmask = usermask;
 				usermask = "*";
 			}
 			if (*hostmask == ':')
 			{
-				sendnotice(client, "[error] For technical reasons you cannot start the host with a ':', sorry");
-				return;
+				*error = "For technical reasons you cannot start the host with a ':', sorry";
+				return 0;
 			}
-			if (add && ((*type == 'z') || (*type == 'Z')))
+			if (add && ((type == 'z') || (type == 'Z')))
 			{
 				/* It's a (G)ZLINE, make sure the user isn't specyfing a HOST.
 				 * Just a warning in 3.2.3, but an error in 3.2.4.
 				 */
 				if (strcmp(usermask, "*"))
 				{
-					sendnotice(client, "ERROR: (g)zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
-							 "because (g)zlines are processed BEFORE dns and ident lookups are done. "
-							 "If you want to use usermasks, use a KLINE/GLINE instead.");
-					return;
+					*error = "(G)Zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
+					         "because (g)zlines are processed BEFORE dns and ident lookups are done. "
+					         "If you want to use usermasks, use a KLINE/GLINE instead.";
+					return 0;
 				}
 				for (p=hostmask; *p; p++)
+				{
 					if (isalpha(*p) && !isxdigit(*p))
 					{
-						sendnotice(client, "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
-								 "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
-								 "This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
-								 "If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.");
-						return;
+						*error = "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
+						         "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
+						         "This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
+						         "If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.";
+						return 0;
 					}
+				}
 			}
 		}
 		else
 		{
 			/* It's seemingly a nick .. let's see if we can find the user */
+			Client *acptr;
 			if ((acptr = find_user(mask, NULL)))
 			{
 				BanAction action = BAN_ACT_KLINE; // just a dummy default
-				if ((*type == 'z') || (*type == 'Z'))
+				if ((type == 'z') || (type == 'Z'))
 					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
 				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
 			}
 			else
 			{
-				sendnumeric(client, ERR_NOSUCHNICK, mask);
-				return;
+				*error = "Nickname not found";
+				return 0;
 			}
 		}
 	}
 
+	/* Success! */
+	*usermask_out = usermask;
+	*hostmask_out = hostmask;
+	return 1;
+}
+
+/** Intermediate layer between user functions such as KLINE/GLINE
+ * and the TKL layer (cmd_tkl).
+ * This allows us doing some syntax checking and other helpful
+ * things that are the same for many types of *LINES.
+ */
+void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
+{
+	time_t secs;
+	int add = 1, soft;
+	time_t i;
+	Client *acptr = NULL;
+	const char *mask;
+	const char *error;
+	char mo[64], mo2[64];
+	char *p, *usermask, *hostmask;
+	const char *tkllayer[10] = {
+		me.name,		/*0  server.name */
+		NULL,			/*1  +|- */
+		NULL,			/*2  G   */
+		NULL,			/*3  user */
+		NULL,			/*4  host */
+		NULL,			/*5  set_by */
+		"0",			/*6  expire_at */
+		NULL,			/*7  set_at */
+		"no reason",	/*8  reason */
+		NULL
+	};
+	struct tm *t;
+
+	if ((parc == 1) || BadPtr(parv[1]))
+		return; /* shouldn't happen */
+
+	mask = parv[1];
+
+	if (*mask == '-')
+	{
+		add = 0;
+		mask++;
+	}
+	else if (*mask == '+')
+	{
+		add = 1;
+		mask++;
+	}
+
+	if (!server_ban_parse_mask(client, add, *type, mask, &usermask, &hostmask, &soft, &error))
+	{
+		sendnotice(client, "[ERROR] %s", error);
+		return;
+	}
+
 	if (add && ban_too_broad(usermask, hostmask))
 	{
 		sendnotice(client, "*** [error] Too broad mask");
@@ -1619,103 +1747,68 @@ int contains_invalid_server_ban_exception_type(const char *str, char *c)
 	return 0;
 }
 
-CMD_FUNC(cmd_eline)
+/** Parse a server ban exception (ELINE) request such as 'blah@blah.com' or '~account:EvilUser'
+ * @param client	Client requesting the operation (can be NULL)
+ * @param add		Set to 1 for add ban, 0 for remove ban
+ * @param bantypes	Ban types to exempt from
+ * @param str		The input string
+ * @param usermask_out	Will be set to the TKL usermask
+ * @param hostmask_out	Will be set to the TKL hostmask
+ * @param soft		Will be set to 1 if it's a softban, otherwise 0
+ * @param error		On failure, this will contain the error string
+ * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
+ * @retval 0	Failed: error is set appropriately
+ */
+int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
 {
-	time_t secs = 0;
-	int add = 1;
-	Client *acptr = NULL;
-	char *mask = NULL;
-	char mo[64], mo2[64];
-	char maskbuf[BUFSIZE];
-	char mask1buf[BUFSIZE];
-	char mask2buf[BUFSIZE];
-	const char *p, *bantypes=NULL, *reason=NULL;
-	char *usermask, *hostmask;
-	const char *tkllayer[11] = {
-		me.name,		/*0  server.name */
-		NULL,			/*1  +|- */
-		NULL,			/*2  E   */
-		NULL,			/*3  user */
-		NULL,			/*4  host */
-		NULL,			/*5  set_by */
-		"0",			/*6  expire_at */
-		"-",			/*7  set_at */
-		"-",			/*8  ban types */
-		"-",			/*9  reason */
-		NULL
-	};
+	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE], errbuf[BUFSIZE];
+	char *hostmask = NULL, *usermask = NULL;
+	char *mask, *p;
 	TKLTypeTable *t;
 
-	if (IsServer(client))
-		return;
+	/* Set defaults */
+	*usermask_out = *hostmask_out = NULL;
+	*soft = 0;
 
-	if (!ValidatePermissionsForPath("server-ban:eline",client,NULL,NULL,NULL))
-	{
-		sendnumeric(client, ERR_NOPRIVILEGES);
-		return;
-	}
+	strlcpy(maskbuf, str, sizeof(maskbuf));
+	mask = maskbuf;
 
-	/* For del we need at least:
-	 * ELINE -user@host
-	 * The 'add' case is checked later.
-	 */
-	if ((parc < 2) || BadPtr(parv[1]))
+	if ((*mask != '~') && strchr(mask, '!'))
 	{
-		eline_syntax(client);
-		return;
+		*error = "Cannot have '!' in masks.";
+		return 0;
 	}
 
-	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
-	mask = maskbuf;
-	if (*mask == '-')
-	{
-		add = 0;
-		mask++;
-	}
-	else if (*mask == '+')
+	if (*mask == ':')
 	{
-		add = 1;
-		mask++;
+		*error = "Mask cannot start with a ':'.";
+		return 0;
 	}
 
-	/* For add we need more:
-	 * ELINE user@host bantypes expiry :reason
-	 */
-	if (add)
+	if (strchr(mask, ' '))
 	{
-		if ((parc < 5) || BadPtr(parv[4]))
-		{
-			eline_syntax(client);
-			return;
-		}
-		bantypes = parv[2];
-		reason = parv[4];
+		*error = "Mask may not contain spaces";
+		return 0;
 	}
 
-	if ((*mask != '~') && strchr(mask, '!'))
-	{
-		sendnotice(client, "[error] Cannot have '!' in masks.");
-		return;
-	}
-	if (*mask == ':')
+	if (*mask == '%')
 	{
-		sendnotice(client, "[error] Mask cannot start with a ':'.");
-		return;
+		*soft = 1;
+		/* do we need more sanity checks here? */
 	}
-	if (strchr(mask, ' '))
-		return;
 
 	/* Check if it's an extended server ban */
 	if (is_extended_server_ban(mask))
 	{
 		char *err;
+
 		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
 		{
 			/* If adding, reject it */
 			if (add)
 			{
-				sendnotice(client, "ERROR: %s", err);
-				return;
+				*error = err;
+				return 0;
 			} else
 			{
 				/* Always allow any removal attempt... */
@@ -1733,10 +1826,12 @@ CMD_FUNC(cmd_eline)
 		}
 		if (add && (t = eline_type_requires_ip(bantypes)))
 		{
-			sendnotice(client, "ERROR: Ban exception with type '%c' does not work on extended server bans. "
-					   "This is because checking for %s takes places BEFORE "
-					   "extended bans can be checked.", t->letter, t->log_name);
-			return;
+			snprintf(errbuf, sizeof(errbuf),
+			         "ERROR: Ban exception with type '%c' does not work on extended server bans. "
+			         "This is because checking for %s takes places BEFORE "
+			         "extended bans can be checked.", t->letter, t->log_name);
+			*error = errbuf;
+			return 0;
 		}
 		usermask = mask1buf; /* eg ~S: */
 		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
@@ -1744,26 +1839,28 @@ CMD_FUNC(cmd_eline)
 	{
 		/* Check if it's a hostmask and legal .. */
 		p = strchr(mask, '@');
-		if (p)
-		{
+		if (p) {
 			if ((p == mask) || !p[1])
 			{
-				sendnotice(client, "Error: no user@host specified");
-				return;
+				*error = "No user@host specified";
+				return 0;
 			}
 			usermask = strtok(mask, "@");
 			hostmask = strtok(NULL, "");
-			if (BadPtr(hostmask)) {
-				if (BadPtr(usermask)) {
-					return;
+			if (BadPtr(hostmask))
+			{
+				if (BadPtr(usermask))
+				{
+					*error = "Invalid mask";
+					return 0;
 				}
 				hostmask = usermask;
 				usermask = "*";
 			}
 			if (*hostmask == ':')
 			{
-				sendnotice(client, "[error] For technical reasons you cannot start the host with a ':', sorry");
-				return;
+				*error = "For technical reasons you cannot start the host with a ':', sorry";
+				return 0;
 			}
 			if (add && ((t = eline_type_requires_ip(bantypes))))
 			{
@@ -1772,22 +1869,24 @@ CMD_FUNC(cmd_eline)
 				 */
 				if (strcmp(usermask, "*"))
 				{
-					sendnotice(client, "ERROR: Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
-					                   "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
-					                   t->letter,
-					                   t->log_name);
-					return;
+					snprintf(errbuf, sizeof(errbuf),
+					         "Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
+					         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+					         t->letter, t->log_name);
+					*error = errbuf;
+					return 0;
 				}
 				for (p=hostmask; *p; p++)
 				{
 					if (isalpha(*p) && !isxdigit(*p))
 					{
-						sendnotice(client, "ERROR: Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
-						                   "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
-						                   "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
-						                   t->letter,
-						                   t->log_name);
-						return;
+						snprintf(errbuf, sizeof(errbuf),
+						         "Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
+						         "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
+						         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
+						         t->letter, t->log_name);
+						*error = errbuf;
+						return 0;
 					}
 				}
 			}
@@ -1795,6 +1894,7 @@ CMD_FUNC(cmd_eline)
 		else
 		{
 			/* It's seemingly a nick .. let's see if we can find the user */
+			Client *acptr;
 			if ((acptr = find_user(mask, NULL)))
 			{
 				BanAction action = BAN_ACT_KLINE; // just a dummy default
@@ -1804,12 +1904,99 @@ CMD_FUNC(cmd_eline)
 			}
 			else
 			{
-				sendnumeric(client, ERR_NOSUCHNICK, mask);
-				return;
+				*error = "Nickname not found";
+				return 0;
 			}
 		}
 	}
 
+	/* Success! */
+	*usermask_out = usermask;
+	*hostmask_out = hostmask;
+	return 1;
+}
+
+CMD_FUNC(cmd_eline)
+{
+	time_t secs = 0;
+	int add = 1;
+	int soft = 0;
+	const char *error = NULL;
+	Client *acptr = NULL;
+	char *mask = NULL;
+	char mo[64], mo2[64];
+	char maskbuf[BUFSIZE];
+	char mask1buf[BUFSIZE];
+	char mask2buf[BUFSIZE];
+	const char *p, *bantypes=NULL, *reason=NULL;
+	char *usermask, *hostmask;
+	const char *tkllayer[11] = {
+		me.name,		/*0  server.name */
+		NULL,			/*1  +|- */
+		NULL,			/*2  E   */
+		NULL,			/*3  user */
+		NULL,			/*4  host */
+		NULL,			/*5  set_by */
+		"0",			/*6  expire_at */
+		"-",			/*7  set_at */
+		"-",			/*8  ban types */
+		"-",			/*9  reason */
+		NULL
+	};
+	TKLTypeTable *t;
+
+	if (IsServer(client))
+		return;
+
+	if (!ValidatePermissionsForPath("server-ban:eline",client,NULL,NULL,NULL))
+	{
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return;
+	}
+
+	/* For del we need at least:
+	 * ELINE -user@host
+	 * The 'add' case is checked later.
+	 */
+	if ((parc < 2) || BadPtr(parv[1]))
+	{
+		eline_syntax(client);
+		return;
+	}
+
+	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
+	mask = maskbuf;
+	if (*mask == '-')
+	{
+		add = 0;
+		mask++;
+	}
+	else if (*mask == '+')
+	{
+		add = 1;
+		mask++;
+	}
+
+	/* For add we need more:
+	 * ELINE user@host bantypes expiry :reason
+	 */
+	if (add)
+	{
+		if ((parc < 5) || BadPtr(parv[4]))
+		{
+			eline_syntax(client);
+			return;
+		}
+		bantypes = parv[2];
+		reason = parv[4];
+	}
+
+	if (!server_ban_exception_parse_mask(client, add, bantypes, mask, &usermask, &hostmask, &soft, &error))
+	{
+		sendnotice(client, "[ERROR] %s", error);
+		return;
+	}
+
 	if (add)
 	{
 		secs = config_checkval(parv[3], CFG_TIME);
@@ -2186,6 +2373,15 @@ int _tkl_chartotype(char c)
 	return 0;
 }
 
+char _tkl_configtypetochar(const char *name)
+{
+	int i;
+	for (i=0; tkl_types[i].config_name; i++)
+		if (!strcmp(tkl_types[i].config_name, name))
+			return tkl_types[i].letter;
+	return 0;
+}
+
 int tkl_banexception_chartotype(char c)
 {
 	int i;
@@ -2710,7 +2906,9 @@ static void add_default_exempts(void)
 	 * Currently the list is: gline, kline, gzline, zline, shun, blacklist,
 	 *                        connect-flood, handshake-data-flood.
 	 */
-	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.0/8", NULL, "localhost is always exempt",
+	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.1", NULL, "localhost is always exempt",
+	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
+	tkl_add_banexception(TKL_EXCEPTION, "*", "::1", NULL, "localhost is always exempt",
 	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
 }
 
@@ -3680,6 +3878,10 @@ void tkl_broadcast_entry(int add, Client *sender, Client *skip, TKL *tkl)
 {
 	Client *acptr;
 
+	/* Silly fix for RPC calls that lead to broadcasts from this sender */
+	if (!IsUser(sender) && !IsServer(sender))
+		sender = &me;
+
 	list_for_each_entry(acptr, &server_list, special_node)
 	{
 		if (skip && acptr == skip->direction)
@@ -3890,6 +4092,26 @@ void _sendnotice_tkl_del(char *removed_by, TKL *tkl)
 	}
 }
 
+/** Called when a TKL is added by a remote user, local user, RPC user, ..
+ * (but not when a TKL is added via the config)
+ */
+void _tkl_added(Client *client, TKL *tkl)
+{
+	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
+
+	sendnotice_tkl_add(tkl);
+
+	/* spamfilter 'warn' action is special */
+	if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
+		spamfilter_check_users(tkl);
+
+	/* Ban checking executes during run loop for efficiency */
+	loop.do_bancheck = 1;
+
+	if (tkl->type & TKL_GLOBAL)
+		tkl_broadcast_entry(1, client, client, tkl);
+}
+
 /** Add a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
 CMD_FUNC(cmd_tkl_add)
 {
@@ -4172,21 +4394,7 @@ CMD_FUNC(cmd_tkl_add)
 		return;
 	}
 
-	/* Below this line we will only use 'tkl'. No parc/parv reading anymore. */
-
-	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
-
-	sendnotice_tkl_add(tkl);
-
-	/* spamfilter 'warn' action is special */
-	if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
-		spamfilter_check_users(tkl);
-
-	/* Ban checking executes during run loop for efficiency */
-	loop.do_bancheck = 1;
-
-	if (type & TKL_GLOBAL)
-		tkl_broadcast_entry(1, client, client, tkl);
+	tkl_added(client, tkl);
 }
 
 /** Delete a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
@@ -4369,10 +4577,10 @@ CMD_FUNC(_cmd_tkl)
 	switch (*parv[1])
 	{
 		case '+':
-			cmd_tkl_add(client, recv_mtags, parc, parv);
+			CALL_CMD_FUNC(cmd_tkl_add);
 			break;
 		case '-':
-			cmd_tkl_del(client, recv_mtags, parc, parv);
+			CALL_CMD_FUNC(cmd_tkl_del);
 			break;
 		default:
 			break;
diff --git a/src/modules/tkldb.c b/src/modules/tkldb.c
@@ -128,7 +128,7 @@ MOD_TEST()
 MOD_INIT()
 {
 	MARK_AS_OFFICIAL_MODULE(modinfo);
-	ModuleSetOptions(modinfo->handle, MOD_OPT_UNLOAD_PRIORITY, -9999);
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, -9999);
 
 	LoadPersistentLong(modinfo, tkldb_next_event);
 
diff --git a/src/modules/tline.c b/src/modules/tline.c
@@ -0,0 +1,87 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/tline.c
+ *   (C) 2022 Noisytoot & The UnrealIRCd Team
+ *
+ *   See file AUTHORS in IRC package for additional names of
+ *   the programmers.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 1, or (at your option)
+ *   any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "unrealircd.h"
+
+#define MSG_TLINE "TLINE"
+
+CMD_FUNC(cmd_tline);
+
+ModuleHeader MOD_HEADER
+  = {
+	"tline",
+	"1.0",
+	"TLINE command to show amount of clients matching a server ban mask",
+	"UnrealIRCd team",
+	"unrealircd-6",
+	};
+
+MOD_INIT()
+{
+	CommandAdd(modinfo->handle, MSG_TLINE, cmd_tline, 1, CMD_OPER);
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/*
+** cmd_tline
+**	parv[1] = mask to test
+*/
+CMD_FUNC(cmd_tline)
+{
+	Client *acptr;
+	int matching_lclients = 0;
+	int matching_clients = 0;
+
+	if ((parc < 1) || BadPtr(parv[1]))
+	{
+		sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_TLINE);
+		return;
+	}
+
+	list_for_each_entry(acptr, &client_list, client_node)
+	{
+		if (match_user(parv[1], acptr, MATCH_CHECK_REAL))
+		{
+			if (MyUser(acptr))
+				matching_lclients++;
+			matching_clients++;
+		}
+	}
+
+	sendnotice(client,
+	    "*** TLINE: Users matching mask '%s': global: %d/%d (%.2f%%), local: %d/%d (%.2f%%).",
+	    parv[1], matching_clients, irccounts.clients,
+	    (double)matching_clients / irccounts.clients * 100,
+	    matching_lclients, irccounts.me_clients,
+	    (double)matching_lclients / irccounts.me_clients * 100);
+}
diff --git a/src/modules/tls_antidos.c b/src/modules/tls_antidos.c
@@ -81,7 +81,7 @@ void ssl_info_callback(const SSL *ssl, int where, int ret)
 			e->n++;
 			if (e->n >= HANDSHAKE_LIMIT_COUNT)
 			{
-				unreal_log(ULOG_INFO, "flood", "TLS_HANDSHAKE_FLOOD", client, "TLS Handshake flood detected from $client -- killed");
+				unreal_log(ULOG_INFO, "flood", "TLS_HANDSHAKE_FLOOD", client, "TLS Handshake flood detected from $client.details -- killed");
 				dead_socket(client, "TLS Handshake flood detected");
 			}
 		}
diff --git a/src/modules/tls_cipher.c b/src/modules/tls_cipher.c
@@ -22,7 +22,7 @@ void tls_cipher_unserialize(const char *str, ModData *m);
 int tls_cipher_handshake(Client *client);
 int tls_cipher_connect(Client *client);
 int tls_cipher_whois(Client *client, Client *target);
-int log_tls_cipher(Client *client, int detail, json_t *j);
+int tls_json_expand_client(Client *client, int detail, json_t *j);
 
 ModDataInfo *tls_cipher_md; /* Module Data structure which we acquire */
 
@@ -46,7 +46,7 @@ ModDataInfo mreq;
 	HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, tls_cipher_handshake);
 	HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, tls_cipher_handshake);
 
-	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, log_tls_cipher);
+	HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, tls_json_expand_client);
 
 	return MOD_SUCCESS;
 }
@@ -93,11 +93,14 @@ void tls_cipher_unserialize(const char *str, ModData *m)
 	safe_strdup(m->str, str);
 }
 
-int log_tls_cipher(Client *client, int detail, json_t *j)
+int tls_json_expand_client(Client *client, int detail, json_t *j)
 {
 	json_t *tls;
 	const char *str;
 
+	if (detail < 2)
+		return 0;
+
 	str = moddata_client_get(client, "tls_cipher");
 	if (!str)
 		return 0;
diff --git a/src/modules/topic.c b/src/modules/topic.c
@@ -35,6 +35,16 @@ ModuleHeader MOD_HEADER
 	"unrealircd-6",
     };
 
+/* Forward declarations */
+void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at);
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_SET_CHANNEL_TOPIC, _set_channel_topic);
+	return MOD_SUCCESS;
+}
+
 MOD_INIT()
 {
 	CommandAdd(modinfo->handle, MSG_TOPIC, cmd_topic, 4, CMD_USER|CMD_SERVER);
@@ -110,8 +120,14 @@ CMD_FUNC(cmd_topic)
 		if (parc > 2)
 			topic = parv[2];
 	}
+
 	if (parc > 4)
 	{
+		if (MyUser(client))
+		{
+			sendnumeric(client, ERR_CANNOTDOCOMMAND, "TOPIC", "Invalid parameters. Usage is TOPIC #channel :topic here");
+			return;
+		}
 		tnick = parv[2];
 		ttime = atol(parv[3]);
 		topic = parv[4];
@@ -264,15 +280,33 @@ CMD_FUNC(cmd_topic)
 			tnick = make_nick_user_host(client->name, client->user->username, GetHost(client));
 	}
 
+	_set_channel_topic(client, channel, recv_mtags, topic, tnick, ttime);
+}
+
+/** Set topic on a channel.
+ * @param client	The client setting the topic
+ * @param channel	The channel
+ * @param recv_mtags	Message tags
+ * @param topic		The new topic (TODO: this function does not support unsetting yet)
+ * @param set_by	Who set the topic (can be NULL, means client->name)
+ * @param set_at	When the topic was set (can be 0, means now)
+ */
+void _set_channel_topic(Client *client, Channel *channel, MessageTag *recv_mtags, const char *topic, const char *set_by, time_t set_at)
+{
+	MessageTag *mtags = NULL;
+
+	/* Set default values when needed */
+	if (set_by == NULL)
+		set_by = client->name;
+	if (set_at == 0)
+		set_at = TStime();
+
 	/* Set the topic */
 	safe_strldup(channel->topic, topic, iConf.topic_length+1);
-	safe_strldup(channel->topic_nick, tnick, NICKLEN+USERLEN+HOSTLEN+5);
-
-	if (ttime && !MyUser(client))
-		channel->topic_time = ttime;
-	else
-		channel->topic_time = TStime();
+	safe_strldup(channel->topic_nick, set_by, NICKLEN+USERLEN+HOSTLEN+5);
+	channel->topic_time = set_at;
 
+	/* And broadcast the change - locally and remote */
 	new_message(client, recv_mtags, &mtags);
 	RunHook(HOOKTYPE_TOPIC, client, channel, mtags, topic);
 	sendto_server(client, 0, 0, mtags, ":%s TOPIC %s %s %lld :%s",
diff --git a/src/modules/webirc.c b/src/modules/webirc.c
@@ -174,7 +174,7 @@ int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
 			errors++;
 			continue;
 		}
-		if (!strcmp(cep->name, "mask"))
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
 		{
 			if (cep->value || cep->items)
 				has_mask = 1;
@@ -260,7 +260,7 @@ int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
-		if (!strcmp(cep->name, "mask"))
+		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
 			unreal_add_masks(&webirc->mask, cep);
 		else if (!strcmp(cep->name, "password"))
 			webirc->auth = AuthBlockToAuthConfig(cep);
diff --git a/src/modules/webserver.c b/src/modules/webserver.c
@@ -0,0 +1,675 @@
+/*
+ * Webserver
+ * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ */
+   
+#include "unrealircd.h"
+#include "dns.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"webserver",
+	"1.0.0",
+	"Webserver",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#if CHAR_MIN < 0
+ #error "In UnrealIRCd char should always be unsigned. Check your compiler"
+#endif
+
+/* How many seconds to wait with closing after sending the response */
+#define WEB_CLOSE_TIME 1
+
+/* The "Server: xyz" in the response */
+#define WEB_SOFTWARE "UnrealIRCd"
+
+/* Macros */
+#define WEB(client)		((WebRequest *)moddata_client(client, webserver_md).ptr)
+#define WEBSERVER(client)	((client->local && client->local->listener) ? client->local->listener->webserver : NULL)
+#define reset_handshake_timeout(client, delta)  do { client->local->creationtime = TStime() - iConf.handshake_timeout + delta; } while(0)
+
+/* Forward declarations */
+int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
+int webserver_packet_in(Client *client, const char *readbuf, int *length);
+void webserver_mdata_free(ModData *m);
+int webserver_handle_packet(Client *client, const char *readbuf, int length);
+int webserver_handle_handshake(Client *client, const char *readbuf, int *length);
+int webserver_handle_request_header(Client *client, const char *readbuf, int *length);
+void _webserver_send_response(Client *client, int status, char *msg);
+void _webserver_close_client(Client *client);
+int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int length);
+
+/* Global variables */
+ModDataInfo *webserver_md;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response);
+	EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSERVER_HANDLE_BODY, _webserver_handle_body);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	//HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, webserver_packet_out);
+	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, webserver_packet_in);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "web";
+	mreq.serialize = NULL;
+	mreq.unserialize = NULL;
+	mreq.free = webserver_mdata_free;
+	mreq.sync = 0;
+	mreq.type = MODDATATYPE_CLIENT;
+	webserver_md = ModDataAdd(modinfo->handle, mreq);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+/** UnrealIRCd internals: free WebRequest object. */
+void webserver_mdata_free(ModData *m)
+{
+	WebRequest *wsu = (WebRequest *)m->ptr;
+	if (wsu)
+	{
+		safe_free(wsu->uri);
+		free_nvplist(wsu->headers);
+		safe_free(wsu->lefttoparse);
+		safe_free(wsu->request_buffer);
+		safe_free(m->ptr);
+	}
+}
+
+/** Outgoing packet hook.
+ * Do we need this?
+ */
+int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
+{
+	static char utf8buf[510];
+
+	if (MyConnect(to) && WEB(to))
+	{
+		// TODO: Inhibit all?
+		// Websocket can override though?
+		return 0;
+	}
+	return 0;
+}
+
+HttpMethod webserver_get_method(const char *buf)
+{
+	if (!strncmp(buf, "HEAD ", 5))
+		return HTTP_METHOD_HEAD;
+	if (!strncmp(buf, "GET ", 4))
+		return HTTP_METHOD_GET;
+	if (!strncmp(buf, "PUT ", 4))
+		return HTTP_METHOD_PUT;
+	if (!strncmp(buf, "POST ", 5))
+		return HTTP_METHOD_POST;
+	return HTTP_METHOD_NONE; /* invalid */
+}
+
+void webserver_possible_request(Client *client, const char *buf, int len)
+{
+	HttpMethod method;
+
+	if (len < 8)
+		return;
+
+	/* Probably redundant, but just to be sure, if already tagged, then don't change it! */
+	if (WEB(client))
+		return;
+
+	method = webserver_get_method(buf);
+	if (method == HTTP_METHOD_NONE)
+		return; /* invalid */
+
+	moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
+	WEB(client)->method = method;
+
+	/* Set some default values: */
+	WEB(client)->content_length = -1;
+	WEB(client)->config_max_request_buffer_size = 4096; /* 4k */
+}
+
+/** Incoming packet hook. This processes web requests.
+ * NOTE The different return values:
+ * -1 means: don't touch this client anymore, it has or might have been killed!
+ * 0 means: don't process this data, but you can read another packet if you want
+ * >0 means: process this data (regular IRC data, non-web stuff)
+ */
+int webserver_packet_in(Client *client, const char *readbuf, int *length)
+{
+	if ((client->local->traffic.messages_received == 0) && WEBSERVER(client))
+		webserver_possible_request(client, readbuf, *length);
+
+	if (!WEB(client))
+		return 1; /* "normal" IRC client */
+
+	if (WEB(client)->request_header_parsed)
+		return WEBSERVER(client)->handle_body(client, WEB(client), readbuf, *length);
+
+	/* else.. */
+	return webserver_handle_request_header(client, readbuf, length);
+}
+
+/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
+int webserver_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
+{
+	static char buf[4096], *nextptr;
+	char *p;
+	char *k = NULL, *v = NULL;
+	int foundlf = 0;
+
+	if (buffer)
+	{
+		/* Initialize */
+		if (len > sizeof(buf) - 1)
+			len = sizeof(buf) - 1;
+
+		memcpy(buf, buffer, len);
+		buf[len] = '\0';
+		nextptr = buf;
+	}
+
+	*end_of_request = 0;
+
+	p = nextptr;
+
+	if (!p)
+	{
+		*key = *value = NULL;
+		return 0; /* done processing data */
+	}
+
+	if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
+	{
+		*key = *value = NULL;
+		*end_of_request = 1;
+		return 0;
+	}
+
+	/* Note: p *could* point to the NUL byte ('\0') */
+
+	/* Special handling for GET line itself. */
+	if (webserver_get_method(p) != HTTP_METHOD_NONE)
+	{
+		k = "REQUEST";
+		p = strchr(p, ' ') + 1; /* space (0x20) is guaranteed to be there, see strncmp above */
+		v = p; /* SET VALUE */
+		nextptr = NULL; /* set to "we are done" in case next for loop fails */
+		for (; *p; p++)
+		{
+			if (*p == ' ')
+			{
+				*p = '\0'; /* terminate before "HTTP/1.X" part */
+			}
+			else if (*p == '\r')
+			{
+				*p = '\0'; /* eat silently, but don't consider EOL */
+			}
+			else if (*p == '\n')
+			{
+				*p = '\0';
+				nextptr = p+1; /* safe, there is data or at least a \0 there */
+				break;
+			}
+		}
+		*key = k;
+		*value = v;
+		return 1;
+	}
+
+	/* Header parsing starts here.
+	 * Example line "Host: www.unrealircd.org"
+	 */
+	k = p; /* SET KEY */
+
+	/* First check if the line contains a terminating \n. If not, don't process it
+	 * as it may have been a cut header.
+	 */
+	for (; *p; p++)
+	{
+		if (*p == '\n')
+		{
+			foundlf = 1;
+			break;
+		}
+	}
+
+	if (!foundlf)
+	{
+		*key = *value = NULL;
+		*lastloc = k;
+		return 0;
+	}
+
+	p = k;
+
+	for (; *p; p++)
+	{
+		if ((*p == '\n') || (*p == '\r'))
+		{
+			/* Reached EOL but 'value' not found */
+			*p = '\0';
+			break;
+		}
+		if (*p == ':')
+		{
+			*p++ = '\0';
+			if (*p++ != ' ')
+				break; /* missing mandatory space after ':' */
+
+			v = p; /* SET VALUE */
+			nextptr = NULL; /* set to "we are done" in case next for loop fails */
+			for (; *p; p++)
+			{
+				if (*p == '\r')
+				{
+					*p = '\0'; /* eat silently, but don't consider EOL */
+				}
+				else if (*p == '\n')
+				{
+					*p = '\0';
+					nextptr = p+1; /* safe, there is data or at least a \0 there */
+					break;
+				}
+			}
+			/* A key-value pair was succesfully parsed, return it */
+			*key = k;
+			*value = v;
+			return 1;
+		}
+	}
+
+	/* Fatal parse error */
+	*key = *value = NULL;
+	return 0;
+}
+
+/** Check if there is any data at the end of the request */
+char *find_end_of_request(char *header, int totalsize, int *remaining_bytes)
+{
+	char *nextframe1;
+	char *nextframe2;
+	char *nextframe = NULL;
+
+	// find first occurance, yeah this is just stupid, but it works.
+	nextframe1 = strstr(header, "\r\n\r\n"); // = +4
+	nextframe2 = strstr(header, "\n\n");     // = +2
+	if (nextframe1 && nextframe2)
+	{
+		if (nextframe1 < nextframe2)
+		{
+			nextframe = nextframe1 + 4;
+		} else {
+			nextframe = nextframe2 + 2;
+		}
+	} else
+	if (nextframe1)
+	{
+		nextframe = nextframe1 + 4;
+	} else
+	if (nextframe2)
+	{
+		nextframe = nextframe2 + 2;
+	}
+	if (nextframe)
+	{
+		*remaining_bytes = totalsize - (nextframe - header);
+		if (*remaining_bytes > 0)
+			return nextframe;
+	}
+	return NULL;
+}
+
+/** Handle HTTP request
+ * Yes, I'm going to assume that the header fits in one packet and one packet only.
+ */
+int webserver_handle_request_header(Client *client, const char *readbuf, int *length)
+{
+	char *key, *value;
+	int r, end_of_request;
+	static char netbuf[16384];
+	static char netbuf2[16384];
+	char *lastloc = NULL;
+	int n, maxcopy, nprefix=0;
+	int totalsize;
+
+	/* Totally paranoid: */
+	memset(netbuf, 0, sizeof(netbuf));
+	memset(netbuf2, 0, sizeof(netbuf2));
+
+	/** Frame re-assembling starts here **/
+	if (WEB(client)->lefttoparse)
+	{
+		strlcpy(netbuf, WEB(client)->lefttoparse, sizeof(netbuf));
+		nprefix = strlen(netbuf);
+	}
+	maxcopy = sizeof(netbuf) - nprefix - 1;
+	/* (Need to do some manual checking here as strlen() can't be safely used
+	 *  on readbuf. Same is true for strlncat since it uses strlen().)
+	 */
+	n = *length;
+	if (n > maxcopy)
+		n = maxcopy;
+	if (n <= 0)
+	{
+		webserver_close_client(client); // Oversized line
+		return -1;
+	}
+	memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
+	totalsize = n + nprefix;
+	netbuf[totalsize] = '\0';
+	memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc".
+	safe_free(WEB(client)->lefttoparse);
+
+	/** Now step through the lines.. **/
+	for (r = webserver_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
+	     r;
+	     r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
+	{
+		if (BadPtr(value))
+			continue; /* skip empty values */
+
+		if (!strcasecmp(key, "REQUEST"))
+		{
+			safe_strdup(WEB(client)->uri, value);
+		} else
+		{
+			if (!strcasecmp(key, "Content-Length"))
+			{
+				WEB(client)->content_length = atoll(value);
+			} else
+			if (!strcasecmp(key, "Transfer-Encoding"))
+			{
+				if (!strcasecmp(value, "chunked"))
+					WEB(client)->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
+			}
+			add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value);
+		}
+	}
+
+	if (end_of_request)
+	{
+		int n;
+		int remaining_bytes = 0;
+		char *nextframe;
+
+		/* Some sanity checks */
+		if (!WEB(client)->uri)
+		{
+			webserver_send_response(client, 400, "Malformed HTTP request");
+			return -1;
+		}
+
+		WEB(client)->request_header_parsed = 1;
+		n = WEBSERVER(client)->handle_request(client, WEB(client));
+		if ((n <= 0) || IsDead(client))
+			return n; /* byebye */
+		
+		/* There could be data directly after the request header (eg for
+		 * a POST or PUT), check for it here so it isn't lost.
+		 */
+		nextframe = find_end_of_request(netbuf2, totalsize, &remaining_bytes);
+		if (nextframe)
+			return WEBSERVER(client)->handle_body(client, WEB(client), nextframe, remaining_bytes);
+		return 0;
+	}
+
+	if (lastloc)
+	{
+		/* Last line was cut somewhere, save it for next round. */
+		safe_strdup(WEB(client)->lefttoparse, lastloc);
+	}
+	return 0; /* don't let UnrealIRCd process this */
+}
+
+/** Send a HTTP(S) response.
+ * @param client	Client to send to
+ * @param status	HTTP status code
+ * @param msg		The message body.
+ * @note if 'msgs' is NULL then don't close the connection.
+ */
+void _webserver_send_response(Client *client, int status, char *msg)
+{
+	char buf[512];
+	char *statusmsg = "???";
+
+	if (status == 200)
+		statusmsg = "OK";
+	else if (status == 201)
+		statusmsg = "Created";
+	else if (status == 500)
+		statusmsg = "Internal Server Error";
+	else if (status == 400)
+		statusmsg = "Bad Request";
+	else if (status == 401)
+		statusmsg = "Unauthorized";
+	else if (status == 403)
+		statusmsg = "Forbidden";
+	else if (status == 404)
+		statusmsg = "Not Found";
+	else if (status == 416)
+		statusmsg = "Range Not Satisfiable";
+
+	snprintf(buf, sizeof(buf),
+		"HTTP/1.1 %d %s\r\nServer: %s\r\nConnection: close\r\n\r\n",
+		status, statusmsg, WEB_SOFTWARE);
+	if (msg)
+	{
+		strlcat(buf, msg, sizeof(buf));
+		strlcat(buf, "\n", sizeof(buf));
+	}
+
+	dbuf_put(&client->local->sendQ, buf, strlen(buf));
+	if (msg)
+		webserver_close_client(client);
+}
+
+/** Close a web client softly, after data has been sent. */
+void _webserver_close_client(Client *client)
+{
+	send_queued(client);
+	if (DBufLength(&client->local->sendQ) == 0)
+	{
+		exit_client(client, NULL, "End of request");
+		//dead_socket(client, "");
+	} else {
+		send_queued(client);
+		reset_handshake_timeout(client, WEB_CLOSE_TIME);
+	}
+}
+
+int webserver_handle_body_append_buffer(Client *client, const char *buf, int len)
+{
+	/* Guard.. */
+	if (len <= 0)
+	{
+		dead_socket(client, "HTTP request error");
+		return 0;
+	}
+
+	if (WEB(client)->request_buffer)
+	{
+		long long newsize = WEB(client)->request_buffer_size + len + 1;
+		if (newsize > WEB(client)->config_max_request_buffer_size)
+		{
+			/* We would overflow */
+			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
+			           "[webserver] Client $client: request body too large ($length)",
+			           log_data_integer("length", newsize));
+			dead_socket(client, "");
+			return 0;
+		}
+		WEB(client)->request_buffer = realloc(WEB(client)->request_buffer, newsize);
+	} else
+	{
+		if (len + 1 > WEB(client)->config_max_request_buffer_size)
+		{
+			/* We would overflow */
+			unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
+			           "[webserver] Client $client: request body too large ($length)",
+			           log_data_integer("length", len+1));
+			dead_socket(client, "");
+			return 0;
+		}
+		WEB(client)->request_buffer = malloc(len+1);
+	}
+	memcpy(WEB(client)->request_buffer + WEB(client)->request_buffer_size, buf, len);
+	WEB(client)->request_buffer_size += len;
+	WEB(client)->request_buffer[WEB(client)->request_buffer_size] = '\0';
+	return 1;
+}
+
+/** Handle HTTP body parsing, eg for a PUT request, concatting it all together.
+ * @param client	The client
+ * @param web		The WEB(client)
+ * @param readbuf	Packet in the read buffer
+ * @param pktsize	Packet size of the read buffer
+ * @return 1 to continue processing, 0 if client is killed.
+ */
+int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int pktsize)
+{
+	char *buf;
+	long long n;
+	char *free_this_buffer = NULL;
+
+	if (WEB(client)->transfer_encoding == TRANSFER_ENCODING_NONE)
+	{
+		if (!webserver_handle_body_append_buffer(client, readbuf, pktsize))
+			return 0;
+
+		if ((WEB(client)->content_length >= 0) &&
+		    (WEB(client)->request_buffer_size >= WEB(client)->content_length))
+		{
+			WEB(client)->request_body_complete = 1;
+		}
+		return 1;
+	}
+
+	/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
+	 * Makes things easy.
+	 */
+	if (WEB(client)->lefttoparse)
+	{
+		n = WEB(client)->lefttoparselen + pktsize;
+		free_this_buffer = buf = safe_alloc(n);
+		memcpy(buf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen);
+		memcpy(buf+WEB(client)->lefttoparselen, readbuf, pktsize);
+		safe_free(WEB(client)->lefttoparse);
+		WEB(client)->lefttoparselen = 0;
+	} else {
+		n = pktsize;
+		free_this_buffer = buf = safe_alloc(n);
+		memcpy(buf, readbuf, n);
+	}
+
+	/* Chunked transfers.. yayyyy.. */
+	while (n > 0)
+	{
+		if (WEB(client)->chunk_remaining > 0)
+		{
+			/* Eat it */
+			int eat = MIN(WEB(client)->chunk_remaining, n);
+			if (!webserver_handle_body_append_buffer(client, buf, eat))
+			{
+				/* fatal error such as size exceeded */
+				safe_free(free_this_buffer);
+				return 0;
+			}
+			n -= eat;
+			buf += eat;
+			WEB(client)->chunk_remaining -= eat;
+		} else
+		{
+			int gotlf = 0;
+			int i;
+
+			/* First check if it is a (trailing) empty line,
+			 * eg from a previous chunk. Skip over.
+			 */
+			if ((n >= 2) && !strncmp(buf, "\r\n", 2))
+			{
+				buf += 2;
+				n -= 2;
+			} else
+			if ((n >= 1) && !strncmp(buf, "\n", 1))
+			{
+				buf++;
+				n--;
+			}
+
+			/* Now we are (possibly) at the chunk size line,
+			 * this is or example '7f' + newline.
+			 * So first, check if we have a newline at all.
+			 */
+			for (i=0; i < n; i++)
+			{
+				if (buf[i] == '\n')
+				{
+					gotlf = 1;
+					break;
+				}
+			}
+			if (!gotlf)
+			{
+				/* The line telling us the chunk size is incomplete,
+				 * as it does not contain an \n. Wait for more data
+				 * from the network socket.
+				 */
+				if (n > 0)
+				{
+					/* Store what we have first.. */
+					WEB(client)->lefttoparselen = n;
+					WEB(client)->lefttoparse = safe_alloc(n);
+					memcpy(WEB(client)->lefttoparse, buf, n);
+				}
+				safe_free(free_this_buffer);
+				return 1; /* WE WANT MORE! */
+			}
+			buf[i] = '\0'; /* cut at LF */
+			i++; /* point to next data */
+			WEB(client)->chunk_remaining = strtol(buf, NULL, 16);
+			if (WEB(client)->chunk_remaining < 0)
+			{
+				unreal_log(ULOG_WARNING, "webserver", "WEB_NEGATIVE_CHUNK", client,
+				           "Webrequest from $client: Negative chunk encountered");
+				safe_free(free_this_buffer);
+				dead_socket(client, "");
+				return 0;
+			}
+			if (WEB(client)->chunk_remaining == 0)
+			{
+				/* DONE! */
+				WEB(client)->request_body_complete = 1;
+				safe_free(free_this_buffer);
+				return 1;
+			}
+			buf += i;
+			n -= i;
+		}
+	}
+
+	safe_free(free_this_buffer);
+	return 1;
+}
diff --git a/src/modules/websocket.c b/src/modules/websocket.c
@@ -27,38 +27,11 @@ ModuleHeader MOD_HEADER
  #define WEBSOCKET_SEND_BUFFER_SIZE 16384
 #endif
 
-typedef enum WebSocketType {
-	WEBSOCKET_TYPE_BINARY = 1,
-	WEBSOCKET_TYPE_TEXT   = 2
-} WebSocketType;
-
-typedef struct WebSocketUser WebSocketUser;
-struct WebSocketUser {
-	char get; /**< GET initiated */
-	char handshake_completed; /**< Handshake completed, use websocket frames */
-	char *handshake_key; /**< Handshake key (used during handshake) */
-	char *lefttoparse; /**< Leftover buffer to parse */
-	int lefttoparselen; /**< Length of lefttoparse buffer */
-	WebSocketType type; /**< WEBSOCKET_TYPE_BINARY or WEBSOCKET_TYPE_TEXT */
-	char *sec_websocket_protocol; /**< Only valid during parsing of the request, after that it is NULL again */
-	char *forwarded; /**< Unparsed `Forwarded:` header, RFC 7239 */
-	int secure; /**< If there is a Forwarded header, this indicates if the remote connection is secure */
-};
-
 #define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
 
 #define WEBSOCKET_PORT(client)	((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
 #define WEBSOCKET_TYPE(client)	(WSU(client)->type)
 
-#define WEBSOCKET_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" /* see RFC6455 */
-
-#define WSOP_CONTINUATION 0x00
-#define WSOP_TEXT         0x01
-#define WSOP_BINARY       0x02
-#define WSOP_CLOSE        0x08
-#define WSOP_PING         0x09
-#define WSOP_PONG         0x0a
-
 /* used to parse http Forwarded header (RFC 7239) */
 #define IPLEN 48
 #define FHEADER_NAMELEN	20
@@ -72,20 +45,16 @@ struct HTTPForwardedHeader
 
 /* Forward declarations */
 int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int websocket_config_posttest(int *);
 int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
 int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
-int websocket_packet_in(Client *client, const char *readbuf, int *length);
-void websocket_mdata_free(ModData *m);
-int websocket_handle_packet(Client *client, const char *readbuf, int length);
 int websocket_handle_handshake(Client *client, const char *readbuf, int *length);
 int websocket_handshake_send_response(Client *client);
-int websocket_handle_packet_ping(Client *client, const char *buf, int len);
-int websocket_handle_packet_pong(Client *client, const char *buf, int len);
-int websocket_create_packet(int opcode, char **buf, int *len);
-int websocket_send_pong(Client *client, const char *buf, int len);
+int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
 int websocket_secure_connect(Client *client);
 struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
 int websocket_ip_compare(const char *ip1, const char *ip2);
+int websocket_handle_request(Client *client, WebRequest *web);
 
 /* Global variables */
 ModDataInfo *websocket_md;
@@ -94,6 +63,10 @@ static int ws_text_mode_available = 1;
 MOD_TEST()
 {
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, websocket_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, websocket_config_posttest);
+
+	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
 	return MOD_SUCCESS;
 }
 
@@ -103,20 +76,16 @@ MOD_INIT()
 
 	MARK_AS_OFFICIAL_MODULE(modinfo);
 
+	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
+	if (!websocket_md)
+		config_warn("The 'websocket_common' module is not loaded, even though it was promised to be ???");
+
 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, websocket_config_run_ex);
 	HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, websocket_packet_out);
-	HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, websocket_packet_in);
 	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, websocket_secure_connect);
 
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.name = "websocket";
-	mreq.serialize = NULL;
-	mreq.unserialize = NULL;
-	mreq.free = websocket_mdata_free;
-	mreq.sync = 0;
-	mreq.type = MODDATATYPE_CLIENT;
-	websocket_md = ModDataAdd(modinfo->handle, mreq);
-
+	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
 	return MOD_SUCCESS;
 }
 
@@ -224,6 +193,9 @@ int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr
 		return 0;
 
 	l = (ConfigItem_listen *)ptr;
+	l->webserver = safe_alloc(sizeof(WebServer));
+	l->webserver->handle_request = websocket_handle_request;
+	l->webserver->handle_body = websocket_handle_body_websocket;
 
 	for (cep = ce->items; cep; cep = cep->next)
 	{
@@ -253,250 +225,96 @@ int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr
 	return 1;
 }
 
-/** UnrealIRCd internals: free WebSocketUser object. */
-void websocket_mdata_free(ModData *m)
+int websocket_config_posttest(int *errs)
 {
-	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
-	if (wsu)
+	int errors = 0;
+	char webserver_module = 1, websocket_common_module = 1;
+
+	if (!is_module_loaded("webserver"))
 	{
-		safe_free(wsu->handshake_key);
-		safe_free(wsu->lefttoparse);
-		safe_free(wsu->sec_websocket_protocol);
-		safe_free(wsu->forwarded);
-		safe_free(m->ptr);
+		config_error("The 'websocket' module requires the 'webserver' module to be loaded, otherwise websocket connections will not work!");
+		webserver_module = 0;
+		errors++;
 	}
-}
-
-/** Outgoing packet hook.
- * This transforms the output to be Websocket-compliant, if necessary.
- */
-int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
-{
-	static char utf8buf[510];
 
-	if (MyConnect(to) && WSU(to) && WSU(to)->handshake_completed)
+	if (!is_module_loaded("websocket_common"))
 	{
-		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
-			websocket_create_packet(WSOP_BINARY, msg, length);
-		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
-		{
-			/* Some more conversions are needed */
-			char *safe_msg = unrl_utf8_make_valid(*msg, utf8buf, sizeof(utf8buf), 1);
-			*msg = safe_msg;
-			*length = *msg ? strlen(safe_msg) : 0;
-			websocket_create_packet(WSOP_TEXT, msg, length);
-		}
-		return 0;
+		config_error("The 'websocket' module requires the 'websocket_common' module to be loaded, otherwise websocket connections will not work!");
+		websocket_common_module = 0;
+		errors++;
 	}
-	return 0;
+
+	/* Is nicer for the admin when these are grouped... */
+	if (!webserver_module)
+		config_error("Add the following line to your config file: loadmodule \"webserver\";");
+	if (!websocket_common_module)
+		config_error("Add the following line to your config file: loadmodule \"websocket_common\";");
+
+	*errs = errors;
+	return errors ? -1 : 1;
 }
 
-int websocket_handle_websocket(Client *client, const char *readbuf2, int length2)
+/* Add LF (if needed) to a buffer. Max 4K. */
+void add_lf_if_needed(char **buf, int *len)
 {
-	int n;
-	char *ptr;
-	int length;
-	int length1 = WSU(client)->lefttoparselen;
-	char readbuf[4096];
-
-	length = length1 + length2;
-	if (length > sizeof(readbuf)-1)
-	{
-		dead_socket(client, "Illegal buffer stacking/Excess flood");
-		return 0;
-	}
+	static char newbuf[MAXLINELENGTH];
+	char *b = *buf;
+	int l = *len;
 
-	if (length1 > 0)
-		memcpy(readbuf, WSU(client)->lefttoparse, length1);
-	memcpy(readbuf+length1, readbuf2, length2);
+	if (l <= 0)
+		return; /* too short */
 
-	safe_free(WSU(client)->lefttoparse);
-	WSU(client)->lefttoparselen = 0;
+	if (b[l - 1] == '\n')
+		return; /* already contains \n */
 
-	ptr = readbuf;
-	do {
-		n = websocket_handle_packet(client, ptr, length);
-		if (n < 0)
-			return -1; /* killed -- STOP processing */
-		if (n == 0)
-		{
-			/* Short read. Stop processing for now, but save data for next time */
-			safe_free(WSU(client)->lefttoparse);
-			WSU(client)->lefttoparse = safe_alloc(length);
-			WSU(client)->lefttoparselen = length;
-			memcpy(WSU(client)->lefttoparse, ptr, length);
-			return 0;
-		}
-		length -= n;
-		ptr += n;
-		if (length < 0)
-			abort(); /* less than 0 is impossible */
-	} while(length > 0);
+	if (l >= sizeof(newbuf)-2)
+		l = sizeof(newbuf)-2; /* cut-off if necessary */
 
-	return 0;
+	memcpy(newbuf, b, l);
+	newbuf[l] = '\n';
+	newbuf[l + 1] = '\0'; /* not necessary, but I like zero termination */
+	l++;
+	*buf = newbuf; /* new buffer */
+	*len = l; /* new length */
 }
 
-/** Incoming packet hook.
- * This processes websocket frames, if this is a websocket connection.
- * NOTE The different return values:
- * -1 means: don't touch this client anymore, it has or might have been killed!
- * 0 means: don't process this data, but you can read another packet if you want
- * >0 means: process this data (regular IRC data, non-websocket stuff)
+/** Called on decoded websocket frame (INPUT).
+ * Should contain exactly 1 IRC line (command)
  */
-int websocket_packet_in(Client *client, const char *readbuf, int *length)
+int websocket_irc_callback(Client *client, char *buf, int len)
 {
-	if ((client->local->traffic.messages_received == 0) &&
-	    WEBSOCKET_PORT(client) &&
-	    !WSU(client) &&
-	    (*length > 8) &&
-	    !strncmp(readbuf, "GET ", 4))
-	{
-		/* Allocate a new WebSocketUser struct for this session */
-		moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
-		WSU(client)->get = 1;
-		WSU(client)->type = client->local->listener->websocket_options; /* the default, unless the client chooses otherwise */
-	}
-
-	if (!WSU(client))
-		return 1; /* "normal" IRC client */
-
-	if (WSU(client)->handshake_completed)
-		return websocket_handle_websocket(client, readbuf, *length);
-	/* else.. */
-	return websocket_handle_handshake(client, readbuf, length);
+	add_lf_if_needed(&buf, &len);
+	if (!process_packet(client, buf, len, 1)) /* Let UnrealIRCd handle this as usual */
+		return 0; /* client killed */
+	return 1;
 }
 
-/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
-int websocket_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
+int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
 {
-	static char buf[4096], *nextptr;
-	char *p;
-	char *k = NULL, *v = NULL;
-	int foundlf = 0;
-
-	if (buffer)
-	{
-		/* Initialize */
-		if (len > sizeof(buf) - 1)
-			len = sizeof(buf) - 1;
-
-		memcpy(buf, buffer, len);
-		buf[len] = '\0';
-		nextptr = buf;
-	}
-
-	*end_of_request = 0;
-
-	p = nextptr;
-
-	if (!p)
-	{
-		*key = *value = NULL;
-		return 0; /* done processing data */
-	}
-
-	if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
-	{
-		*key = *value = NULL;
-		*end_of_request = 1;
-		return 0;
-	}
-
-	/* Note: p *could* point to the NUL byte ('\0') */
-
-	/* Special handling for GET line itself. */
-	if (!strncmp(p, "GET ", 4))
-	{
-		k = "GET";
-		p += 4;
-		v = p; /* SET VALUE */
-		nextptr = NULL; /* set to "we are done" in case next for loop fails */
-		for (; *p; p++)
-		{
-			if (*p == ' ')
-			{
-				*p = '\0'; /* terminate before "HTTP/1.X" part */
-			}
-			else if (*p == '\r')
-			{
-				*p = '\0'; /* eat silently, but don't consider EOL */
-			}
-			else if (*p == '\n')
-			{
-				*p = '\0';
-				nextptr = p+1; /* safe, there is data or at least a \0 there */
-				break;
-			}
-		}
-		*key = k;
-		*value = v;
-		return 1;
-	}
+	return websocket_handle_websocket(client, web, readbuf2, length2, websocket_irc_callback);
+}
 
-	/* Header parsing starts here.
-	 * Example line "Host: www.unrealircd.org"
-	 */
-	k = p; /* SET KEY */
+/** Outgoing packet hook.
+ * This transforms the output to be Websocket-compliant, if necessary.
+ */
+int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
+{
+	static char utf8buf[510];
 
-	/* First check if the line contains a terminating \n. If not, don't process it
-	 * as it may have been a cut header.
-	 */
-	for (; *p; p++)
+	if (MyConnect(to) && !IsRPC(to) && websocket_md && WSU(to) && WSU(to)->handshake_completed)
 	{
-		if (*p == '\n')
+		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
+			websocket_create_packet(WSOP_BINARY, msg, length);
+		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
 		{
-			foundlf = 1;
-			break;
+			/* Some more conversions are needed */
+			char *safe_msg = unrl_utf8_make_valid(*msg, utf8buf, sizeof(utf8buf), 1);
+			*msg = safe_msg;
+			*length = *msg ? strlen(safe_msg) : 0;
+			websocket_create_packet(WSOP_TEXT, msg, length);
 		}
-	}
-
-	if (!foundlf)
-	{
-		*key = *value = NULL;
-		*lastloc = k;
 		return 0;
 	}
-
-	p = k;
-
-	for (; *p; p++)
-	{
-		if ((*p == '\n') || (*p == '\r'))
-		{
-			/* Reached EOL but 'value' not found */
-			*p = '\0';
-			break;
-		}
-		if (*p == ':')
-		{
-			*p++ = '\0';
-			if (*p++ != ' ')
-				break; /* missing mandatory space after ':' */
-
-			v = p; /* SET VALUE */
-			nextptr = NULL; /* set to "we are done" in case next for loop fails */
-			for (; *p; p++)
-			{
-				if (*p == '\r')
-				{
-					*p = '\0'; /* eat silently, but don't consider EOL */
-				}
-				else if (*p == '\n')
-				{
-					*p = '\0';
-					nextptr = p+1; /* safe, there is data or at least a \0 there */
-					break;
-				}
-			}
-			/* A key-value pair was succesfully parsed, return it */
-			*key = k;
-			*value = v;
-			return 1;
-		}
-	}
-
-	/* Fatal parse error */
-	*key = *value = NULL;
 	return 0;
 }
 
@@ -635,9 +453,49 @@ struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input)
 	return &forwarded;
 }
 
-/** Finally, validate the websocket request (handshake) and proceed or reject. */
-int websocket_handshake_valid(Client *client)
+/** We got a HTTP(S) request and we need to check if we can upgrade the connection
+ * to a websocket connection.
+ */
+int websocket_handle_request(Client *client, WebRequest *web)
 {
+	NameValuePrioList *r;
+	const char *key, *value;
+
+	/* Allocate a new WebSocketUser struct for this session */
+	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
+	/* ...and set the default protocol (text or binary) */
+	WSU(client)->type = client->local->listener->websocket_options;
+
+	/** Now step through the lines.. **/
+	for (r = web->headers; r; r = r->next)
+	{
+		key = r->name;
+		value = r->value;
+		if (!strcasecmp(key, "Sec-WebSocket-Key"))
+		{
+			if (strchr(value, ':'))
+			{
+				/* This would cause unserialization issues. Should be base64 anyway */
+				webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
+				return -1;
+			}
+			safe_strdup(WSU(client)->handshake_key, value);
+		} else
+		if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
+		{
+			/* Save it here, will be processed later */
+			safe_strdup(WSU(client)->sec_websocket_protocol, value);
+		} else
+		if (!strcasecmp(key, "Forwarded"))
+		{
+			/* will be processed later too */
+			safe_strdup(WSU(client)->forwarded, value);
+		}
+	}
+
+	/** Finally, validate the websocket request (handshake) and proceed or reject. */
+
+	/* Not websocket and webredir loaded? Let that module serve a redirect. */
 	if (!WSU(client)->handshake_key)
 	{
 		if (is_module_loaded("webredir"))
@@ -645,9 +503,11 @@ int websocket_handshake_valid(Client *client)
 			const char *parx[2] = { NULL, NULL };
 			do_cmd(client, NULL, "GET", 1, parx);
 		}
-		dead_socket(client, "Invalid WebSocket request");
+		webserver_send_response(client, 404, "This port is for IRC WebSocket only");
 		return 0;
 	}
+
+	/* Sec-WebSocket-Protocol (optional) */
 	if (WSU(client)->sec_websocket_protocol)
 	{
 		char *p = NULL, *name;
@@ -684,6 +544,8 @@ int websocket_handshake_valid(Client *client)
 			safe_free(WSU(client)->sec_websocket_protocol);
 		}
 	}
+
+	/* Check forwarded header (by k4be) */
 	if (WSU(client)->forwarded)
 	{
 		struct HTTPForwardedHeader *forwarded;
@@ -693,7 +555,7 @@ int websocket_handshake_valid(Client *client)
 		if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
 		{
 			unreal_log(ULOG_WARNING, "websocket", "UNAUTHORIZED_FORWARDED_HEADER", client, "Received unauthorized Forwarded header from $ip", log_data_string("ip", client->ip));
-			dead_socket(client, "Forwarded: no access");
+			webserver_send_response(client, 403, "Forwarded: no access");
 			return 0;
 		}
 		/* parse the header */
@@ -702,7 +564,7 @@ int websocket_handshake_valid(Client *client)
 		if (!is_valid_ip(forwarded->ip))
 		{
 			unreal_log(ULOG_WARNING, "websocket", "INVALID_FORWARDED_IP", client, "Received invalid IP in Forwarded header from $ip", log_data_string("ip", client->ip));
-			dead_socket(client, "Forwarded: invalid IP");
+			webserver_send_response(client, 400, "Forwarded: invalid IP");
 			return 0;
 		}
 		/* store data */
@@ -738,6 +600,8 @@ int websocket_handshake_valid(Client *client)
 		}
 		RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
 	}
+
+	websocket_handshake_send_response(client);
 	return 1;
 }
 
@@ -747,88 +611,11 @@ int websocket_secure_connect(Client *client)
 	 * us that their [client]--[webirc gateway] connection is also
 	 * secure (eg: using https)
 	 */
-	if (IsSecureConnect(client) && WSU(client) && WSU(client)->forwarded && !WSU(client)->secure)
+	if (IsSecureConnect(client) && websocket_md && WSU(client) && WSU(client)->forwarded && !WSU(client)->secure)
 		client->umodes &= ~UMODE_SECURE;
 	return 0;
 }
 
-/** Handle client GET WebSocket handshake.
- * Yes, I'm going to assume that the header fits in one packet and one packet only.
- */
-int websocket_handle_handshake(Client *client, const char *readbuf, int *length)
-{
-	char *key, *value;
-	int r, end_of_request;
-	char netbuf[2048];
-	char *lastloc = NULL;
-	int n, maxcopy, nprefix=0;
-
-	/** Frame re-assembling starts here **/
-	*netbuf = '\0';
-	if (WSU(client)->lefttoparse)
-	{
-		strlcpy(netbuf, WSU(client)->lefttoparse, sizeof(netbuf));
-		nprefix = strlen(netbuf);
-	}
-	maxcopy = sizeof(netbuf) - nprefix - 1;
-	/* (Need to some manual checking here as strlen() can't be safely used
-	 *  on readbuf. Same is true for strlncat since it uses strlen().)
-	 */
-	n = *length;
-	if (n > maxcopy)
-		n = maxcopy;
-	if (n <= 0)
-	{
-		dead_socket(client, "Oversized line");
-		return -1;
-	}
-	memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
-	netbuf[n+nprefix] = '\0';
-	safe_free(WSU(client)->lefttoparse);
-
-	/** Now step through the lines.. **/
-	for (r = websocket_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
-	     r;
-	     r = websocket_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
-	{
-		if (!strcasecmp(key, "Sec-WebSocket-Key"))
-		{
-			if (strchr(value, ':'))
-			{
-				/* This would cause unserialization issues. Should be base64 anyway */
-				dead_socket(client, "Invalid characters in Sec-WebSocket-Key");
-				return -1;
-			}
-			safe_strdup(WSU(client)->handshake_key, value);
-		} else
-		if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
-		{
-			/* Save it here, will be processed later */
-			safe_strdup(WSU(client)->sec_websocket_protocol, value);
-		} else
-		if (!strcasecmp(key, "Forwarded"))
-		{
-			/* will be processed later too */
-			safe_strdup(WSU(client)->forwarded, value);
-		}
-	}
-
-	if (end_of_request)
-	{
-		if (!websocket_handshake_valid(client) || IsDead(client))
-			return -1;
-		websocket_handshake_send_response(client);
-		return 0;
-	}
-
-	if (lastloc)
-	{
-		/* Last line was cut somewhere, save it for next round. */
-		safe_strdup(WSU(client)->lefttoparse, lastloc);
-	}
-	return 0; /* don't let UnrealIRCd process this */
-}
-
 /** Complete the handshake by sending the appropriate HTTP 101 response etc. */
 int websocket_handshake_send_response(Client *client)
 {
@@ -870,313 +657,6 @@ int websocket_handshake_send_response(Client *client)
 	return 0;
 }
 
-/* Add LF (if needed) to a buffer. Max 4K. */
-void add_lf_if_needed(char **buf, int *len)
-{
-	static char newbuf[4096];
-	char *b = *buf;
-	int l = *len;
-
-	if (l <= 0)
-		return; /* too short */
-
-	if (b[l - 1] == '\n')
-		return; /* already contains \n */
-
-	if (l >= sizeof(newbuf)-2)
-		l = sizeof(newbuf)-2; /* cut-off if necessary */
-
-	memcpy(newbuf, b, l);
-	newbuf[l] = '\n';
-	newbuf[l + 1] = '\0'; /* not necessary, but I like zero termination */
-	l++;
-	*buf = newbuf; /* new buffer */
-	*len = l; /* new length */
-}
-
-/** WebSocket packet handler.
- * For more information on the format, check out page 28 of RFC6455.
- * @returns The number of bytes processed (the size of the frame)
- *          OR 0 to indicate a possible short read (want more data)
- *          OR -1 in case of an error.
- */
-int websocket_handle_packet(Client *client, const char *readbuf, int length)
-{
-	char opcode; /**< Opcode */
-	char masked; /**< Masked */
-	int len; /**< Length of the packet */
-	char maskkey[4]; /**< Key used for masking */
-	const char *p;
-	int total_packet_size;
-	char *payload = NULL;
-	static char payloadbuf[READBUF_SIZE];
-
-	if (length < 4)
-	{
-		/* WebSocket packet too short */
-		return 0;
-	}
-
-	/* fin    = readbuf[0] & 0x80; -- unused */
-	opcode = readbuf[0] & 0x7F;
-	masked = readbuf[1] & 0x80;
-	len    = readbuf[1] & 0x7F;
-	p = &readbuf[2]; /* point to next element */
-
-	/* actually 'fin' is unused.. we don't care. */
-
-	if (!masked)
-	{
-		dead_socket(client, "WebSocket packet not masked");
-		return -1; /* Having the masked bit set is required (RFC6455 p29) */
-	}
-
-	if (len == 127)
-	{
-		dead_socket(client, "WebSocket packet with insane size");
-		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
-	}
-
-	total_packet_size = len + 2 + 4; /* 2 for header, 4 for mask key, rest for payload */
-
-	/* Early (minimal) length check */
-	if (length < total_packet_size)
-	{
-		/* WebSocket frame too short */
-		return 0;
-	}
-
-	/* Len=126 is special. It indicates the data length is actually "126 or more" */
-	if (len == 126)
-	{
-		/* Extended payload length (16 bit). For packets of >=126 bytes */
-		len = (readbuf[2] << 8) + readbuf[3];
-		if (len < 126)
-		{
-			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
-			return -1; /* This is a violation (not a short read), see page 29 */
-		}
-		p += 2; /* advance pointer 2 bytes */
-
-		/* Need to check the length again, now it has changed: */
-		if (length < len + 4 + 4)
-		{
-			/* WebSocket frame too short */
-			return 0;
-		}
-		/* And update the packet size */
-		total_packet_size = len + 4 + 4; /* 4 for header, 4 for mask key, rest for payload */
-	}
-
-	memcpy(maskkey, p, 4);
-	p+= 4;
-
-	if (len > 0)
-	{
-		memcpy(payloadbuf, p, len);
-		payload = payloadbuf;
-	} /* else payload is NULL */
-
-	if (len > 0)
-	{
-		/* Unmask this thing (page 33, section 5.3) */
-		int n;
-		char v;
-		char *p;
-		for (p = payload, n = 0; n < len; n++)
-		{
-			v = *p;
-			*p++ = v ^ maskkey[n % 4];
-		}
-	}
-
-	switch(opcode)
-	{
-		case WSOP_CONTINUATION:
-		case WSOP_TEXT:
-		case WSOP_BINARY:
-			if (len > 0)
-			{
-				add_lf_if_needed(&payload, &len);
-				if (!process_packet(client, payload, len, 1)) /* let UnrealIRCd process this data */
-					return -1; /* fatal error occured (such as flood kill) */
-			}
-			return total_packet_size;
-
-		case WSOP_CLOSE:
-			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
-			return -1;
-
-		case WSOP_PING:
-			if (websocket_handle_packet_ping(client, payload, len) < 0)
-				return -1;
-			return total_packet_size;
-
-		case WSOP_PONG:
-			if (websocket_handle_packet_pong(client, payload, len) < 0)
-				return -1;
-			return total_packet_size;
-
-		default:
-			dead_socket(client, "WebSocket: Unknown opcode");
-			return -1;
-	}
-
-	return -1; /* NOTREACHED */
-}
-
-int websocket_handle_packet_ping(Client *client, const char *buf, int len)
-{
-	if (len > 500)
-	{
-		dead_socket(client, "WebSocket: oversized PING request");
-		return -1;
-	}
-	websocket_send_pong(client, buf, len);
-	add_fake_lag(client, 1000); /* lag penalty of 1 second */
-	return 0;
-}
-
-int websocket_handle_packet_pong(Client *client, const char *buf, int len)
-{
-	/* We don't care */
-	return 0;
-}
-
-/** Create a simple websocket packet that is ready to be send.
- * This is the simple version that is used ONLY for WSOP_PONG,
- * as it does not take \r\n into account.
- */
-int websocket_create_packet_simple(int opcode, const char **buf, int *len)
-{
-	static char sendbuf[8192];
-
-	sendbuf[0] = opcode | 0x80; /* opcode & final */
-
-	if (*len > sizeof(sendbuf) - 8)
-		return -1; /* should never happen (safety) */
-
-	if (*len < 126)
-	{
-		/* Short payload */
-		sendbuf[1] = (char)*len;
-		memcpy(&sendbuf[2], *buf, *len);
-		*buf = sendbuf;
-		*len += 2;
-	} else {
-		/* Long payload */
-		sendbuf[1] = 126;
-		sendbuf[2] = (char)((*len >> 8) & 0xFF);
-		sendbuf[3] = (char)(*len & 0xFF);
-		memcpy(&sendbuf[4], *buf, *len);
-		*buf = sendbuf;
-		*len += 4;
-	}
-	return 0;
-}
-
-/** Create a websocket packet that is ready to be send.
- * This is the more complex version that takes into account
- * stripping off \r and \n, and possibly multi line due to
- * labeled-response. It is used for WSOP_TEXT and WSOP_BINARY.
- * The end result is one or more websocket frames,
- * all in a single packet *buf with size *len.
- */
-int websocket_create_packet(int opcode, char **buf, int *len)
-{
-	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
-	char *s = *buf; /* points to start of current line */
-	char *s2; /* used for searching of end of current line */
-	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
-	int bytes_to_copy;
-	char newline;
-	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
-	int bytes_in_sendbuf = 0;
-	int bytes_single_frame;
-
-	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
-	if (*len == 0)
-		return -1;
-
-	do {
-		/* Find next \r or \n */
-		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
-		{
-			if ((*s2 == '\n') || (*s2 == '\r'))
-				break;
-		}
-
-		/* Now 's' points to start of line and 's2' points to beyond end of the line
-		 * (either at \r, \n or beyond the buffer).
-		 */
-		bytes_to_copy = s2 - s;
-
-		if (bytes_to_copy < 126)
-			bytes_single_frame = 2 + bytes_to_copy;
-		else
-			bytes_single_frame = 4 + bytes_to_copy;
-
-		if (bytes_in_sendbuf + bytes_single_frame > sizeof(sendbuf))
-		{
-			/* Overflow. This should never happen. */
-			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
-			           "[BUG] [websocket] Overflow prevented in websocket_create_packet(): "
-			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
-			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
-			           log_data_integer("bytes_single_frame", bytes_single_frame),
-			           log_data_integer("sendbuf_size", sizeof(sendbuf)));
-			return -1;
-		}
-
-		/* Create the new frame */
-		o[0] = opcode | 0x80; /* opcode & final */
-
-		if (bytes_to_copy < 126)
-		{
-			/* Short payload */
-			o[1] = (char)bytes_to_copy;
-			memcpy(&o[2], s, bytes_to_copy);
-		} else {
-			/* Long payload */
-			o[1] = 126;
-			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
-			o[3] = (char)(bytes_to_copy & 0xFF);
-			memcpy(&o[4], s, bytes_to_copy);
-		}
-
-		/* Advance destination pointer and counter */
-		o += bytes_single_frame;
-		bytes_in_sendbuf += bytes_single_frame;
-
-		/* Advance source pointer and skip all trailing \n and \r */
-		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
-	} while(s <= lastbyte);
-
-	*buf = sendbuf;
-	*len = bytes_in_sendbuf;
-	return 0;
-}
-
-/** Create and send a WSOP_PONG frame */
-int websocket_send_pong(Client *client, const char *buf, int len)
-{
-	const char *b = buf;
-	int l = len;
-
-	if (websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
-		return -1;
-
-	if (DBufLength(&client->local->sendQ) > get_sendq(client))
-	{
-		dead_socket(client, "Max SendQ exceeded");
-		return -1;
-	}
-
-	dbuf_put(&client->local->sendQ, b, l);
-	send_queued(client);
-	return 0;
-}
-
 /** Compare IP addresses (for authorization checking) */
 int websocket_ip_compare(const char *ip1, const char *ip2)
 {
diff --git a/src/modules/websocket_common.c b/src/modules/websocket_common.c
@@ -0,0 +1,523 @@
+/*
+ * websocket_common - Common WebSocket functions (RFC6455)
+ * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
+ * License: GPLv2 or later
+ * The websocket module was sponsored by Aberrant Software Inc.
+ */
+   
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER
+  = {
+	"websocket_common",
+	"6.0.0",
+	"WebSocket support (RFC6455)",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+    };
+
+#if CHAR_MIN < 0
+ #error "In UnrealIRCd char should always be unsigned. Check your compiler"
+#endif
+
+#ifndef WEBSOCKET_SEND_BUFFER_SIZE
+ #define WEBSOCKET_SEND_BUFFER_SIZE 16384
+#endif
+
+#define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
+
+/* used to parse http Forwarded header (RFC 7239) */
+#define IPLEN 48
+#define FHEADER_NAMELEN	20
+
+struct HTTPForwardedHeader
+{
+	int secure;
+	char hostname[HOSTLEN+1];
+	char ip[IPLEN+1];
+};
+
+/* Forward declarations - public functions */
+int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
+int _websocket_create_packet(int opcode, char **buf, int *len);
+int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
+int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
+/* Forward declarations - other */
+int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
+int websocket_handle_packet_ping(Client *client, const char *buf, int len);
+int websocket_handle_packet_pong(Client *client, const char *buf, int len);
+int websocket_send_pong(Client *client, const char *buf, int len);
+const char *websocket_mdata_serialize(ModData *m);
+void websocket_mdata_unserialize(const char *str, ModData *m);
+void websocket_mdata_free(ModData *m);
+
+/* Global variables */
+ModDataInfo *websocket_md;
+static int ws_text_mode_available = 1;
+
+MOD_TEST()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
+	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
+
+	/* Init first, since we manage sockets */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);
+
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	ModDataInfo mreq;
+
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.name = "websocket";
+	mreq.serialize = websocket_mdata_serialize;
+	mreq.unserialize = websocket_mdata_unserialize;
+	mreq.free = websocket_mdata_free;
+	mreq.sync = MODDATA_SYNC_EARLY;
+	mreq.type = MODDATATYPE_CLIENT;
+	websocket_md = ModDataAdd(modinfo->handle, mreq);
+
+	/* Unload last, since we manage sockets */
+	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);
+
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	return MOD_SUCCESS;
+}
+
+int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
+{
+	int n;
+	char *ptr;
+	int length;
+	int length1 = WSU(client)->lefttoparselen;
+	char readbuf[MAXLINELENGTH];
+
+	length = length1 + length2;
+	if (length > sizeof(readbuf)-1)
+	{
+		dead_socket(client, "Illegal buffer stacking/Excess flood");
+		return 0;
+	}
+
+	if (length1 > 0)
+		memcpy(readbuf, WSU(client)->lefttoparse, length1);
+	memcpy(readbuf+length1, readbuf2, length2);
+
+	safe_free(WSU(client)->lefttoparse);
+	WSU(client)->lefttoparselen = 0;
+
+	ptr = readbuf;
+	do {
+		n = websocket_handle_packet(client, ptr, length, callback);
+		if (n < 0)
+			return -1; /* killed -- STOP processing */
+		if (n == 0)
+		{
+			/* Short read. Stop processing for now, but save data for next time */
+			safe_free(WSU(client)->lefttoparse);
+			WSU(client)->lefttoparse = safe_alloc(length);
+			WSU(client)->lefttoparselen = length;
+			memcpy(WSU(client)->lefttoparse, ptr, length);
+			return 0;
+		}
+		length -= n;
+		ptr += n;
+		if (length < 0)
+			abort(); /* less than 0 is impossible */
+	} while(length > 0);
+
+	return 0;
+}
+
+/** WebSocket packet handler.
+ * For more information on the format, check out page 28 of RFC6455.
+ * @returns The number of bytes processed (the size of the frame)
+ *          OR 0 to indicate a possible short read (want more data)
+ *          OR -1 in case of an error.
+ */
+int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
+{
+	char opcode; /**< Opcode */
+	char masked; /**< Masked */
+	int len; /**< Length of the packet */
+	char maskkey[4]; /**< Key used for masking */
+	const char *p;
+	int total_packet_size;
+	char *payload = NULL;
+	static char payloadbuf[READBUF_SIZE];
+	int maskkeylen = 4;
+
+	if (length < 4)
+	{
+		/* WebSocket packet too short */
+		return 0;
+	}
+
+	/* fin    = readbuf[0] & 0x80; -- unused */
+	opcode = readbuf[0] & 0x7F;
+	masked = readbuf[1] & 0x80;
+	len    = readbuf[1] & 0x7F;
+	p = &readbuf[2]; /* point to next element */
+
+	/* actually 'fin' is unused.. we don't care. */
+
+	/* Masked. According to RFC6455 page 29:
+	 * "All frames sent from client to server have this bit set to 1."
+	 * But in practice i see that for PONG this may not always be
+	 * true, so let's make an exception for that...
+	 */
+	if (!masked && (opcode != WSOP_PONG))
+	{
+		dead_socket(client, "WebSocket packet not masked");
+		return -1; /* Having the masked bit set is required (RFC6455 p29) */
+	}
+
+	if (!masked)
+		maskkeylen = 0;
+
+	if (len == 127)
+	{
+		dead_socket(client, "WebSocket packet with insane size");
+		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
+	}
+
+	total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */
+
+	/* Early (minimal) length check */
+	if (length < total_packet_size)
+	{
+		/* WebSocket frame too short */
+		return 0;
+	}
+
+	/* Len=126 is special. It indicates the data length is actually "126 or more" */
+	if (len == 126)
+	{
+		/* Extended payload length (16 bit). For packets of >=126 bytes */
+		len = (readbuf[2] << 8) + readbuf[3];
+		if (len < 126)
+		{
+			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
+			return -1; /* This is a violation (not a short read), see page 29 */
+		}
+		p += 2; /* advance pointer 2 bytes */
+
+		/* Need to check the length again, now it has changed: */
+		if (length < len + 4 + maskkeylen)
+		{
+			/* WebSocket frame too short */
+			return 0;
+		}
+		/* And update the packet size */
+		total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
+	}
+
+	if (masked)
+	{
+		memcpy(maskkey, p, maskkeylen);
+		p+= maskkeylen;
+	}
+
+	if (len > 0)
+	{
+		memcpy(payloadbuf, p, len);
+		payload = payloadbuf;
+	} /* else payload is NULL */
+
+	if (masked && (len > 0))
+	{
+		/* Unmask this thing (page 33, section 5.3) */
+		int n;
+		char v;
+		char *p;
+		for (p = payload, n = 0; n < len; n++)
+		{
+			v = *p;
+			*p++ = v ^ maskkey[n % 4];
+		}
+	}
+
+	switch(opcode)
+	{
+		case WSOP_CONTINUATION:
+		case WSOP_TEXT:
+		case WSOP_BINARY:
+			if (len > 0)
+			{
+				if (!callback(client, payload, len))
+					return -1; /* fatal error occured (such as flood kill) */
+			}
+			return total_packet_size;
+
+		case WSOP_CLOSE:
+			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
+			return -1;
+
+		case WSOP_PING:
+			if (websocket_handle_packet_ping(client, payload, len) < 0)
+				return -1;
+			return total_packet_size;
+
+		case WSOP_PONG:
+			if (websocket_handle_packet_pong(client, payload, len) < 0)
+				return -1;
+			return total_packet_size;
+
+		default:
+			dead_socket(client, "WebSocket: Unknown opcode");
+			return -1;
+	}
+
+	return -1; /* NOTREACHED */
+}
+
+int websocket_handle_packet_ping(Client *client, const char *buf, int len)
+{
+	if (len > 500)
+	{
+		dead_socket(client, "WebSocket: oversized PING request");
+		return -1;
+	}
+	websocket_send_pong(client, buf, len);
+	add_fake_lag(client, 1000); /* lag penalty of 1 second */
+	return 0;
+}
+
+int websocket_handle_packet_pong(Client *client, const char *buf, int len)
+{
+	/* We only care about pongs for RPC websocket connections.
+	 * Also, we don't verify the content, actually,
+	 * so don't use this for security like a pingpong cookie.
+	 */
+	if (IsRPC(client))
+	{
+		client->local->last_msg_received = TStime();
+		ClearPingSent(client);
+	}
+	return 0;
+}
+
+/** Create a simple websocket packet that is ready to be sent.
+ * This is the simple version that is used ONLY for WSOP_PONG,
+ * as it does not take \r\n into account.
+ */
+int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
+{
+	static char sendbuf[8192];
+
+	sendbuf[0] = opcode | 0x80; /* opcode & final */
+
+	if (*len > sizeof(sendbuf) - 8)
+		return -1; /* should never happen (safety) */
+
+	if (*len < 126)
+	{
+		/* Short payload */
+		sendbuf[1] = (char)*len;
+		memcpy(&sendbuf[2], *buf, *len);
+		*buf = sendbuf;
+		*len += 2;
+	} else {
+		/* Long payload */
+		sendbuf[1] = 126;
+		sendbuf[2] = (char)((*len >> 8) & 0xFF);
+		sendbuf[3] = (char)(*len & 0xFF);
+		memcpy(&sendbuf[4], *buf, *len);
+		*buf = sendbuf;
+		*len += 4;
+	}
+	return 0;
+}
+
+/** Create a websocket packet that is ready to be send.
+ * This version takes into account stripping off \r and \n,
+ * and possibly multi line due to labeled-response.
+ * It is used for WSOP_TEXT and WSOP_BINARY.
+ * The end result is one or more websocket frames,
+ * all in a single packet *buf with size *len.
+ *
+ * This is the version that uses the specified buffer,
+ * it is used from the JSON-RPC code,
+ * and indirectly from websocket_create_packet().
+ */
+int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
+{
+	char *s = *buf; /* points to start of current line */
+	char *s2; /* used for searching of end of current line */
+	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
+	int bytes_to_copy;
+	char newline;
+	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
+	int bytes_in_sendbuf = 0;
+	int bytes_single_frame;
+
+	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
+	if (*len == 0)
+		return -1;
+
+	do {
+		/* Find next \r or \n */
+		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
+		{
+			if ((*s2 == '\n') || (*s2 == '\r'))
+				break;
+		}
+
+		/* Now 's' points to start of line and 's2' points to beyond end of the line
+		 * (either at \r, \n or beyond the buffer).
+		 */
+		bytes_to_copy = s2 - s;
+
+		if (bytes_to_copy < 126)
+			bytes_single_frame = 2 + bytes_to_copy;
+		else if (bytes_to_copy < 65536)
+			bytes_single_frame = 4 + bytes_to_copy;
+		else
+			bytes_single_frame = 10 + bytes_to_copy;
+
+		if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
+		{
+			/* Overflow. This should never happen. */
+			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
+			           "[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
+			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
+			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
+			           log_data_integer("bytes_single_frame", bytes_single_frame),
+			           log_data_integer("sendbuf_size", sendbufsize));
+			return -1;
+		}
+
+		/* Create the new frame */
+		o[0] = opcode | 0x80; /* opcode & final */
+
+		if (bytes_to_copy < 126)
+		{
+			/* Short payload */
+			o[1] = (char)bytes_to_copy;
+			memcpy(&o[2], s, bytes_to_copy);
+		} else
+		if (bytes_to_copy < 65536)
+		{
+			/* Long payload */
+			o[1] = 126;
+			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
+			o[3] = (char)(bytes_to_copy & 0xFF);
+			memcpy(&o[4], s, bytes_to_copy);
+		} else {
+			/* Longest payload */
+			// XXX: yeah we don't support sending more than 4GB.
+			o[1] = 127;
+			o[2] = 0;
+			o[3] = 0;
+			o[4] = 0;
+			o[5] = 0;
+			o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
+			o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
+			o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
+			o[9] = (char)(bytes_to_copy & 0xFF);
+			memcpy(&o[10], s, bytes_to_copy);
+		}
+
+		/* Advance destination pointer and counter */
+		o += bytes_single_frame;
+		bytes_in_sendbuf += bytes_single_frame;
+
+		/* Advance source pointer and skip all trailing \n and \r */
+		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
+	} while(s <= lastbyte);
+
+	*buf = sendbuf;
+	*len = bytes_in_sendbuf;
+	return 0;
+}
+
+/** Create a websocket packet that is ready to be send.
+ * This version takes into account stripping off \r and \n,
+ * and possibly multi line due to labeled-response.
+ * It is used for WSOP_TEXT and WSOP_BINARY.
+ * The end result is one or more websocket frames,
+ * all in a single packet *buf with size *len.
+ *
+ * This is the version that uses a static sendbuf buffer,
+ * it is used from IRC websockets.
+ */
+int _websocket_create_packet(int opcode, char **buf, int *len)
+{
+	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
+	return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
+}
+
+/** Create and send a WSOP_PONG frame */
+int websocket_send_pong(Client *client, const char *buf, int len)
+{
+	const char *b = buf;
+	int l = len;
+
+	if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
+		return -1;
+
+	if (DBufLength(&client->local->sendQ) > get_sendq(client))
+	{
+		dead_socket(client, "Max SendQ exceeded");
+		return -1;
+	}
+
+	dbuf_put(&client->local->sendQ, b, l);
+	send_queued(client);
+	return 0;
+}
+
+/** UnrealIRCd internals: free WebSocketUser object. */
+void websocket_mdata_free(ModData *m)
+{
+	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
+	if (wsu)
+	{
+		safe_free(wsu->handshake_key);
+		safe_free(wsu->lefttoparse);
+		safe_free(wsu->sec_websocket_protocol);
+		safe_free(wsu->forwarded);
+		safe_free(m->ptr);
+	}
+}
+
+/** This only serializes wsu->type atm */
+const char *websocket_mdata_serialize(ModData *m)
+{
+	static char buf[32];
+	WebSocketUser *wsu = m->ptr;
+
+	if (!wsu)
+		return NULL; /* not set */
+
+	snprintf(buf, sizeof(buf), "%d", wsu->type);
+	return buf;
+}
+
+/** This only sets wsu->type atm */
+void websocket_mdata_unserialize(const char *str, ModData *m)
+{
+	WebSocketUser *wsu;
+	if (m->ptr)
+		websocket_mdata_free(m);
+	if (BadPtr(str))
+		return; /* empty/freed */
+	m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
+	wsu->type = atoi(str);
+}
diff --git a/src/modules/whois.c b/src/modules/whois.c
@@ -614,13 +614,15 @@ CMD_FUNC(cmd_whois)
 			                    target->name, "is shunned");
 		}
 
-		if (target->user->swhois && !hideoper && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE))
+		if (target->user->swhois && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE))
 		{
 			SWhois *s;
 			int swhois_lines = 0;
 
 			for (s = target->user->swhois; s; s = s->next)
 			{
+				if (hideoper && !IsOper(client) && s->setby && !strcmp(s->setby, "oper"))
+					continue; /* hide oper-based swhois entries */
 				add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL,
 				                    target->name, s->line);
 				swhois_lines++;
diff --git a/src/modules/whowas.c b/src/modules/whowas.c
@@ -73,11 +73,15 @@ CMD_FUNC(cmd_whowas)
 		sendnumeric(client, ERR_NONICKNAMEGIVEN);
 		return;
 	}
+
 	if (parc > 2)
 		max = atoi(parv[2]);
+
 	if (parc > 3)
+	{
 		if (hunt_server(client, recv_mtags, "WHOWAS", 3, parc, parv))
-			return;
+			return; /* Not for us */
+	}
 
 	if (!MyConnect(client) && (max > 20))
 		max = 20;
@@ -85,7 +89,8 @@ CMD_FUNC(cmd_whowas)
 	strlcpy(request, parv[1], sizeof(request));
 	p = strchr(request, ',');
 	if (p)
-		*p = '\0';
+		*p = '\0'; /* cut off at first */
+
 	nick = request;
 	temp = WHOWASHASH[hash_whowas_name(nick)];
 	found = 0;
@@ -95,13 +100,26 @@ CMD_FUNC(cmd_whowas)
 		{
 			sendnumeric(client, RPL_WHOWASUSER, temp->name,
 			    temp->username,
-			    (IsOper(client) ? temp->hostname :
-			    (*temp->virthost !=
-			    '\0') ? temp->virthost : temp->hostname),
+			    BadPtr(temp->virthost) ? temp->hostname : temp->virthost,
 			    temp->realname);
-                	if (!((find_uline(temp->servername)) && !IsOper(client) && HIDE_ULINES))
+			if (!BadPtr(temp->ip) && ValidatePermissionsForPath("client:see:ip",client,NULL,NULL,NULL))
+			{
+				sendnumericfmt(client, RPL_WHOISHOST, "%s :was connecting from %s@%s %s",
+					temp->name,
+					temp->username, temp->hostname,
+					temp->ip ? temp->ip : "");
+			}
+			if (IsOper(client) && !BadPtr(temp->account))
+			{
+				sendnumericfmt(client, RPL_WHOISLOGGEDIN, "%s %s :was logged in as",
+					temp->name,
+					temp->account);
+			}
+			if (!((find_uline(temp->servername)) && !IsOper(client) && HIDE_ULINES))
+			{
 				sendnumeric(client, RPL_WHOISSERVER, temp->name, temp->servername,
-				    myctime(temp->logoff));
+				            myctime(temp->logoff));
+			}
 			cur++;
 			found++;
 		}
diff --git a/src/modules/whowasdb.c b/src/modules/whowasdb.c
@@ -0,0 +1,641 @@
+/*
+ * Stores WHOWAS history in a .db file
+ * (C) Copyright 2023 Syzop
+ * License: GPLv2 or later
+ */
+
+#include "unrealircd.h"
+
+ModuleHeader MOD_HEADER = {
+	"whowasdb",
+	"1.0",
+	"Stores and retrieves WHOWAS history",
+	"UnrealIRCd Team",
+	"unrealircd-6",
+};
+
+/* Our header */
+#define WHOWASDB_HEADER		0x57484F57
+/* Database version */
+#define WHOWASDB_VERSION 100
+/* Save whowas of users to file every <this> seconds */
+#define WHOWASDB_SAVE_EVERY 300
+/* The very first save after boot, apply this delta, this
+ * so we don't coincide with other (potentially) expensive
+ * I/O events like saving tkldb.
+ */
+#define WHOWASDB_SAVE_EVERY_DELTA -60
+
+#define MAGIC_WHOWASDB_START	0x11111111
+#define MAGIC_WHOWASDB_END		0x22222222
+
+// #undef BENCHMARK
+
+/* Yeah, W_SAFE_PROPERTY raises "the address .. will always evaluate to true" warnings,
+ * disabling it in the entire file for now...
+ */
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Waddress"
+#endif
+
+#define WARN_WRITE_ERROR(fname) \
+	do { \
+		unreal_log(ULOG_ERROR, "whowasdb", "WHOWASDB_FILE_WRITE_ERROR", NULL, \
+			   "[whowasdb] Error writing to temporary database file $filename: $system_error", \
+			   log_data_string("filename", fname), \
+			   log_data_string("system_error", unrealdb_get_error_string())); \
+	} while(0)
+
+#define W_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define W_SAFE_PROPERTY(db, x, y) \
+	do { \
+		if (x && y && (!unrealdb_write_str(db, x) || !unrealdb_write_str(db, y))) \
+		{ \
+			WARN_WRITE_ERROR(tmpfname); \
+			unrealdb_close(db); \
+			return 0; \
+		} \
+	} while(0)
+
+#define IsMDErr(x, y, z) \
+	do { \
+		if (!(x)) { \
+			config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER.name, ModuleGetErrorStr((z)->handle)); \
+			return MOD_FAILED; \
+		} \
+	} while(0)
+
+/* Structs */
+struct cfgstruct {
+	char *database;
+	char *db_secret;
+};
+
+/* Forward declarations */
+void whowasdb_moddata_free(ModData *md);
+void setcfg(struct cfgstruct *cfg);
+void freecfg(struct cfgstruct *cfg);
+int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
+int whowasdb_config_posttest(int *errs);
+int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
+EVENT(write_whowasdb_evt);
+int write_whowasdb(void);
+int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e);
+int read_whowasdb(void);
+
+/* External variables */
+extern WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
+extern WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
+extern MODVAR int whowas_next;
+
+/* Global variables */
+static uint32_t whowasdb_version = WHOWASDB_VERSION;
+static struct cfgstruct cfg;
+static struct cfgstruct test;
+
+static long whowasdb_next_event = 0;
+
+MOD_TEST()
+{
+	memset(&cfg, 0, sizeof(cfg));
+	memset(&test, 0, sizeof(test));
+	setcfg(&test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whowasdb_config_test);
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, whowasdb_config_posttest);
+	return MOD_SUCCESS;
+}
+
+MOD_INIT()
+{
+	MARK_AS_OFFICIAL_MODULE(modinfo);
+
+	LoadPersistentLong(modinfo, whowasdb_next_event);
+
+	setcfg(&cfg);
+
+	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whowasdb_config_run);
+	return MOD_SUCCESS;
+}
+
+MOD_LOAD()
+{
+	if (!whowasdb_next_event)
+	{
+		/* If this is the first time that our module is loaded, then read the database. */
+		if (!read_whowasdb())
+		{
+			char fname[512];
+			snprintf(fname, sizeof(fname), "%s.corrupt", cfg.database);
+			if (rename(cfg.database, fname) == 0)
+				config_warn("[whowasdb] Existing database renamed to %s and starting a new one...", fname);
+			else
+				config_warn("[whowasdb] Failed to rename database from %s to %s: %s", cfg.database, fname, strerror(errno));
+		}
+		whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY + WHOWASDB_SAVE_EVERY_DELTA;
+	}
+	EventAdd(modinfo->handle, "whowasdb_write_whowasdb", write_whowasdb_evt, NULL, 1000, 0);
+	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
+	{
+		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
+		return MOD_FAILED;
+	}
+	return MOD_SUCCESS;
+}
+
+MOD_UNLOAD()
+{
+	if (loop.terminating)
+		write_whowasdb();
+	freecfg(&test);
+	freecfg(&cfg);
+	SavePersistentLong(modinfo, whowasdb_next_event);
+	return MOD_SUCCESS;
+}
+
+void whowasdb_moddata_free(ModData *md)
+{
+	if (md->i)
+		md->i = 0;
+}
+
+void setcfg(struct cfgstruct *cfg)
+{
+	// Default: data/whowas.db
+	safe_strdup(cfg->database, "whowas.db");
+	convert_to_absolute_path(&cfg->database, PERMDATADIR);
+}
+
+void freecfg(struct cfgstruct *cfg)
+{
+	safe_free(cfg->database);
+	safe_free(cfg->db_secret);
+}
+
+int whowasdb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
+{
+	int errors = 0;
+	ConfigEntry *cep;
+
+	// We are only interested in set::whowasdb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "whowasdb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!cep->value)
+		{
+			config_error("%s:%i: blank set::whowasdb::%s without value", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		} else
+		if (!strcmp(cep->name, "database"))
+		{
+			convert_to_absolute_path(&cep->value, PERMDATADIR);
+			safe_strdup(test.database, cep->value);
+		} else
+		if (!strcmp(cep->name, "db-secret"))
+		{
+			const char *err;
+			if ((err = unrealdb_test_secret(cep->value)))
+			{
+				config_error("%s:%i: set::whowasdb::db-secret: %s", cep->file->filename, cep->line_number, err);
+				errors++;
+				continue;
+			}
+			safe_strdup(test.db_secret, cep->value);
+		} else
+		{
+			config_error("%s:%i: unknown directive set::whowasdb::%s", cep->file->filename, cep->line_number, cep->name);
+			errors++;
+		}
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int whowasdb_config_posttest(int *errs)
+{
+	int errors = 0;
+	char *errstr;
+
+	if (test.database && ((errstr = unrealdb_test_db(test.database, test.db_secret))))
+	{
+		config_error("[whowasdb] %s", errstr);
+		errors++;
+	}
+
+	*errs = errors;
+	return errors ? -1 : 1;
+}
+
+int whowasdb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
+{
+	ConfigEntry *cep;
+
+	// We are only interested in set::whowasdb::database
+	if (type != CONFIG_SET)
+		return 0;
+
+	if (!ce || strcmp(ce->name, "whowasdb"))
+		return 0;
+
+	for (cep = ce->items; cep; cep = cep->next)
+	{
+		if (!strcmp(cep->name, "database"))
+			safe_strdup(cfg.database, cep->value);
+		else if (!strcmp(cep->name, "db-secret"))
+			safe_strdup(cfg.db_secret, cep->value);
+	}
+	return 1;
+}
+
+EVENT(write_whowasdb_evt)
+{
+	if (whowasdb_next_event > TStime())
+		return;
+	whowasdb_next_event = TStime() + WHOWASDB_SAVE_EVERY;
+	write_whowasdb();
+}
+
+int count_whowas_and_user_entries(void)
+{
+	int i;
+	int cnt = 0;
+	Client *client;
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (e->name)
+			cnt++;
+	}
+
+	list_for_each_entry(client, &client_list, client_node)
+		if (IsUser(client))
+			cnt++;
+
+	return cnt;
+}
+
+int write_whowasdb(void)
+{
+	char tmpfname[512];
+	UnrealDB *db;
+	WhoWas *e;
+	Client *client;
+	int cnt, i;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	// Write to a tempfile first, then rename it if everything succeeded
+	snprintf(tmpfname, sizeof(tmpfname), "%s.%x.tmp", cfg.database, getrandom32());
+	db = unrealdb_open(tmpfname, UNREALDB_MODE_WRITE, cfg.db_secret);
+	if (!db)
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+	W_SAFE(unrealdb_write_int32(db, WHOWASDB_HEADER));
+	W_SAFE(unrealdb_write_int32(db, whowasdb_version));
+
+	cnt = count_whowas_and_user_entries();
+	W_SAFE(unrealdb_write_int64(db, cnt));
+
+	for (i=0; i < NICKNAMEHISTORYLENGTH; i++)
+	{
+		WhoWas *e = &WHOWAS[i];
+		if (e->name)
+		{
+			if (!write_whowas_entry(db, tmpfname, e))
+				return 0;
+		}
+	}
+
+	/* Add all the currently connected users to WHOWAS history (as if they left just now) */
+	list_for_each_entry(client, &client_list, client_node)
+	{
+		if (IsUser(client))
+		{
+			WhoWas *e = safe_alloc(sizeof(WhoWas));
+			int ret;
+
+			create_whowas_entry(client, e, WHOWAS_EVENT_SERVER_TERMINATING);
+			ret = write_whowas_entry(db, tmpfname, e);
+			free_whowas_fields(e);
+			safe_free(e);
+
+			if (ret == 0)
+				return 0;
+		}
+	}
+
+
+	// Everything seems to have gone well, attempt to close and rename the tempfile
+	if (!unrealdb_close(db))
+	{
+		WARN_WRITE_ERROR(tmpfname);
+		return 0;
+	}
+
+#ifdef _WIN32
+	/* The rename operation cannot be atomic on Windows as it will cause a "file exists" error */
+	unlink(cfg.database);
+#endif
+	if (rename(tmpfname, cfg.database) < 0)
+	{
+		config_error("[whowasdb] Error renaming '%s' to '%s': %s (DATABASE NOT SAVED)", tmpfname, cfg.database, strerror(errno));
+		return 0;
+	}
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	config_status("[whowasdb] Benchmark: SAVE DB: %ld microseconds",
+		((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec));
+#endif
+	return 1;
+}
+
+int write_whowas_entry(UnrealDB *db, const char *tmpfname, WhoWas *e)
+{
+	char connected_since[64];
+	char logontime[64];
+	char logofftime[64];
+	char event[16];
+
+	snprintf(connected_since, sizeof(connected_since), "%lld", (long long)e->connected_since);
+	snprintf(logontime, sizeof(logontime), "%lld", (long long)e->logon);
+	snprintf(logofftime, sizeof(logofftime), "%lld", (long long)e->logoff);
+	snprintf(event, sizeof(event), "%d", e->event);
+
+	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_START));
+	W_SAFE_PROPERTY(db, "nick", e->name);
+	W_SAFE_PROPERTY(db, "event", event);
+	W_SAFE_PROPERTY(db, "connected_since", connected_since);
+	W_SAFE_PROPERTY(db, "logontime", logontime);
+	W_SAFE_PROPERTY(db, "logofftime", logofftime);
+	W_SAFE_PROPERTY(db, "username", e->username);
+	W_SAFE_PROPERTY(db, "hostname", e->hostname);
+	W_SAFE_PROPERTY(db, "ip", e->ip);
+	W_SAFE_PROPERTY(db, "realname", e->realname);
+	W_SAFE_PROPERTY(db, "server", e->servername);
+	W_SAFE_PROPERTY(db, "virthost", e->virthost);
+	W_SAFE_PROPERTY(db, "account", e->account);
+	W_SAFE_PROPERTY(db, "end", "");
+	W_SAFE(unrealdb_write_int32(db, MAGIC_WHOWASDB_END));
+	return 1;
+}
+
+#define FreeWhowasEntry() \
+ 	do { \
+		/* Some of these might be NULL */ \
+		safe_free(key); \
+		safe_free(value); \
+		safe_free(nick); \
+		safe_free(username); \
+		safe_free(hostname); \
+		safe_free(ip); \
+		safe_free(realname); \
+		connected_since = logontime = logofftime = 0; \
+		event = 0; \
+		safe_free(server); \
+		safe_free(virthost); \
+		safe_free(account); \
+	} while(0)
+
+#define R_SAFE(x) \
+	do { \
+		if (!(x)) { \
+			config_warn("[whowasdb] Read error from database file '%s' (possible corruption): %s", cfg.database, unrealdb_get_error_string()); \
+			unrealdb_close(db); \
+			FreeWhowasEntry(); \
+			return 0; \
+		} \
+	} while(0)
+
+int read_whowasdb(void)
+{
+	UnrealDB *db;
+	uint32_t version;
+	int added = 0;
+	int i;
+	uint64_t count = 0;
+	uint32_t magic;
+	char *key = NULL;
+	char *value = NULL;
+	char *nick = NULL;
+	char *username = NULL;
+	char *hostname = NULL;
+	char *ip = NULL;
+	char *realname = NULL;
+	long long connected_since = 0;
+	long long logontime = 0;
+	long long logofftime = 0;
+	int event = 0;
+	char *server = NULL;
+	char *virthost = NULL;
+	char *account = NULL;
+#ifdef BENCHMARK
+	struct timeval tv_alpha, tv_beta;
+
+	gettimeofday(&tv_alpha, NULL);
+#endif
+
+	db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, cfg.db_secret);
+	if (!db)
+	{
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_FILENOTFOUND)
+		{
+			/* Database does not exist. Could be first boot */
+			config_warn("[whowasdb] No database present at '%s', will start a new one", cfg.database);
+			return 1;
+		} else
+		if (unrealdb_get_error_code() == UNREALDB_ERROR_NOTCRYPTED)
+		{
+			/* Re-open as unencrypted */
+			db = unrealdb_open(cfg.database, UNREALDB_MODE_READ, NULL);
+			if (!db)
+			{
+				/* This should actually never happen, unless some weird I/O error */
+				config_warn("[whowasdb] Unable to open the database file '%s': %s", cfg.database, unrealdb_get_error_string());
+				return 0;
+			}
+		} else
+		{
+			config_warn("[whowasdb] Unable to open the database file '%s' for reading: %s", cfg.database, unrealdb_get_error_string());
+			return 0;
+		}
+	}
+
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version != WHOWASDB_HEADER)
+	{
+		config_warn("[whowasdb] Database '%s' is not a whowas db (incorrect header)", cfg.database);
+		unrealdb_close(db);
+		return 0;
+	}
+	R_SAFE(unrealdb_read_int32(db, &version));
+	if (version > whowasdb_version)
+	{
+		config_warn("[whowasdb] Database '%s' has a wrong version: expected it to be <= %u but got %u instead", cfg.database, whowasdb_version, version);
+		unrealdb_close(db);
+		return 0;
+	}
+
+	R_SAFE(unrealdb_read_int64(db, &count));
+
+	for (i=1; i <= count; i++)
+	{
+		// Variables
+		key = value = NULL;
+		nick = username = hostname = ip = realname = virthost = account = server = NULL;
+		connected_since = logontime = logofftime = 0;
+		event = 0;
+
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != MAGIC_WHOWASDB_START)
+		{
+			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic start is 0x%x. Further reading aborted.", cfg.database, magic);
+			break;
+		}
+		while(1)
+		{
+			R_SAFE(unrealdb_read_str(db, &key));
+			R_SAFE(unrealdb_read_str(db, &value));
+			if (!strcmp(key, "nick"))
+			{
+				safe_strdup(nick, value);
+			} else
+			if (!strcmp(key, "username"))
+			{
+				safe_strdup(username, value);
+			} else
+			if (!strcmp(key, "hostname"))
+			{
+				safe_strdup(hostname, value);
+			} else
+			if (!strcmp(key, "ip"))
+			{
+				safe_strdup(ip, value);
+			} else
+			if (!strcmp(key, "realname"))
+			{
+				safe_strdup(realname, value);
+			} else
+			if (!strcmp(key, "connected_since"))
+			{
+				connected_since = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "logontime"))
+			{
+				logontime = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "logofftime"))
+			{
+				logofftime = atoll(value);
+				safe_free(value);
+			} else
+			if (!strcmp(key, "event"))
+			{
+				event = atoi(value);
+				if ((event < WHOWAS_LOWEST_EVENT) || (event > WHOWAS_HIGHEST_EVENT))
+					event = WHOWAS_EVENT_QUIT; /* safety */
+				safe_free(value);
+			} else
+			if (!strcmp(key, "server"))
+			{
+				safe_strdup(server, value);
+			} else
+			if (!strcmp(key, "virthost"))
+			{
+				safe_strdup(virthost, value);
+			} else
+			if (!strcmp(key, "account"))
+			{
+				safe_strdup(account, value);
+			} else
+			if (!strcmp(key, "end"))
+			{
+				safe_free(key);
+				safe_free(value);
+				break; /* DONE! */
+			}
+			safe_free(key);
+			safe_free(value);
+		}
+		R_SAFE(unrealdb_read_int32(db, &magic));
+		if (magic != MAGIC_WHOWASDB_END)
+		{
+			config_error("[whowasdb] Corrupt database (%s) - whowasdb magic end is 0x%x. Further reading aborted.", cfg.database, magic);
+			FreeWhowasEntry();
+			break;
+		}
+
+		if (nick && username && hostname && realname)
+		{
+			WhoWas *e = &WHOWAS[whowas_next];
+			if (e->hashv != -1)
+				free_whowas_fields(e);
+			/* Set values */
+			//unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_READ_RECORD", NULL,
+			//           "[whowasdb] Adding '$nick'...",
+			//           log_data_string("nick", nick));
+			e->hashv = hash_whowas_name(nick);
+			e->event = event;
+			e->connected_since = connected_since;
+			e->logon = logontime;
+			e->logoff = logofftime;
+			safe_strdup(e->name, nick);
+			safe_strdup(e->username, username);
+			safe_strdup(e->hostname, hostname);
+			safe_strdup(e->ip, ip);
+			if (virthost)
+				safe_strdup(e->virthost, virthost);
+			else
+				safe_strdup(e->virthost, "");
+			e->servername = find_or_add(server); /* scache */
+			safe_strdup(e->realname, realname);
+			safe_strdup(e->account, account);
+			e->online = NULL;
+			/* Server is special - scache shit */
+			/* Add to hash table */
+			add_whowas_to_list(&WHOWASHASH[e->hashv], e);
+			/* And advance pointer (well, integer) */
+			whowas_next++;
+			if (whowas_next == NICKNAMEHISTORYLENGTH)
+				whowas_next = 0;
+		}
+
+		FreeWhowasEntry();
+		added++;
+	}
+
+	unrealdb_close(db);
+
+	if (added)
+		config_status("[whowasdb] Added %d WHOWAS items", added);
+#ifdef BENCHMARK
+	gettimeofday(&tv_beta, NULL);
+	unreal_log(ULOG_DEBUG, "whowasdb", "WHOWASDB_BENCHMARK", NULL,
+	           "[whowasdb] Benchmark: LOAD DB: $time_msec microseconds",
+	           log_data_integer("time_msec", ((tv_beta.tv_sec - tv_alpha.tv_sec) * 1000000) + (tv_beta.tv_usec - tv_alpha.tv_usec)));
+#endif
+	return 1;
+}
+#undef FreeWhowasEntry
+#undef R_SAFE
diff --git a/src/operclass.c b/src/operclass.c
@@ -291,7 +291,7 @@ OperPermission ValidatePermissionsForPath(const char *path, Client *client, Clie
 		return OPER_DENY;
 
 	/* Trust Servers, U-Lines and remote opers */
-	if (IsServer(client) || IsULine(client) || (IsOper(client) && !MyUser(client)))
+	if (IsServer(client) || IsMe(client) || IsULine(client) || (IsOper(client) && !MyUser(client)))
 		return OPER_ALLOW;
 
 	if (!IsOper(client))
diff --git a/src/parse.c b/src/parse.c
@@ -105,7 +105,7 @@ void parse_client_queued(Client *client)
 
 	if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
 	    !IsNoHandshakeDelay(client) &&
-	    !IsControl(client) &&
+	    !IsUnixSocket(client) &&
 	    (TStime() - client->local->creationtime < iConf.handshake_delay))
 	{
 		return; /* we delay processing of data until set::handshake-delay is reached */
diff --git a/src/proc_io_server.c b/src/proc_io_server.c
@@ -26,11 +26,14 @@
 #include "unrealircd.h"
 #include <ares.h>
 
+/* Forward declarations */
 CMD_FUNC(procio_status);
 CMD_FUNC(procio_modules);
 CMD_FUNC(procio_rehash);
 CMD_FUNC(procio_exit);
 CMD_FUNC(procio_help);
+void start_of_control_client_handshake(Client *client);
+int procio_accept(Client *client);
 
 /** Create the unrealircd.ctl socket (server-side) */
 void add_proc_io_server(void)
@@ -45,7 +48,8 @@ void add_proc_io_server(void)
 	listener = safe_alloc(sizeof(ConfigItem_listen));
 	safe_strdup(listener->file, CONTROLFILE);
 	listener->socket_type = SOCKET_TYPE_UNIX;
-	listener->options = LISTENER_CONTROL;
+	listener->options = LISTENER_CONTROL|LISTENER_NO_CHECK_CONNECT_FLOOD|LISTENER_NO_CHECK_ZLINED;
+	listener->start_handshake = start_of_control_client_handshake;
 	listener->fd = -1;
 	AddListItem(listener, conf_listen);
 	if (add_listener(listener) == -1)
@@ -55,6 +59,19 @@ void add_proc_io_server(void)
 	CommandAdd(NULL, "REHASH", procio_rehash, MAXPARA, CMD_CONTROL);
 	CommandAdd(NULL, "EXIT", procio_exit, MAXPARA, CMD_CONTROL);
 	CommandAdd(NULL, "HELP", procio_help, MAXPARA, CMD_CONTROL);
+	HookAdd(NULL, HOOKTYPE_ACCEPT, -1000000, procio_accept);
+}
+
+int procio_accept(Client *client)
+{
+	if (client->local->listener->options & LISTENER_CONTROL)
+	{
+		irccounts.unknown--;
+		client->status = CLIENT_STATUS_CONTROL;
+		list_del(&client->lclient_node);
+		list_add(&client->lclient_node, &control_list);
+	}
+	return 0;
 }
 
 /** Start of "control channel" client handshake - this is minimal
@@ -77,6 +94,9 @@ CMD_FUNC(procio_status)
 #endif
 	sendto_one(client, NULL, "REPLY libcares_version %s", ares_version(NULL));
 	sendto_one(client, NULL, "REPLY libpcre2_version %s", pcre2_version());
+#if JANSSON_VERSION_HEX >= 0x020D00
+	sendto_one(client, NULL, "REPLY libjansson %s\n", jansson_version_str());
+#endif
 	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);
@@ -132,6 +152,7 @@ CMD_FUNC(procio_rehash)
 		ClearMonitorRehash(client);
 	} else {
 		SetMonitorRehash(client);
+		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [./unrealircd rehash]");
 		request_rehash(client);
 		/* completion will go via procio_post_rehash() */
 	}
diff --git a/src/securitygroup.c b/src/securitygroup.c
@@ -168,6 +168,10 @@ int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors)
 	{
 		CheckNullX(cep);
 	} else
+	if (!strcmp(cep->name, "websocket") || !strcmp(cep->name, "exclude-websocket"))
+	{
+		CheckNullX(cep);
+	} else
 	if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified"))
 	{
 		CheckNullX(cep);
@@ -365,6 +369,8 @@ int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
 
 	if (!strcmp(cep->name, "webirc"))
 		s->webirc = config_checkval(cep->value, CFG_YESNO);
+	if (!strcmp(cep->name, "websocket"))
+		s->websocket = config_checkval(cep->value, CFG_YESNO);
 	else if (!strcmp(cep->name, "identified"))
 		s->identified = config_checkval(cep->value, CFG_YESNO);
 	else if (!strcmp(cep->name, "tls"))
@@ -397,6 +403,8 @@ int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
 	}
 	else if (!strcmp(cep->name, "exclude-webirc"))
 		s->exclude_webirc = config_checkval(cep->value, CFG_YESNO);
+	else if (!strcmp(cep->name, "exclude-websocket"))
+		s->exclude_websocket = config_checkval(cep->value, CFG_YESNO);
 	else if (!strcmp(cep->name, "exclude-identified"))
 		s->exclude_identified = config_checkval(cep->value, CFG_YESNO);
 	else if (!strcmp(cep->name, "exclude-tls"))
@@ -439,7 +447,15 @@ int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
 		}
 	}
 
-	add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
+	/* And update the printable list */
+	if (cep->items)
+	{
+		ConfigEntry *cep2;
+		for (cep2 = cep->items; cep2; cep2 = cep2->next)
+			add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep2->name);
+	} else {
+		add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
+	}
 
 	return 1; /* Handled by us (guaranteed earlier) */
 }
@@ -588,6 +604,10 @@ void set_security_group_defaults(void)
 	s = add_security_group("webirc-users", 50);
 	s->webirc = 1;
 
+	/* Default group: websocket */
+	s = add_security_group("websocket-users", 51);
+	s->websocket = 1;
+
 	/* Default group: known-users */
 	s = add_security_group("known-users", 100);
 	s->identified = 1;
@@ -717,6 +737,8 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s)
 		goto user_not_allowed;
 	if (s->exclude_webirc && moddata_client_get(client, "webirc"))
 		goto user_not_allowed;
+	if (s->exclude_websocket && moddata_client_get(client, "websocket"))
+		goto user_not_allowed;
 	if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score))
 		goto user_not_allowed;
 	if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score))
@@ -745,6 +767,8 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s)
 		goto user_allowed;
 	if (s->webirc && moddata_client_get(client, "webirc"))
 		goto user_allowed;
+	if (s->websocket && moddata_client_get(client, "websocket"))
+		goto user_allowed;
 	if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score))
 		goto user_allowed;
 	if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score))
diff --git a/src/send.c b/src/send.c
@@ -36,7 +36,7 @@ static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, con
 
 /* These are two local (static) buffers used by the various send functions */
 static char sendbuf[2048];
-static char sendbuf2[4096];
+static char sendbuf2[MAXLINELENGTH];
 
 /** This is used to ensure no duplicate messages are sent
  * to the same server uplink/direction. In send functions
@@ -75,7 +75,7 @@ int dead_socket(Client *to, const char *notice)
 	if (to->local->error_str)
 		return -1; /* don't overwrite & don't send multiple times */
 	
-	if (!IsUser(to) && !IsUnknown(to) && !IsClosing(to))
+	if (!IsUser(to) && !IsUnknown(to) && !IsRPC(to) && !IsControl(to) && !IsClosing(to))
 	{
 		/* Looks like a duplicate error message to me?
 		 * If so, remove it here.
@@ -284,13 +284,8 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 		{
 			/* The message includes one or more message tags:
 			 * Spec-wise the rules allow about 8K for message tags
-			 * and then 512 bytes for the remainder of the message.
-			 * Since we do not allow user tags and only permit a
-			 * limited set of tags we can have our own limits for
-			 * the outgoing messages that we generate: a maximum of
-			 * 500 bytes for message tags and 512 for the remainder.
-			 * These limits will never be hit unless there is a bug
-			 * somewhere.
+			 * (MAXTAGSIZE) and then 512 bytes for
+			 * the remainder of the message (BUFSIZE).
 			 */
 			p = strchr(msg+1, ' ');
 			if (!p)
@@ -300,7 +295,7 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 				           log_data_string("buf", msg));
 				return;
 			}
-			if (p - msg > 4094)
+			if (p - msg > MAXTAGSIZE)
 			{
 				unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG", to,
 				           "Oversized message to $client (length $length): $buf",
@@ -324,7 +319,7 @@ void sendbufto_one(Client *to, char *msg, unsigned int quick)
 		len = quick;
 	}
 
-	if (len >= 10240)
+	if (len >= MAXLINELENGTH)
 	{
 		unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG2", to,
 			   "Oversized message to $client (length $length): $buf",
@@ -636,6 +631,72 @@ void sendto_local_common_channels(Client *user, Client *skip, long clicap, Messa
 	}
 }
 
+/** Send a QUIT message to all local users on all channels where
+ * the user 'user' is on.
+ * This is used for events such as a nick change and quit.
+ * @param user        The user and source of the message.
+ * @param skip        The client to skip (can be NULL)
+ * @param clicap      Client capability the recipient should have
+ *                    (this only works for local clients, we will
+ *                     always send the message to remote clients and
+ *                     assume the server there will handle it)
+ * @param mtags       The message tags to attach to this message.
+ * @param pattern     The pattern (eg: ":%s NICK %s").
+ * @param ...         The parameters for the pattern.
+ */
+void quit_sendto_local_common_channels(Client *user, MessageTag *mtags, const char *reason)
+{
+	va_list vl;
+	Membership *channels;
+	Member *users;
+	Client *acptr;
+	char sender[512];
+	MessageTag *m;
+	const char *real_quit_reason = NULL;
+
+	m = find_mtag(mtags, "unrealircd.org/real-quit-reason");
+	if (m && m->value)
+		real_quit_reason = m->value;
+
+	if (IsUser(user))
+	{
+		snprintf(sender, sizeof(sender), "%s!%s@%s",
+		         user->name, user->user->username, GetHost(user));
+	} else {
+		strlcpy(sender, user->name, sizeof(sender));
+	}
+
+	++current_serial;
+
+	if (user->user)
+	{
+		for (channels = user->user->channel; channels; channels = channels->next)
+		{
+			for (users = channels->channel->members; users; users = users->next)
+			{
+				acptr = users->client;
+
+				if (!MyConnect(acptr))
+					continue; /* only process local clients */
+
+				if (acptr->local->serial == current_serial)
+					continue; /* message already sent to this client */
+
+				if (!user_can_see_member(acptr, user, channels->channel))
+					continue; /* the sending user (QUITing) is 'invisible' -- skip */
+
+				acptr->local->serial = current_serial;
+				if (!reason)
+					sendto_one(acptr, mtags, ":%s QUIT", sender);
+				else if (!IsOper(acptr) || !real_quit_reason)
+					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, reason);
+				else
+					sendto_one(acptr, mtags, ":%s QUIT :%s", sender, real_quit_reason);
+			}
+		}
+	}
+}
+
 /*
 ** send a msg to all ppl on servers/hosts that match a specified mask
 ** (used for enhanced PRIVMSGs)
@@ -899,9 +960,9 @@ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char 
 	const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
 
 	if (to && from && MyUser(to) && from->user)
-		vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, from, pattern, vl);
+		vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl);
 	else
-		ircvsnprintf(sendbuf, sizeof(sendbuf), pattern, vl);
+		ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
 
 	if (BadPtr(mtags_str))
 	{
@@ -909,7 +970,7 @@ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char 
 		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);
 	}
 }
@@ -1014,12 +1075,13 @@ void sendnotice_multiline(Client *client, MultiLine *m)
  * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
  * followed by the pattern and format string you choose.
  * @param to		The recipient
+ * @param mtags     NULL, or NULL-terminated array of message tags
  * @param numeric	The numeric, one of RPL_* or ERR_*, see src/numeric.c
  * @param pattern	The format string / pattern to use.
  * @param ...		Format string parameters.
  * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
  */
-void sendnumericfmt(Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
+void sendtaggednumericfmt(Client *to, MessageTag *mtags, int numeric, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
 	char realpattern[512];
@@ -1027,7 +1089,7 @@ void sendnumericfmt(Client *to, int numeric, FORMAT_STRING(const char *pattern),
 	snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
 
 	va_start(vl, pattern);
-	vsendto_one(to, NULL, realpattern, vl);
+	vsendto_one(to, mtags, realpattern, vl);
 	va_end(vl);
 }
 
diff --git a/src/serv.c b/src/serv.c
@@ -227,6 +227,9 @@ CMD_FUNC(cmd_version)
 #endif
 			sendnotice(client, "c-ares %s", ares_version(NULL));
 			sendnotice(client, "%s", pcre2_version());
+#if JANSSON_VERSION_HEX >= 0x020D00
+			sendnotice(client, "jansson %s\n", jansson_version_str());
+#endif
 		}
 		if (MyUser(client))
 			send_version(client,0);
@@ -276,8 +279,8 @@ int remotecmdfilter(Client *client, int parc, const char *parv[])
 	/* no remote requests permitted from non-ircops */
 	if (MyUser(client) && !ValidatePermissionsForPath("server:remote",client,NULL,NULL,NULL) && !BadPtr(parv[1]))
 	{
-		parv[1] = NULL;
-		parc = 1;
+		sendnumeric(client, ERR_NOPRIVILEGES);
+		return 1; /* STOP */
 	}
 
 	/* same as above, but in case an old server forwards a request to us: we ignore it */
@@ -553,22 +556,6 @@ CMD_FUNC(cmd_rehash)
 	if (x != HUNTED_ISME)
 		return; /* Now forwarded or server didnt exist */
 
-	if (MyUser(client) && IsWebsocket(client))
-	{
-		sendnotice(client, "Sorry, for technical reasons it is not possible to REHASH "
-		                 "the local server from a WebSocket connection.");
-		/* Issue details:
-		 * websocket_handle_packet -> process_packet -> parse_client_queued ->
-		 * dopacket -> parse -> cmd_rehash... and then 'websocket' is unloaded so
-		 * we "cannot get back" as that websocket_handle_packet function is gone.
-		 *
-		 * Solution would be either to delay the rehash or to make websocket perm.
-		 * The latter removes all our ability to upgrade the module on the fly
-		 * and the former is rather ugly.. not going to do that hassle now anyway.
-		 */
-		return;
-	}
-
 	if (!MyConnect(client))
 	{
 #ifndef REMOTE_REHASH
@@ -582,7 +569,6 @@ CMD_FUNC(cmd_rehash)
 				sendnotice(client, "A rehash is already in progress");
 				return;
 			}
-			unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", client, "Rehashing server configuration file [by: $client.details]");
 			remote_rehash_client = client;
 			/* fallthrough... so we deal with this the same way as local rehashes */
 		}
diff --git a/src/socket.c b/src/socket.c
@@ -41,7 +41,6 @@ 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) */
@@ -92,12 +91,12 @@ void close_connections(void)
 }
 
 /** Accept an incoming connection.
- * @param listener_fd	The file descriptor of a listen() socket.
- * @param data		The listen { } block configuration data.
+ * @param listener	The listen { } block configuration data.
+ * @returns 1 if the connection was accepted (even if it was rejected),
+ * 0 if there is no more work to do (accept returned an error).
  */
-static void listener_accept(int listener_fd, int revents, void *data)
+static int listener_accept_wrapper(ConfigItem_listen *listener)
 {
-	ConfigItem_listen *listener = data;
 	int cli_fd;
 
 	if ((cli_fd = fd_accept(listener->fd)) < 0)
@@ -125,7 +124,7 @@ static void listener_accept(int listener_fd, int revents, void *data)
 			close_listener(listener);
 			start_listeners();
 		}
-		return;
+		return 0;
 	}
 
 	ircstats.is_ac++;
@@ -147,7 +146,7 @@ static void listener_accept(int listener_fd, int revents, void *data)
 			}
 			fd_close(cli_fd);
 			--OpenFiles;
-			return;
+			return 1;
 		}
 	} else
 	{
@@ -172,12 +171,31 @@ static void listener_accept(int listener_fd, int revents, void *data)
 
 			fd_close(cli_fd);
 			--OpenFiles;
-			return;
+			return 1;
 		}
 	}
 
 	/* add_connection() may fail. we just don't care. */
 	add_connection(listener, cli_fd);
+	return 1;
+}
+
+/** Accept an incoming connection.
+ * @param listener_fd	The file descriptor of a listen() socket.
+ * @param data		The listen { } block configuration data.
+ */
+static void listener_accept(int listener_fd, int revents, void *data)
+{
+	int i;
+
+	/* Accept clients, but only up to a maximum in each run,
+	 * as to allow some CPU available to existing clients.
+	 * Better refuse or lag a few new clients than become
+	 * unresponse to existing clients.
+	 */
+	for (i=0; i < 100; i++)
+		if (!listener_accept_wrapper((ConfigItem_listen *)data))
+			break;
 }
 
 int unreal_listen_inet(ConfigItem_listen *listener)
@@ -311,7 +329,7 @@ int unreal_listen_unix(ConfigItem_listen *listener)
 
 	set_sock_opts(listener->fd, NULL, listener->socket_type);
 
-	if (!unreal_bind(listener->fd, listener->file, 0, SOCKET_TYPE_UNIX))
+	if (!unreal_bind(listener->fd, listener->file, listener->mode, SOCKET_TYPE_UNIX))
 	{
 		unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
 		           "Could not listen on UNIX domain socket $file: $socket_error",
@@ -641,7 +659,6 @@ void close_connection(Client *client)
 		--OpenFiles;
 		DBufClear(&client->local->sendQ);
 		DBufClear(&client->local->recvQ);
-
 	}
 
 	client->direction = NULL;
@@ -790,30 +807,6 @@ const char *getpeerip(Client *client, int fd, int *port)
 	}
 }
 
-/** This checks set::max-unknown-connections-per-ip,
- * which is an important safety feature.
- */
-static int check_too_many_unknown_connections(Client *client)
-{
-	int cnt = 1;
-	Client *c;
-
-	if (!find_tkl_exception(TKL_CONNECT_FLOOD, client))
-	{
-		list_for_each_entry(c, &unknown_list, lclient_node)
-		{
-			if (!strcmp(client->ip,GetIP(c)))
-			{
-				cnt++;
-				if (cnt > iConf.max_unknown_connections_per_ip)
-					return 1;
-			}
-		}
-	}
-
-	return 0;
-}
-
 /** Process the incoming connection which has just been accepted.
  * This creates a client structure for the user.
  * The sockhost field is initialized with the ip# of the host.
@@ -830,12 +823,15 @@ Client *add_connection(ConfigItem_listen *listener, int fd)
 	Client *client;
 	const char *ip;
 	int port = 0;
+	Hook *h;
 
 	client = make_client(NULL, &me);
 	client->local->socket_type = listener->socket_type;
+	client->local->listener = listener;
+	client->local->listener->clients++;
 
 	if (listener->socket_type == SOCKET_TYPE_UNIX)
-		ip = "127.0.0.1";
+		ip = listener->spoof_ip ? listener->spoof_ip : "127.0.0.1";
 	else
 		ip = getpeerip(client, fd, &port);
 
@@ -855,6 +851,10 @@ Client *add_connection(ConfigItem_listen *listener, int fd)
 refuse_client:
 			ircstats.is_ref++;
 			client->local->fd = -2;
+			if (!list_empty(&client->client_node))
+				list_del(&client->client_node);
+			if (!list_empty(&client->lclient_node))
+				list_del(&client->lclient_node);
 			free_client(client);
 			fd_close(fd);
 			--OpenFiles;
@@ -874,37 +874,21 @@ refuse_client:
 		SetLocalhost(client);
 	}
 
-	if (!(listener->options & LISTENER_CONTROL))
+	add_client_to_list(client);
+	irccounts.unknown++;
+	client->status = CLIENT_STATUS_UNKNOWN;
+	list_add(&client->lclient_node, &unknown_list);
+
+	for (h = Hooks[HOOKTYPE_ACCEPT]; h; h = h->next)
 	{
-		/* Check set::max-unknown-connections-per-ip */
-		if (check_too_many_unknown_connections(client))
+		int value = (*(h->func.intfunc))(client);
+		if (value == HOOK_DENY)
 		{
-			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);
+			irccounts.unknown--;
 			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);
-
-	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 (value != HOOK_CONTINUE)
+			break;
 	}
 
 	if ((listener->options & LISTENER_TLS) && ctx_server)
@@ -916,6 +900,7 @@ refuse_client:
 			SetTLSAcceptHandshake(client);
 			if ((client->local->ssl = SSL_new(ctx)) == NULL)
 			{
+				irccounts.unknown--;
 				goto refuse_client;
 			}
 			SetTLS(client);
@@ -927,14 +912,14 @@ refuse_client:
 				SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
 				SSL_smart_shutdown(client->local->ssl);
 				SSL_free(client->local->ssl);
+				irccounts.unknown--;
 				goto refuse_client;
 			}
 		}
 	} else
-	if (listener->options & LISTENER_CONTROL)
-		start_of_control_client_handshake(client);
-	else
-		start_of_normal_client_handshake(client);
+	{
+		listener->start_handshake(client);
+	}
 	return client;
 }
 
@@ -1240,7 +1225,8 @@ int deliver_it(Client *client, char *str, int len, int *want_read)
 
 	if (IsDeadSocket(client) ||
 	    (!IsServer(client) && !IsUser(client) && !IsHandshake(client) &&
-	     !IsTLSHandshake(client) && !IsUnknown(client)))
+	     !IsTLSHandshake(client) && !IsUnknown(client) &&
+	     !IsControl(client) && !IsRPC(client)))
 	{
 		return -1;
 	}
@@ -1369,15 +1355,20 @@ int unreal_bind(int fd, const char *ip, int port, SocketType socket_type)
 	} else
 	{
 		struct sockaddr_un server;
-		mode_t saved_umask;
+		mode_t saved_umask, new_umask;
 		int ret;
 
+		if (port == 0)
+			new_umask = 077;
+		else
+			new_umask = port ^ 0777;
+
 		unlink(ip); /* (ignore errors) */
 
 		memset(&server, 0, sizeof(server));
 		server.sun_family = AF_UNIX;
 		strlcpy(server.sun_path, ip, sizeof(server.sun_path));
-		saved_umask = umask(077); // TODO: make this configurable
+		saved_umask = umask(new_umask);
 		ret = !bind(fd, (struct sockaddr *)&server, sizeof(server));
 		umask(saved_umask);
 
diff --git a/src/support.c b/src/support.c
@@ -82,6 +82,54 @@ char *strtoken(char **save, char *str, char *fs)
 	return (tmp);
 }
 
+/** Walk through a string of tokens, using a set of separators.
+ * This is the special version that won't skip/merge tokens,
+ * eg "a,,c" would return "a", then "" (empty), then "c".
+ * This in contrast to strtoken() which would return "a" and then "c".
+ * This strtoken_noskip() will also not skip tokens at the
+ * beginning, eg ",,c" would return "" (empty), "" (empty), "c".
+ *
+ * @param save	Pointer used for saving between calls
+ * @param str	String to parse (will be altered!)
+ * @param fs	Separator character(s)
+ * @returns substring (token)
+ * @note This function works similar to (but not identical?) to strtok_r().
+ */
+char *strtoken_noskip(char **save, char *str, char *fs)
+{
+	char *pos, *tmp;
+
+	if (str)
+	{
+		pos = str;	/* new string scan */
+	} else {
+		if (*save == NULL)
+		{
+			/* We reached the end of the string */
+			return NULL;
+		}
+		pos = *save; /* keep last position across calls */
+	}
+
+	tmp = pos; /* start position, used for returning later */
+
+	/* Hunt for next separator (fs in pos) */
+	while (*pos && !strchr(fs, *pos))
+		pos++;
+
+	if (!*pos)
+	{
+		/* Next call is end of string */
+		*save = NULL;
+		*pos++ = '\0';
+	} else {
+		*pos++ = '\0';
+		*save = pos;
+	}
+
+	return tmp;
+}
+
 /** Convert binary address to an IP string - like inet_ntop but will always return the uncompressed IPv6 form.
  * @param af	Address family (AF_INET, AF_INET6)
  * @param in	Address (binary)
@@ -155,6 +203,14 @@ void stripcrlf(char *c)
 	}
 }
 
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+	const char *end = memchr (s, 0, maxlen);
+	return end ? (size_t)(end - s) : maxlen;
+}
+#endif
+
 #ifndef HAVE_STRLCPY
 /** BSD'ish strlcpy().
  * The strlcpy() function copies up to size-1 characters from the
@@ -182,13 +238,11 @@ size_t strlcpy(char *dst, const char *src, size_t size)
  */
 size_t strlncpy(char *dst, const char *src, size_t size, size_t n)
 {
-	size_t len = strlen(src);
+	size_t len = strnlen(src, n);
 	size_t ret = len;
 
 	if (size <= 0)
 		return 0;
-	if (len > n)
-		len = n;
 	if (len >= size)
 		len = size - 1;
 	memcpy(dst, src, len);
@@ -230,15 +284,12 @@ size_t strlcat(char *dst, const char *src, size_t size)
 size_t strlncat(char *dst, const char *src, size_t size, size_t n)
 {
 	size_t len1 = strlen(dst);
-	size_t len2 = strlen(src);
+	size_t len2 = strnlen(src, n);
 	size_t ret = len1 + len2;
 
 	if (size <= len1)
 		return size;
 		
-	if (len2 > n)
-		len2 = n;
-
 	if (len1 + len2 >= size)
 		len2 = size - (len1 + 1);
 
@@ -856,6 +907,63 @@ const char *unreal_getfilename(const char *path)
 	return end;
 }
 
+/** Wrapper for mkdir() so you don't need ifdefs everywhere for Windows.
+ * @returns 0 on failure!! (like mkdir)
+ */
+int unreal_mkdir(const char *pathname, mode_t mode)
+{
+#ifdef _WIN32
+	return mkdir(pathname);
+#else
+	return mkdir(pathname, mode);
+#endif
+}
+
+/** Create the entire directory structure.
+ * @param dname	The directory name, eg /home/irc/unrealircd/logs/2022/08/05
+ * @param mode	The mode to create with, eg 0777. Ignored on Windows.
+ * @returns 1 on success, 0 on failure.
+ */
+int unreal_create_directory_structure(const char *dname, mode_t mode)
+{
+	if (unreal_mkdir(dname, mode) == 0)
+	{
+		/* Ok, that failed as well, we have some work to do:
+		 * for every path element run mkdir().
+		 */
+		int lastresult;
+		char buf[512], *p;
+		strlcpy(buf, dname, sizeof(buf)); /* work on a copy */
+		for (p=strchr(buf+1, '/'); p; p=strchr(p+1, '/'))
+		{
+			*p = '\0';
+			unreal_mkdir(buf,mode);
+			*p = '/';
+		}
+		/* Finally, try the complete path */
+		if (unreal_mkdir(dname, mode))
+			return 0; /* failed */
+		/* fallthrough.... */
+	}
+	return 1; /* success */
+}
+
+/** Create entire directory structure for a path with a filename.
+ * @param fname	The full path name, eg /home/irc/unrealircd/logs/2022/08/05/ircd.log
+ * @param mode	The mode to create with, eg 0777. Ignored on Windows.
+ * @notes This is used as an easier way to call unreal_create_directory_structure()
+ *        if you have a filename instead of the directory part.
+ * @returns 1 on success, 0 on failure.
+ */
+int unreal_create_directory_structure_for_file(const char *fname, mode_t mode)
+{
+	char buf[PATH_MAX+1];
+	const char *path = unreal_getpathname(fname, buf);
+	if (!path)
+		return 0;
+	return unreal_create_directory_structure(path, mode);
+}
+
 /** Returns the special module tmp name for a given path.
  * The original string is not modified.
  */
diff --git a/src/tls.c b/src/tls.c
@@ -215,7 +215,7 @@ void disable_ssl_protocols(SSL_CTX *ctx, TLSOptions *tlsoptions)
 	if ((tlsoptions->protocols & TLS_PROTOCOL_TLSV1) ||
 	    (tlsoptions->protocols & TLS_PROTOCOL_TLSV1_1))
 	{
-		SSL_CTX_set_security_level(ctx, 1);
+		SSL_CTX_set_security_level(ctx, 0);
 	}
 #endif
 
@@ -692,7 +692,14 @@ void unreal_tls_client_handshake(int fd, int revents, void *data)
 	switch (unreal_tls_connect(client, fd))
 	{
 		case -1:
+			SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
+			SSL_smart_shutdown(client->local->ssl);
+			SSL_free(client->local->ssl);
+			client->local->ssl = NULL;
+			ClearTLS(client);
+			SetDeadSocket(client);
 			fd_close(fd);
+			fd_unnotify(fd);
 			client->local->fd = -1;
 			--OpenFiles;
 			return;
@@ -780,7 +787,7 @@ int unreal_tls_accept(Client *client, int fd)
 		return -1;
 	}
 
-	start_of_normal_client_handshake(client);
+	client->local->listener->start_handshake(client);
 
 	return 1;
 }
@@ -1063,19 +1070,24 @@ int verify_certificate(SSL *ssl, const char *hostname, char **errstr)
 		return 0;
 	}
 
-#if 1
-	n = validate_hostname(hostname, cert);
+#ifdef HAS_X509_check_host
+	n = X509_check_host(cert, hostname, strlen(hostname), 0, NULL);
 	X509_free(cert);
-	if (n == MatchFound)
+	if (n == 1)
 		return 1; /* Hostname matched. All tests passed. */
 #else
-	/* TODO: make autoconf test for X509_check_host() and verify that this code works:
-	 * (When doing that, also disable the openssl_hostname_validation.c/.h code since
-	 *  it would be unused)
+	/* Fallback code for OpenSSL <1.0.2.
+	 * Wait... 1.0.1 is out of support since January 2017,
+	 * so why do we even support that in 2023 ?
+	 * An well, TODO: ditch this old TLS support in next major UnrealIRCd
+	 * along with all the other old OpenSSL checks in this tls.c :D
+	 * XXX: Actually our HAS_X509_check_host includes openssl/x509v3.h
+	 * which does not exist in 1.0.2 yet either (it is openssl/x509.h there).
+	 * And 1.0.2 is out of support since January 1st, 2020... just saying.
 	 */
-	n = X509_check_host(cert, hostname, strlen(hostname), X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, NULL);
+	n = validate_hostname(hostname, cert);
 	X509_free(cert);
-	if (n == 1)
+	if (n == MatchFound)
 		return 1; /* Hostname matched. All tests passed. */
 #endif
 
diff --git a/src/unrealdb.c b/src/unrealdb.c
@@ -21,7 +21,7 @@
 #include "unrealircd.h"
 
 /** @file
- * @brief Unreal database API - see @ref UnrealDBFunctions
+ * @brief UnrealIRCd database API - see @ref UnrealDBFunctions
  */
 
 /**
diff --git a/src/unrealircdctl.c b/src/unrealircdctl.c
@@ -238,6 +238,8 @@ int main(int argc, char *argv[])
 #ifdef _WIN32
 	chdir(".."); /* go up one level from "bin" */
 	init_winsock();
+#else
+	alarm(20); /* 20 second timeout */
 #endif
 	dbuf_init();
 	init_random();
diff --git a/src/url_curl.c b/src/url_curl.c
@@ -32,6 +32,7 @@ typedef struct Download Download;
 
 struct Download
 {
+	Download *prev, *next;
 	vFP callback;
 	void *callback_data;
 	FILE *file_fd;		/**< File open for writing (otherwise NULL) */
@@ -43,14 +44,32 @@ struct Download
 
 CURLM *multihandle = NULL;
 
+Download *downloads = NULL;
+
 void url_free_handle(Download *handle)
 {
+	DelListItem(handle, downloads);
 	if (handle->file_fd)
 		fclose(handle->file_fd);
 	safe_free(handle->url);
 	safe_free(handle);
 }
 
+void url_cancel_handle_by_callback_data(void *ptr)
+{
+	Download *d, *d_next;
+
+	for (d = downloads; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->callback_data == ptr)
+		{
+			d->callback = NULL;
+			d->callback_data = NULL;
+		}
+	}
+}
+
 /*
  * Sets up all of the SSL options necessary to support HTTPS/FTPS
  * transfers.
@@ -112,6 +131,11 @@ static void url_check_multi_handles(void)
 			fclose(handle->file_fd);
 			handle->file_fd = NULL;
 
+			if (handle->callback == NULL)
+			{
+				/* Request is already canceled, we don't care about the result, just clean up */
+				remove(handle->filename);
+			} else
 			if (msg->data.result == CURLE_OK)
 			{
 				if (code == 304 || (last_mod != -1 && last_mod <= handle->cachetime))
@@ -269,6 +293,7 @@ void download_file_async(const char *url, time_t cachetime, vFP callback, void *
 		safe_free(handle);
 		return;
 	}
+	AddListItem(handle, downloads);
 
 	handle->callback = callback;
 	handle->callback_data = callback_data;
diff --git a/src/url_unreal.c b/src/url_unreal.c
@@ -22,11 +22,6 @@
 
 /* Structs */
 
-typedef enum TransferEncoding {
-	TRANSFER_ENCODING_NONE=0,
-	TRANSFER_ENCODING_CHUNKED=1
-} TransferEncoding;
-
 /* Stores information about the async transfer.
  * Used to maintain information about the transfer
  * to trigger the callback upon completion.
@@ -115,13 +110,29 @@ void url_free_handle(Download *handle)
 	safe_free(handle);
 }
 
+void url_cancel_handle_by_callback_data(void *ptr)
+{
+	Download *d, *d_next;
+
+	for (d = downloads; d; d = d_next)
+	{
+		d_next = d->next;
+		if (d->callback_data == ptr)
+		{
+			d->callback = NULL;
+			d->callback_data = NULL;
+		}
+	}
+}
+
 void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
 {
 	va_list vl;
 	va_start(vl, pattern);
 	vsnprintf(handle->errorbuf, sizeof(handle->errorbuf), pattern, vl);
 	va_end(vl);
-	handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data);
+	if (handle->callback)
+		handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data);
 	url_free_handle(handle);
 }
 
@@ -825,7 +836,9 @@ void https_done(Download *handle)
 	fclose(handle->file_fd);
 	handle->file_fd = NULL;
 
-	if (!handle->got_response)
+	if (!handle->callback)
+		; /* No special action, request was cancelled */
+	else if (!handle->got_response)
 		handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data);
 	else
 	{
@@ -843,7 +856,8 @@ void https_done_cached(Download *handle)
 
 	fclose(handle->file_fd);
 	handle->file_fd = NULL;
-	handle->callback(url, NULL, NULL, 1, handle->callback_data);
+	if (handle->callback)
+		handle->callback(url, NULL, NULL, 1, handle->callback_data);
 	url_free_handle(handle);
 }
 
@@ -856,8 +870,12 @@ void https_redirect(Download *handle)
 	}
 	handle->redirects_remaining--;
 
-	download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data,
-	                    handle->url, handle->redirects_remaining);
+	if (handle->callback)
+	{
+		/* If still an outstanding request (not cancelled), follow the redirect.. */
+		download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data,
+				    handle->url, handle->redirects_remaining);
+	}
 	/* Don't call the hook, just free this, the new redirect from above will call the hook later */
 	url_free_handle(handle);
 }
diff --git a/src/user.c b/src/user.c
@@ -737,6 +737,25 @@ int hide_idle_time(Client *client, Client *target)
 	}
 }
 
+/** Get creation time of a client.
+ * @param client	The client to check (user, server, anything)
+ * @returns the time when the client first connected to IRC, or 0 for unknown.
+ */
+time_t get_creationtime(Client *client)
+{
+	const char *str;
+
+	/* Shortcut for local clients */
+	if (client->local)
+		return client->local->creationtime;
+
+	/* Otherwise, hopefully available through this... */
+	str = moddata_client_get(client, "creationtime");
+	if (!BadPtr(str) && (*str != '0'))
+		return atoll(str);
+	return 0;
+}
+
 /** Get how long a client is connected to IRC.
  * @param client	The client to check
  * @returns how long the client is connected to IRC (number of seconds)
@@ -829,6 +848,9 @@ void flood_limit_exceeded_log(Client *client, const char *floodname)
 {
 	char buf[1024];
 
+	// NOTE: If you ever change this format, there are a few more
+	// direct unreal_log() calls with "FLOOD_BLOCKED" in the file
+	// src/modules/targetfloodprot.c, so update those as well.
 	unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
 	           "Flood blocked ($flood_type) from $client.details [$client.ip]",
 	           log_data_string("flood_type", floodname));
diff --git a/src/utf8.c b/src/utf8.c
@@ -172,13 +172,20 @@ char *unrl_utf8_make_valid(const char *str, char *outputbuf, size_t outputbuflen
 	{
 		if (unrl_utf8_validate(remainder, &invalid))
 		{
-			if (!replaced && strictlen)
+			if (!replaced)
 			{
-				/* Caller wants us to go through the 'replaced' branch */
-				strlcpy(outputbuf, str, outputbuflen);
-				replaced = 1;
+				if (strictlen)
+				{
+					/* Caller wants us to go through the 'replaced' branch */
+					strlcpy(outputbuf, str, outputbuflen);
+					replaced = 1;
+				}
+				break;
+			} else {
+				/* We already replaced earlier, now just put the rest at the end. */
+				strlcat(outputbuf, remainder, outputbuflen);
+				break;
 			}
-			break;
 		}
 		replaced = 1;
 		valid_bytes = invalid - remainder;
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.4.2$SUFFIX"
+id="6.1.0$SUFFIX"
 echo "$id"
 
 if test -r version.c
diff --git a/src/whowas.c b/src/whowas.c
@@ -23,62 +23,84 @@
 // Consider making add_history an efunc? Or via a hook?
 // Some users may not want to load cmd_whowas at all.
 
-/* internally defined function */
-static void add_whowas_to_clist(WhoWas **, WhoWas *);
-static void del_whowas_from_clist(WhoWas **, WhoWas *);
-static void add_whowas_to_list(WhoWas **, WhoWas *);
-static void del_whowas_from_list(WhoWas **, WhoWas *);
+void add_whowas_to_clist(WhoWas **, WhoWas *);
+void del_whowas_from_clist(WhoWas **, WhoWas *);
+void add_whowas_to_list(WhoWas **, WhoWas *);
+void del_whowas_from_list(WhoWas **, WhoWas *);
 
 WhoWas MODVAR WHOWAS[NICKNAMEHISTORYLENGTH];
 WhoWas MODVAR *WHOWASHASH[WHOWAS_HASH_TABLE_SIZE];
 
 MODVAR int whowas_next = 0;
 
-void add_history(Client *client, int online)
+void free_whowas_fields(WhoWas *e)
 {
-	WhoWas *new;
-
-	new = &WHOWAS[whowas_next];
+	safe_free(e->name);
+	safe_free(e->hostname);
+	safe_free(e->virthost);
+	safe_free(e->realname);
+	safe_free(e->username);
+	safe_free(e->account);
+	safe_free(e->ip);
+	e->servername = NULL;
+	e->event = 0;
+	e->logon = 0;
+	e->logoff = 0;
+	e->connected_since = 0;
+
+	/* Remove from lists and reset hashv */
+	if (e->online)
+		del_whowas_from_clist(&(e->online->user->whowas), e);
+	del_whowas_from_list(&WHOWASHASH[e->hashv], e);
+	e->hashv = -1;
+}
 
-	if (new->hashv != -1)
-	{
-		safe_free(new->name);
-		safe_free(new->hostname);
-		safe_free(new->virthost);
-		safe_free(new->realname);
-		safe_free(new->username);
-		new->servername = NULL;
-
-		if (new->online)
-			del_whowas_from_clist(&(new->online->user->whowas), new);
-		del_whowas_from_list(&WHOWASHASH[new->hashv], new);
-	}
-	new->hashv = hash_whowas_name(client->name);
-	new->logoff = TStime();
-	new->umodes = client->umodes;
-	safe_strdup(new->name, client->name);
-	safe_strdup(new->username, client->user->username);
-	safe_strdup(new->hostname, client->user->realhost);
+void create_whowas_entry(Client *client, WhoWas *e, WhoWasEvent event)
+{
+	e->hashv = hash_whowas_name(client->name);
+	e->event = event;
+	e->connected_since = get_creationtime(client);
+	e->logon = client->lastnick;
+	e->logoff = TStime();
+	e->umodes = client->umodes;
+	safe_strdup(e->name, client->name);
+	safe_strdup(e->username, client->user->username);
+	safe_strdup(e->hostname, client->user->realhost);
+	safe_strdup(e->ip, client->ip);
 	if (client->user->virthost)
-		safe_strdup(new->virthost, client->user->virthost);
+		safe_strdup(e->virthost, client->user->virthost);
 	else
-		safe_strdup(new->virthost, "");
-	new->servername = client->user->server;
-	safe_strdup(new->realname, client->info);
+		safe_strdup(e->virthost, "");
+	e->servername = client->user->server;
+	safe_strdup(e->realname, client->info);
+	if (strcmp(client->user->account, "0"))
+		safe_strdup(e->account, client->user->account);
 
 	/* Its not string copied, a pointer to the scache hash is copied
 	   -Dianora
 	 */
-	/*  strlcpy(new->servername, client->user->server,HOSTLEN); */
-	new->servername = client->user->server;
+	/*  strlcpy(e->servername, client->user->server,HOSTLEN); */
+	e->servername = client->user->server;
+}
+
+void add_history(Client *client, int online, WhoWasEvent event)
+{
+	WhoWas *new;
+
+	new = &WHOWAS[whowas_next];
+
+	if (new->hashv != -1)
+		free_whowas_fields(new);
+
+	create_whowas_entry(client, new, event);
 
 	if (online)
 	{
 		new->online = client;
 		add_whowas_to_clist(&(client->user->whowas), new);
-	}
-	else
+	} else {
 		new->online = NULL;
+	}
 	add_whowas_to_list(&WHOWASHASH[new->hashv], new);
 	whowas_next++;
 	if (whowas_next == NICKNAMEHISTORYLENGTH)
@@ -149,7 +171,7 @@ void initwhowas()
 		WHOWASHASH[i] = NULL;
 }
 
-static void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
+void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
 {
 	whowas->cprev = NULL;
 	if ((whowas->cnext = *bucket) != NULL)
@@ -157,7 +179,7 @@ static void add_whowas_to_clist(WhoWas ** bucket, WhoWas * whowas)
 	*bucket = whowas;
 }
 
-static void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
+void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
 {
 	if (whowas->cprev)
 		whowas->cprev->cnext = whowas->cnext;
@@ -167,7 +189,7 @@ static void del_whowas_from_clist(WhoWas ** bucket, WhoWas * whowas)
 		whowas->cnext->cprev = whowas->cprev;
 }
 
-static void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
+void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
 {
 	whowas->prev = NULL;
 	if ((whowas->next = *bucket) != NULL)
@@ -175,7 +197,7 @@ static void add_whowas_to_list(WhoWas ** bucket, WhoWas * whowas)
 	*bucket = whowas;
 }
 
-static void del_whowas_from_list(WhoWas ** bucket, WhoWas * whowas)
+void del_whowas_from_list(WhoWas ** bucket, WhoWas * whowas)
 {
 	if (whowas->prev)
 		whowas->prev->next = whowas->next;
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.4.2"
+    version="6.1.0.0"
     type="win32"
 />
 <description>Internet Relay Chat Daemon</description>
diff --git a/src/windows/unrealinst.iss b/src/windows/unrealinst.iss
@@ -6,7 +6,7 @@
 
 [Setup]
 AppName=UnrealIRCd 6
-AppVerName=UnrealIRCd 6.0.4.2
+AppVerName=UnrealIRCd 6.1.0
 AppPublisher=UnrealIRCd Team
 AppPublisherURL=https://www.unrealircd.org
 AppSupportURL=https://www.unrealircd.org
@@ -57,6 +57,7 @@ Source: "src\modules\*.dll"; DestDir: "{app}\modules"; Flags: ignoreversion sign
 Source: "src\modules\chanmodes\*.dll"; DestDir: "{app}\modules\chanmodes"; Flags: ignoreversion signonce
 Source: "src\modules\usermodes\*.dll"; DestDir: "{app}\modules\usermodes"; Flags: ignoreversion signonce
 Source: "src\modules\extbans\*.dll"; DestDir: "{app}\modules\extbans"; Flags: ignoreversion signonce
+Source: "src\modules\rpc\*.dll"; DestDir: "{app}\modules\rpc"; Flags: ignoreversion signonce
 Source: "src\modules\third\*.dll"; DestDir: "{app}\modules\third"; Flags: ignoreversion skipifsourcedoesntexist signonce
 
 ; Libraries
diff --git a/unrealircd.in b/unrealircd.in
@@ -2,21 +2,40 @@
 
 PID_FILE="@PIDFILE@"
 PID_BACKUP="@PIDFILE@.bak"
-UNREALIRCDCTL="@BINDIR@/unrealircdctl"
+BINDIR="@BINDIR@"
+UNREALIRCDCTL="$BINDIR/unrealircdctl"
+IRCD="$BINDIR/unrealircd"
+BUILDDIR="@BUILDDIR@"
+CONFDIR="@CONFDIR@"
+TMPDIR="@TMPDIR@"
+SCRIPTDIR="@SCRIPTDIR@"
+MODULESDIR="@MODULESDIR@"
 
 # 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.
-export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1:log_path=@TMPDIR@/unrealircd_asan:detect_leaks=0"
+export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1:log_path=$TMPDIR/unrealircd_asan:detect_leaks=0"
 
-if [ ! -f @BINDIR@/unrealircd ]; then
-	echo "ERROR: Could not find the IRCd binary (@BINDIR@/unrealircd)"
+if [ ! -f $IRCD ]; then
+	echo "ERROR: Could not find the IRCd binary ($IRCD)"
 	echo "This could mean two things:"
 	echo "1) You forgot to run 'make install' after running 'make'"
 	echo "2) You answered a ./Config question incorrectly"
 	exit
 fi
+if [ ! -d "$TMPDIR" ]; then
+	mkdir "$TMPDIR"
+fi
+
 if [ "$1" = "start" ] ; then
-	echo "Starting UnrealIRCd"
+	if [ -r $PID_FILE ] ; then
+		if kill -CHLD `cat $PID_FILE` 1>/dev/null 2>&1; then
+			if $UNREALIRCDCTL status 1>/dev/null 2>&1; then
+				echo "UnrealIRCd is already running (PID `cat $PID_FILE`)."
+				echo "To restart UnrealIRCd, use: $0 restart"
+				exit 1
+			fi
+		fi
+	fi
 	if [ -r $PID_FILE ] ; then
 		mv -f $PID_FILE $PID_BACKUP
 	fi
@@ -24,37 +43,49 @@ if [ "$1" = "start" ] ; then
 	# Check if ~/Unrealxxx/unrealircd.conf exists but the file
 	# ~/unrealircd/conf/unrealircd.conf does not.
 	# If so, then assume a user-build and give the user a nice hint...
-	if [ ! -f @CONFDIR@/unrealircd.conf -a -f @BUILDDIR@/unrealircd.conf ]; then
+	if [ ! -f $CONFDIR/unrealircd.conf -a -f $BUILDDIR/unrealircd.conf ]; then
 		echo ""
-		echo "There is no unrealircd.conf in @CONFDIR@"
-		echo "However I did find an unrealircd.conf in @BUILDDIR@"
-		echo "With UnrealIRCd 4 you should no longer run the IRCd from @BUILDDIR@."
-		echo "You should 'cd @SCRIPTDIR@' and work from there."
+		echo "There is no unrealircd.conf in $CONFDIR"
+		echo "However I did find an unrealircd.conf in $BUILDDIR"
+		echo "With UnrealIRCd 4 you should no longer run the IRCd from $BUILDDIR."
+		echo "You should 'cd $SCRIPTDIR' and work from there."
 		echo "See https://www.unrealircd.org/docs/UnrealIRCd_files_and_directories"
 		exit 1
 	fi
-	if [ ! -f @CONFDIR@/unrealircd.conf ]; then
+	if [ ! -f $CONFDIR/unrealircd.conf ]; then
 		echo ""
-		echo "The configuration file does not exist (@CONFDIR@/unrealircd.conf)."
+		echo "The configuration file does not exist ($CONFDIR/unrealircd.conf)."
 		echo "Create one using the example configuration file, see the documentation:"
 		echo "https://www.unrealircd.org/docs/Installing_from_source#Creating_a_configuration_file"
 		exit 1
 	fi
-	@BINDIR@/unrealircd
+
+	echo "Starting UnrealIRCd"
+
+	$IRCD
 	if [ $? -ne 0 ] ; then
-		echo "====================================================="
-		echo "UnrealIRCd failed to start. Check above for possible errors."
-		echo "If you don't understand the problem, then have a look at our:"
-		echo "* FAQ (Frequently Asked Questions): https://www.unrealircd.org/docs/FAQ"
-		echo "* Documentation: https://www.unrealircd.org/docs/"
-		echo "====================================================="
 		if [ -r $PID_BACKUP ] ; then
 			mv -f $PID_BACKUP $PID_FILE
 		fi
+		# Try to be helpful...
+		if ldd $IRCD 2>&1|grep -qF '=> not found'; then
+			echo "========================================================"
+			echo "UnrealIRCd failed to start due to missing libraries."
+			echo "Maybe you need to recompile UnrealIRCd? See"
+			echo "https://www.unrealircd.org/docs/FAQ#shared-library-error"
+			echo "========================================================"
+		else
+			echo "====================================================="
+			echo "UnrealIRCd failed to start. Check above for possible errors."
+			echo "If you don't understand the problem, then have a look at our:"
+			echo "* FAQ (Frequently Asked Questions): https://www.unrealircd.org/docs/FAQ"
+			echo "* Documentation: https://www.unrealircd.org/docs/"
+			echo "====================================================="
+		fi
 		exit 1
 	fi
 	# Now check if we need to create a crash report.
-	@BINDIR@/unrealircd -R
+	$IRCD -R
 elif [ "$1" = "stop" ] ; then
 	echo -n "Stopping UnrealIRCd"
 	if [ ! -r $PID_FILE ] ; then
@@ -96,7 +127,16 @@ elif [ "$1" = "module-status" ] ; then
 elif [ "$1" = "reloadtls" ] ; then
 	$UNREALIRCDCTL $*
 elif [ "$1" = "restart" ] ; then
-	echo "Restarting UnrealIRCd"
+	echo "Validating configuration..."
+	TMPF="$TMPDIR/configtest.txt"
+	if ! $0 configtest 1>$TMPF 2>&1; then
+		cat $TMPF
+		rm -f $TMPF
+		echo ""
+		echo "Configuration test failed. Server is NOT restarted."
+		exit 1
+	fi
+	echo "Configuration test OK."
 	$0 stop
 	$0 start
 elif [ "$1" = "croncheck" ] ; then
@@ -111,20 +151,18 @@ elif [ "$1" = "croncheck" ] ; then
 	echo "UnrealIRCd is not running. Starting now..."
 	$0 start
 elif [ "$1" = "configtest" ] ; then
-	@BINDIR@/unrealircd -c
+	$IRCD -c
 elif [ "$1" = "module" ] ; then
 	shift
-	@BINDIR@/unrealircd -m $*
+	$IRCD -m $*
 elif [ "$1" = "mkpasswd" ] ; then
 	$UNREALIRCDCTL $*
 elif [ "$1" = "version" ] ; then
-	@BINDIR@/unrealircd -v
+	$IRCD -v
 elif [ "$1" = "gencloak" ] ; then
 	$UNREALIRCDCTL $*
 elif [ "$1" = "backtrace" ] ; then
-	cd @TMPDIR@
-
-	modpath="@MODULESDIR@"
+	cd $TMPDIR
 
 	# Find the corefile
 	echo "Core files available:"
@@ -170,15 +208,15 @@ elif [ "$1" = "backtrace" ] ; then
 
 	# The tmp/*.so files are often already deleted. Here we have some
 	# (ugly) scripting to recreate the tmp/*.so links to the modules *.so files...
-	echo 'info sharedlibrary'|gdb @BINDIR@/unrealircd $corefile 2>/dev/null|\
+	echo 'info sharedlibrary'|gdb $IRCD $corefile 2>/dev/null|\
 	grep No|grep tmp/|awk '{ print $2 }'|\
-	awk -F '.' "{ system(\"[ -f $modpath/\" \$2 \"/\" \$3 \".so ] && ln -s $modpath/\" \$2 \"/\" \$3 \".so \" \$0 \" || ln -s $modpath/\" \$2 \".so \" \$0) }"
+	awk -F '.' "{ system(\"[ -f $MODULESDIR/\" \$2 \"/\" \$3 \".so ] && ln -s $MODULESDIR/\" \$2 \"/\" \$3 \".so \" \$0 \" || ln -s $MODULESDIR/\" \$2 \".so \" \$0) }"
 	
 	echo ""
 	echo "=================== START HERE ======================"
 	echo "BACKTRACE:"
 
-cat >@TMPDIR@/gdb.commands << __EOF__
+cat >$TMPDIR/gdb.commands << __EOF__
 bt
 echo \n
 frame
@@ -189,8 +227,8 @@ bt 3 full
 quit
 __EOF__
 
-	gdb -batch -x @TMPDIR@/gdb.commands @BINDIR@/unrealircd $corefile
-	rm -f @TMPDIR@/gdb.commands
+	gdb -batch -x $TMPDIR/gdb.commands $IRCD $corefile
+	rm -f $TMPDIR/gdb.commands
 	echo "GCC: `gcc -v 2>&1|tail -n 1`"
 	echo "UNAME: `uname -a`"
 	echo "UNREAL: `$0 version`"
@@ -213,7 +251,7 @@ __EOF__
 elif [ "$1" = "spki" -o "$1" = "spkifp" ] ; then
 	$UNREALIRCDCTL $*
 elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
-	if [ ! -d "@BUILDDIR@" ]; then
+	if [ ! -d "$BUILDDIR" ]; then
 		echo "UnrealIRCd source not found. Sorry, it is not possible to patch."
 		exit 1
 	fi
@@ -226,7 +264,7 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 		echo "On Linux consider running 'apt install wget' or a similar command."
 		exit 1
 	fi
-	cd "@BUILDDIR@" || exit 1
+	cd "$BUILDDIR" || exit 1
 
 	# Weird way to get version, but ok.
 	UNREALVER="`./configure --version|head -n1|awk '{ print $3 }'`"
@@ -256,7 +294,7 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 	make || gmake || exit 1
 	make install || gmake install || exit 1
 
-	cd @SCRIPTDIR@
+	cd $SCRIPTDIR
 	if [ "$1" = "hot-patch" ]; then
 		echo "Patch applied successfully and installed. Rehashing your IRCd..."
 		if ./unrealircd rehash; then
@@ -272,10 +310,10 @@ elif [ "$1" = "hot-patch" -o "$1" = "cold-patch" ] ; then
 		echo "Patch applied successfully. You must now restart your IRC server."
 	fi
 elif [ "$1" = "upgrade" ] ; then
-	@BINDIR@/unrealircd-upgrade-script $*
+	$BINDIR/unrealircd-upgrade-script $*
 	exit
 elif [ "$1" = "genlinkblock" ] ; then
-	@BINDIR@/unrealircd -L
+	$IRCD -L
 else
 	if [ "$1" = "" ]; then
 		echo "This script expects a parameter. Use:"
@@ -291,8 +329,6 @@ else
 	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"
 	echo "unrealircd mkpasswd      Hash a password"
 	echo "unrealircd version       Display the UnrealIRCd version"
 	echo "unrealircd module        Install and uninstall 3rd party modules"